feat(component-failover-adoption): add candidate-list-to-client-config helper

xce_cluster_config_to_client_config() converts a validated candidate
list into a fully-populated xce_client_config_t, copying candidates
in order with no special-casing for a single candidate. The
truncate-with-warning safety net stays in place as a defensive guard
now that N_MAX_MASTERS and MAX_HA_CANDIDATES match.

Also removes a WARN check in xce_cluster_config_validate() that
became unreachable dead code once task 1.1 reconciled the two
candidate-count constants (both checks tested the same field against
the same constant with no mutation between them).

Task 1.2 of component-failover-adoption (Requirement 1 AC1-AC7).
This commit is contained in:
Rongsong Shen 2026-08-11 04:57:31 +00:00
parent fe8b4b86bf
commit 210faa2b7a
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
4 changed files with 190 additions and 5 deletions

View file

@ -3,6 +3,7 @@
#include <string.h>
#include <unity/unity.h>
#include "client.h"
#include "xce/cluster.h"
/* ── Test seam: mock connect function ───────────────────────────── */
@ -163,6 +164,132 @@ static void test_reconnect_step_backs_off_on_failure(void)
printf("test_reconnect_step_backs_off_on_failure: PASS\n");
}
/* ── xce_cluster_config_to_client_config() tests ──────────────────
* Builds a cfg with `n` candidates at distinct host/port values, so the
* copy-in-order behavior can be verified precisely. */
static xce_cluster_config_t make_config_with_n_candidates(int n)
{
xce_cluster_config_t cfg = {0};
for (int i = 0; i < n && i < MAX_HA_CANDIDATES; i++) {
char host[XCE_MAXHOSTNAME_LEN];
snprintf(host, sizeof(host), "10.0.2.%d", i + 1);
strncpy(cfg.candidates[i].hostname, host, XCE_MAXHOSTNAME_LEN - 1);
cfg.candidates[i].port = 9000 + i;
}
cfg.candidates_count = n;
strncpy(cfg.identity, "conv-test", sizeof(cfg.identity) - 1);
return cfg;
}
static void assert_converted_masters_match(const xce_cluster_config_t* cfg,
const xce_client_config_t* out,
int expected_count)
{
TEST_ASSERT_EQUAL_INT(expected_count, out->nmasters);
for (int i = 0; i < expected_count; i++) {
TEST_ASSERT_EQUAL_STRING(cfg->candidates[i].hostname,
out->masters[i]);
TEST_ASSERT_EQUAL_INT(cfg->candidates[i].port, out->master_ports[i]);
}
}
/* Test 10: one candidate goes through the identical code path as any other
* count -- no special-cased "single controller" branch (Req 1.5). */
static void test_convert_one_candidate(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(1);
xce_client_config_t out = {0};
int rc = xce_cluster_config_to_client_config(&cfg, &out);
TEST_ASSERT_EQUAL_INT(0, rc);
assert_converted_masters_match(&cfg, &out, 1);
printf("test_convert_one_candidate: PASS\n");
}
/* Test 11: three candidates -- ordinary multi-candidate case. */
static void test_convert_three_candidates(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(3);
xce_client_config_t out = {0};
int rc = xce_cluster_config_to_client_config(&cfg, &out);
TEST_ASSERT_EQUAL_INT(0, rc);
assert_converted_masters_match(&cfg, &out, 3);
printf("test_convert_three_candidates: PASS\n");
}
/* Test 12: four candidates -- the N_MAX_MASTERS boundary, still the
* ordinary (non-clamped) path since N_MAX_MASTERS == MAX_HA_CANDIDATES. */
static void test_convert_four_candidates(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(4);
xce_client_config_t out = {0};
int rc = xce_cluster_config_to_client_config(&cfg, &out);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT(N_MAX_MASTERS, 4);
assert_converted_masters_match(&cfg, &out, 4);
printf("test_convert_four_candidates: PASS\n");
}
/* Test 13: NULL cfg is rejected. */
static void test_convert_rejects_null_cfg(void)
{
xce_client_config_t out = {0};
int rc = xce_cluster_config_to_client_config(NULL, &out);
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
printf("test_convert_rejects_null_cfg: PASS\n");
}
/* Test 14: NULL out is rejected. */
static void test_convert_rejects_null_out(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(1);
int rc = xce_cluster_config_to_client_config(&cfg, NULL);
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
printf("test_convert_rejects_null_out: PASS\n");
}
/* Test 15: candidates_count < 1 is rejected. */
static void test_convert_rejects_zero_candidates(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(0);
xce_client_config_t out = {0};
int rc = xce_cluster_config_to_client_config(&cfg, &out);
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
printf("test_convert_rejects_zero_candidates: PASS\n");
}
/* Test 16: an artificially invalid over-count input (candidates_count >
* N_MAX_MASTERS, bypassing xce_cluster_config_validate()) exercises the
* defensive truncation path -- clamps to N_MAX_MASTERS and still populates
* the first N_MAX_MASTERS candidates correctly. A properly validated input
* can never reach this path today since N_MAX_MASTERS == MAX_HA_CANDIDATES. */
static void test_convert_truncates_over_max_candidates(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(MAX_HA_CANDIDATES);
cfg.candidates_count = MAX_HA_CANDIDATES + 1; /* artificially invalid */
xce_client_config_t out = {0};
int rc = xce_cluster_config_to_client_config(&cfg, &out);
TEST_ASSERT_EQUAL_INT(0, rc);
assert_converted_masters_match(&cfg, &out, N_MAX_MASTERS);
printf("test_convert_truncates_over_max_candidates: PASS\n");
}
/* Test 17: the helper does not set identity/auth fields -- caller sets
* out->from / out->key separately. */
static void test_convert_does_not_set_identity_fields(void)
{
xce_cluster_config_t cfg = make_config_with_n_candidates(2);
xce_client_config_t out = {0};
char sentinel_from[] = "sentinel-from";
char sentinel_key[] = "sentinel-key";
out.from = sentinel_from;
out.key = sentinel_key;
int rc = xce_cluster_config_to_client_config(&cfg, &out);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_PTR(sentinel_from, out.from);
TEST_ASSERT_EQUAL_PTR(sentinel_key, out.key);
printf("test_convert_does_not_set_identity_fields: PASS\n");
}
/* ── Test group entry point ─────────────────────────────────────── */
void test_cluster(void)
{
@ -175,4 +302,12 @@ void test_cluster(void)
RUN_TEST(test_connect_returns_null_all_fail);
RUN_TEST(test_reconnect_step_reaches_connected);
RUN_TEST(test_reconnect_step_backs_off_on_failure);
RUN_TEST(test_convert_one_candidate);
RUN_TEST(test_convert_three_candidates);
RUN_TEST(test_convert_four_candidates);
RUN_TEST(test_convert_rejects_null_cfg);
RUN_TEST(test_convert_rejects_null_out);
RUN_TEST(test_convert_rejects_zero_candidates);
RUN_TEST(test_convert_truncates_over_max_candidates);
RUN_TEST(test_convert_does_not_set_identity_fields);
}