/* P1-7: HA Foundation integration tests — assembled system */ #include #include #include #include #include #include "ha.h" /* Test 1: Single-node startup: after ha_init + ha_do_promote(priority=0), * role must be PRIMARY */ static void test_single_node_starts_as_primary(void) { ha_state_t ha; ha_config_t cfg = { .heartbeat_interval_ms = 3000, .heartbeat_timeout_ms = 10000, .election_timeout_ms = 2000, .probe_connect_timeout_ms = 500, .max_promotion_retries = 5, }; ha_init(&ha, &cfg, 0 /* priority 0 = highest */); /* Simulate am_i_primary() decision for priority-0 node */ ha_do_promote(&ha); TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY, (int)atomic_load_explicit(&ha.role, memory_order_acquire)); ha_fini(&ha); printf("test_single_node_starts_as_primary: PASS\n"); } /* Test 2: Term fencing — promote increments term; second promote increments again */ static void test_term_fencing_monotonic(void) { ha_state_t ha; ha_config_t cfg = { .heartbeat_interval_ms = 3000, .heartbeat_timeout_ms = 10000, .election_timeout_ms = 2000, .probe_connect_timeout_ms = 500, .max_promotion_retries = 5, }; ha_init(&ha, &cfg, 0); uint64_t t0 = atomic_load_explicit(&ha.term, memory_order_acquire); ha_do_promote(&ha); uint64_t t1 = atomic_load_explicit(&ha.term, memory_order_acquire); ha_do_demote(&ha); ha_do_promote(&ha); uint64_t t2 = atomic_load_explicit(&ha.term, memory_order_acquire); TEST_ASSERT_TRUE(t1 > t0); TEST_ASSERT_TRUE(t2 > t1); ha_fini(&ha); printf("test_term_fencing_monotonic: PASS\n"); } /* Test 4: Redirect guard — primary never redirects; standby with known * primary redirects; standby with no known primary reports nomaster */ static void test_redirect_guard_integration(void) { ha_config_t cfg = { .heartbeat_interval_ms = 3000, .heartbeat_timeout_ms = 10000, .election_timeout_ms = 2000, .probe_connect_timeout_ms = 500, .max_promotion_retries = 5, }; /* Primary: no redirect */ ha_state_t ha_primary; ha_init(&ha_primary, &cfg, 0); atomic_store_explicit(&ha_primary.role, (int32_t)CTRL_PRIMARY, memory_order_release); TEST_ASSERT_EQUAL_INT(0, ha_should_redirect(&ha_primary)); /* Standby with known primary: redirect */ ha_state_t ha_standby; ha_init(&ha_standby, &cfg, 1); strncpy(ha_standby.primary_host, "10.0.0.1", sizeof(ha_standby.primary_host) - 1); ha_standby.primary_port = 7000; TEST_ASSERT_EQUAL_INT(1, ha_should_redirect(&ha_standby)); TEST_ASSERT_EQUAL_INT(0, ha_no_primary_known(&ha_standby)); /* Standby with no known primary: no redirect, nomaster */ ha_state_t ha_cold; ha_init(&ha_cold, &cfg, 2); TEST_ASSERT_EQUAL_INT(0, ha_should_redirect(&ha_cold)); TEST_ASSERT_EQUAL_INT(1, ha_no_primary_known(&ha_cold)); ha_fini(&ha_primary); ha_fini(&ha_standby); ha_fini(&ha_cold); printf("test_redirect_guard_integration: PASS\n"); } /* Test 5: Hook idempotency — double-promote does not register hooks twice * (full hook registration needs a cluster_t — this tests the hooks_registered * guard) */ static void test_hooks_registered_guard_integration(void) { ha_state_t ha; ha_config_t cfg = { .heartbeat_interval_ms = 3000, .heartbeat_timeout_ms = 10000, .election_timeout_ms = 2000, .probe_connect_timeout_ms = 500, .max_promotion_retries = 5, }; ha_init(&ha, &cfg, 0); TEST_ASSERT_EQUAL_INT(0, ha.hooks_registered); /* Simulate idempotency: hooks_registered = 1 after first promote */ ha.hooks_registered = 1; /* Second promote should see guard and skip re-registration */ TEST_ASSERT_TRUE(ha.hooks_registered == 1); /* guard still set */ /* Demote resets guard */ ha_do_demote(&ha); TEST_ASSERT_EQUAL_INT(0, ha.hooks_registered); ha_fini(&ha); printf("test_hooks_registered_guard_integration: PASS\n"); } void test_p1_integration(void) { RUN_TEST(test_single_node_starts_as_primary); RUN_TEST(test_term_fencing_monotonic); RUN_TEST(test_redirect_guard_integration); RUN_TEST(test_hooks_registered_guard_integration); }