feat(controller-status): xcecli_ctrlstatus_primary authoritative reach-the-primary read (task 3.2)

This commit is contained in:
Rongsong Shen 2026-06-20 05:32:18 +00:00
parent 691cd9199e
commit 03813bf560
4 changed files with 291 additions and 2 deletions

View file

@ -21,6 +21,7 @@
#include <unity/unity.h>
#include "client.h"
#include "config.h" /* XCE_MAXHOSTNAME_LEN */
#include "request.h"
#include "xce/types.h"
@ -195,12 +196,141 @@ static void test_ctrlstatus_roundtrip_unknown_primary(void)
static void test_xcecli_ctrlstatus_null_ctx_typed_failure(void)
{
xce_ctrlstatus_t* status = (xce_ctrlstatus_t*)0xdeadbeef;
int rc = xcecli_ctrlstatus(NULL, &status);
int rc = xcecli_ctrlstatus(NULL, &status);
/* typed negative, not a partial/empty success */
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
printf("test_xcecli_ctrlstatus_null_ctx_typed_failure: PASS\n");
}
/* ── authoritative (primary-only) read: task 3.2 ─────────────────────────── */
/* The authoritative call must reject a NULL/unconnected ctx with the same
* typed negative as the any-node read — it must NEVER hand back a hint reply
* as the authoritative result (Req 9.2). The `status` out-param is left
* untouched on failure (it still holds the poisoned sentinel below). */
static void test_xcecli_ctrlstatus_primary_null_ctx_typed_failure(void)
{
xce_ctrlstatus_t* status = (xce_ctrlstatus_t*)0xdeadbeef;
int rc = xcecli_ctrlstatus_primary(NULL, &status);
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
/* never returned a hint reply as the authoritative result */
TEST_ASSERT_EQUAL_PTR((xce_ctrlstatus_t*)0xdeadbeef, status);
printf("test_xcecli_ctrlstatus_primary_null_ctx_typed_failure: PASS\n");
}
/* NULL status out-param is also a typed failure (boundary validation). */
static void test_xcecli_ctrlstatus_primary_null_out_typed_failure(void)
{
int rc = xcecli_ctrlstatus_primary(NULL, NULL);
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
printf("test_xcecli_ctrlstatus_primary_null_out_typed_failure: PASS\n");
}
/* The pure authoritative-decision helper: a reply is authoritative iff the
* responder's role is PRIMARY (ctrl_role_t PRIMARY == 0). A standby (1),
* init (2), or electing (3) responder is a non-authoritative HINT — the call
* must keep following the redirect (Req 9.1, 2.3, 3.2). */
static void test_ctrlstatus_is_authoritative_decision(void)
{
xce_ctrlstatus_t reply;
memset(&reply, 0, sizeof(reply));
reply.responderRole = 0; /* PRIMARY */
TEST_ASSERT_TRUE(xcecli_ctrlstatus_is_authoritative(&reply));
reply.responderRole = 1; /* STANDBY */
TEST_ASSERT_FALSE(xcecli_ctrlstatus_is_authoritative(&reply));
reply.responderRole = 2; /* INIT */
TEST_ASSERT_FALSE(xcecli_ctrlstatus_is_authoritative(&reply));
reply.responderRole = 3; /* ELECTING */
TEST_ASSERT_FALSE(xcecli_ctrlstatus_is_authoritative(&reply));
/* NULL reply is never authoritative (defensive). */
TEST_ASSERT_FALSE(xcecli_ctrlstatus_is_authoritative(NULL));
printf("test_ctrlstatus_is_authoritative_decision: PASS\n");
}
/* The pure next-target helper: given a standby's hint reply, map the reported
* primaryName to its host:port via the reply's peers list so the next query
* can target the primary directly (the redirect step of Req 9.1). */
static void test_ctrlstatus_primary_addr_from_peers(void)
{
xce_ctrlpeer_t p0 = { .name = "ctrl-a", .host = "10.0.0.1", .port = 9991 };
xce_ctrlpeer_t p1 = { .name = "ctrl-b", .host = "10.0.0.2", .port = 9992 };
xce_ctrlpeer_t* peerlist[2] = { &p0, &p1 };
xce_ctrlstatus_t hint;
memset(&hint, 0, sizeof(hint));
hint.responderName = "ctrl-a";
hint.responderRole = 1; /* STANDBY */
hint.primaryName = "ctrl-b"; /* the standby knows ctrl-b is primary */
hint.peers.count = 2;
hint.peers.list = peerlist;
hint.peerCount = 2;
char host[XCE_MAXHOSTNAME_LEN];
uint16_t port = 0;
int rc = xcecli_ctrlstatus_primary_addr(&hint, host, sizeof(host), &port);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_STRING("10.0.0.2", host);
TEST_ASSERT_EQUAL_UINT16(9992, port);
printf("test_ctrlstatus_primary_addr_from_peers: PASS\n");
}
/* When the standby reports no known primary (empty primaryName) or the named
* primary is not in the peers list, the address lookup fails (-ENOENT) so the
* caller falls back to plain master rotation rather than targeting a bogus
* address (Req 2.3 unknown-primary, redirect best-effort). */
static void test_ctrlstatus_primary_addr_unknown_or_missing(void)
{
xce_ctrlpeer_t p0 = { .name = "ctrl-a", .host = "10.0.0.1", .port = 9991 };
xce_ctrlpeer_t* peerlist[1] = { &p0 };
xce_ctrlstatus_t hint;
memset(&hint, 0, sizeof(hint));
hint.responderName = "ctrl-a";
hint.responderRole = 3; /* ELECTING */
hint.peers.count = 1;
hint.peers.list = peerlist;
hint.peerCount = 1;
char host[XCE_MAXHOSTNAME_LEN];
uint16_t port = 7;
/* unknown primary (empty) */
hint.primaryName = "";
TEST_ASSERT_EQUAL_INT(-ENOENT, xcecli_ctrlstatus_primary_addr(
&hint, host, sizeof(host), &port));
/* NULL primary */
hint.primaryName = NULL;
TEST_ASSERT_EQUAL_INT(-ENOENT, xcecli_ctrlstatus_primary_addr(
&hint, host, sizeof(host), &port));
/* named primary not present in peers */
hint.primaryName = "ctrl-z";
TEST_ASSERT_EQUAL_INT(-ENOENT, xcecli_ctrlstatus_primary_addr(
&hint, host, sizeof(host), &port));
/* NULL/zero args are rejected */
hint.primaryName = "ctrl-a";
TEST_ASSERT_EQUAL_INT(-EINVAL, xcecli_ctrlstatus_primary_addr(
NULL, host, sizeof(host), &port));
TEST_ASSERT_EQUAL_INT(-EINVAL, xcecli_ctrlstatus_primary_addr(
&hint, NULL, sizeof(host), &port));
TEST_ASSERT_EQUAL_INT(
-EINVAL, xcecli_ctrlstatus_primary_addr(&hint, host, 0, &port));
TEST_ASSERT_EQUAL_INT(
-EINVAL,
xcecli_ctrlstatus_primary_addr(&hint, host, sizeof(host), NULL));
printf("test_ctrlstatus_primary_addr_unknown_or_missing: PASS\n");
}
/* ── Suite entry point ──────────────────────────────────────────────────── */
void test_ctrlstatus(void)
@ -208,4 +338,9 @@ void test_ctrlstatus(void)
RUN_TEST(test_ctrlstatus_roundtrip_all_fields);
RUN_TEST(test_ctrlstatus_roundtrip_unknown_primary);
RUN_TEST(test_xcecli_ctrlstatus_null_ctx_typed_failure);
RUN_TEST(test_xcecli_ctrlstatus_primary_null_ctx_typed_failure);
RUN_TEST(test_xcecli_ctrlstatus_primary_null_out_typed_failure);
RUN_TEST(test_ctrlstatus_is_authoritative_decision);
RUN_TEST(test_ctrlstatus_primary_addr_from_peers);
RUN_TEST(test_ctrlstatus_primary_addr_unknown_or_missing);
}