feat(controller-status): xcecli_ctrlstatus_primary authoritative reach-the-primary read (task 3.2)
This commit is contained in:
parent
691cd9199e
commit
03813bf560
4 changed files with 291 additions and 2 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue