From be4d490e96de3d20cc6c9921f09534cde0ca0765 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 23 May 2026 15:26:52 +0530 Subject: Phase 8c-8f: Extract portal, stratum_client, mint_health, client into tollgate_core New component modules: - tollgate_core_portal.c/h: template rendering, usage calc, captive URI detection - tollgate_core_stratum_client.c/h: Stratum V1 JSON protocol parsing, message building - tollgate_core_mint_health.c/h: mint health state tracking, recovery threshold logic - tollgate_core_client.c/h: discovery/session/usage JSON parsing, renewal check, price calc Main shims updated to delegate pure logic to component: - captive_portal.c: uses tollgate_core_portal_render(), format_usage(), is_captive_uri() - stratum_client.c: uses tollgate_core_stratum_parse_notify/build_*() - mint_health.c: uses tollgate_core_mint_health_update/is_reachable/mark_unreachable() - tollgate_client.c: uses tollgate_core_client_parse_*/should_renew/calc_price_per_min() 4 new unit tests (test_portal, test_stratum_client, test_mint_health_core, test_client_core) All 25 unit tests pass. ESP-IDF build passes (68% partition free). --- tests/unit/Makefile | 26 +++-- tests/unit/test_client_core.c | 189 +++++++++++++++++++++++++++++++++++++ tests/unit/test_mint_health_core.c | 130 +++++++++++++++++++++++++ tests/unit/test_portal.c | 153 ++++++++++++++++++++++++++++++ tests/unit/test_stratum_client.c | 125 ++++++++++++++++++++++++ 5 files changed, 616 insertions(+), 7 deletions(-) create mode 100644 tests/unit/test_client_core.c create mode 100644 tests/unit/test_mint_health_core.c create mode 100644 tests/unit/test_portal.c create mode 100644 tests/unit/test_stratum_client.c (limited to 'tests/unit') diff --git a/tests/unit/Makefile b/tests/unit/Makefile index 9347516..591ec0f 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile @@ -26,7 +26,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o -TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox test_relay_selector test_relay_validator +TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox test_relay_selector test_relay_validator test_portal test_stratum_client test_mint_health_core test_client_core .PHONY: all test clean $(TESTS) @@ -69,8 +69,8 @@ TG_CORE_OBJS_CFLAGS := -include stubs/mint_health.h -include stubs/esp_netif.h - test_session: test_session.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) -test_tollgate_client: test_tollgate_client.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c - $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c -o $@ $(LDFLAGS) +test_tollgate_client: test_tollgate_client.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c $(TG_CORE_SRC)/tollgate_core_client.c + $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(TG_CORE_SRC)/tollgate_core_market.c $(TG_CORE_SRC)/tollgate_core_client.c -o $@ $(LDFLAGS) test_lnurl_pay: test_lnurl_pay.c $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) @@ -87,8 +87,8 @@ test_nip04: test_nip04.c $(REPO_ROOT)/main/nip04.c $(SECP256K1_OBJ) test_cvm_server: test_cvm_server.c $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) -test_mint_health: test_mint_health.c $(REPO_ROOT)/main/mint_health.c - $(CC) -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare -std=gnu17 -g -O0 -DTEST_HOST -include stubs/esp_err.h -I $(REPO_ROOT)/main -I stubs -I $(SECP256K1_INC) -I $(SECP256K1_CFG) -I /usr/include/cjson $< $(REPO_ROOT)/main/mint_health.c -o $@ $(LDFLAGS) +test_mint_health: test_mint_health.c $(REPO_ROOT)/main/mint_health.c $(TG_CORE_SRC)/tollgate_core_mint_health.c + $(CC) -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare -std=gnu17 -g -O0 -DTEST_HOST -include stubs/esp_err.h -I $(REPO_ROOT)/main -I stubs -I $(SECP256K1_INC) -I $(SECP256K1_CFG) -I /usr/include/cjson -I $(TG_CORE_SRC) -I $(TG_CORE_INC) $^ -o $@ $(LDFLAGS) test_beacon_price: test_beacon_price.c $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/beacon_price.c $(TG_CORE_SRC)/tollgate_core_beacon.c -o $@ $(LDFLAGS) @@ -105,8 +105,8 @@ test_stratum_proxy: test_stratum_proxy.c $(REPO_ROOT)/main/stratum_proxy.c $(TG_ test_session_payment_method: test_session_payment_method.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) -test_tollgate_client_mining: test_tollgate_client_mining.c - $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) +test_tollgate_client_mining: test_tollgate_client_mining.c $(TG_CORE_SRC)/tollgate_core_client.c + $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(TG_CORE_SRC)/tollgate_core_client.c -o $@ $(LDFLAGS) test_firewall_sandbox: test_firewall_sandbox.c $(TG_CORE_SRC)/tollgate_core_firewall.c $(TG_CORE_SRC)/tollgate_core.c $(TG_CORE_SRC)/tollgate_core_session.c $(TG_CORE_SRC)/tollgate_core_cashu.c $(TG_CORE_SRC)/tollgate_core_mining.c $(TG_CORE_SRC)/tollgate_core_dns.c $(TG_CORE_SRC)/tollgate_core_stratum_proxy.c $(CC) $(CFLAGS) -include stubs/dns_server.h $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) @@ -117,5 +117,17 @@ test_relay_selector: test_relay_selector.c test_relay_validator: test_relay_validator.c $(REPO_ROOT)/main/nostr_event.c $(REPO_ROOT)/main/identity.c $(REPO_ROOT)/components/wisp_relay/relay_validator.c $(SECP256K1_OBJ) $(CC) $(CFLAGS) -I $(SECP256K1_PRIV_INC) -I $(REPO_ROOT)/main -I $(REPO_ROOT)/components/wisp_relay $< $(REPO_ROOT)/main/nostr_event.c $(REPO_ROOT)/main/identity.c $(REPO_ROOT)/components/wisp_relay/relay_validator.c $(SECP256K1_OBJ) -o $@ $(LDFLAGS) +test_portal: test_portal.c $(TG_CORE_SRC)/tollgate_core_portal.c + $(CC) $(CFLAGS) $(TG_CORE_OBJS_CFLAGS) $^ -o $@ $(LDFLAGS) + +test_stratum_client: test_stratum_client.c $(TG_CORE_SRC)/tollgate_core_stratum_client.c + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +test_mint_health_core: test_mint_health_core.c $(TG_CORE_SRC)/tollgate_core_mint_health.c + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +test_client_core: test_client_core.c $(TG_CORE_SRC)/tollgate_core_client.c + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + clean: rm -f $(TESTS) $(SECP256K1_OBJ) diff --git a/tests/unit/test_client_core.c b/tests/unit/test_client_core.c new file mode 100644 index 0000000..7cc6cc7 --- /dev/null +++ b/tests/unit/test_client_core.c @@ -0,0 +1,189 @@ +#include "test_framework.h" +#include "../../components/tollgate_core/src/tollgate_core_client.h" +#include +#include +#include + +int main(void) +{ + printf("=== test_client_core ===\n"); + + printf("\n--- parse_discovery valid ---\n"); + { + const char *json = "{\"kind\":10021,\"content\":\"discovery\"," + "\"tags\":[" + "[\"metric\",\"milliseconds\"]," + "[\"step_size\",\"60000\"]," + "[\"price_per_step\",\"21\",\"21\",\"sha256\",\"https://test.mint\"]" + "]}"; + tollgate_discovery_t disc = {0}; + bool ok = tollgate_core_client_parse_discovery(json, &disc); + ASSERT(ok, "parse_discovery returns true"); + ASSERT(disc.is_tollgate, "is_tollgate set"); + ASSERT_EQ_INT(21, disc.price_per_step, "price_per_step"); + ASSERT_EQ_INT(60000, disc.step_size_ms, "step_size_ms"); + ASSERT(strcmp(disc.metric, "milliseconds") == 0, "metric"); + ASSERT(strcmp(disc.mint_url, "https://test.mint") == 0, "mint_url"); + } + + printf("\n--- parse_discovery wrong kind ---\n"); + { + const char *json = "{\"kind\":9999}"; + tollgate_discovery_t disc = {0}; + bool ok = tollgate_core_client_parse_discovery(json, &disc); + ASSERT(!ok, "wrong kind returns false"); + } + + printf("\n--- parse_discovery mining payment ---\n"); + { + const char *json = "{\"kind\":10021,\"content\":\"\"," + "\"tags\":[" + "[\"metric\",\"milliseconds\"]," + "[\"step_size\",\"60000\"]," + "[\"price_per_step\",\"0\",\"mining\",\"3333\",\"extra\"]" + "]}"; + tollgate_discovery_t disc = {0}; + bool ok = tollgate_core_client_parse_discovery(json, &disc); + ASSERT(ok, "parse_discovery mining returns true"); + ASSERT(disc.mining_available, "mining_available set"); + ASSERT_EQ_INT(3333, (int)disc.mining_port, "mining_port"); + } + + printf("\n--- parse_discovery empty tags ---\n"); + { + const char *json = "{\"kind\":10021,\"tags\":[]}"; + tollgate_discovery_t disc = {0}; + bool ok = tollgate_core_client_parse_discovery(json, &disc); + ASSERT(ok, "empty tags returns true"); + ASSERT(disc.is_tollgate, "is_tollgate set"); + } + + printf("\n--- parse_discovery no tags ---\n"); + { + const char *json = "{\"kind\":10021}"; + tollgate_discovery_t disc = {0}; + bool ok = tollgate_core_client_parse_discovery(json, &disc); + ASSERT(ok, "no tags returns true"); + } + + printf("\n--- parse_discovery invalid JSON ---\n"); + { + tollgate_discovery_t disc = {0}; + bool ok = tollgate_core_client_parse_discovery("not json", &disc); + ASSERT(!ok, "invalid JSON returns false"); + } + + printf("\n--- parse_session valid ---\n"); + { + const char *json = "{\"kind\":1022,\"content\":\"session\"," + "\"tags\":[[\"allotment\",\"120000\"]]}"; + int64_t allotment = 0; + bool ok = tollgate_core_client_parse_session(json, &allotment); + ASSERT(ok, "parse_session returns true"); + ASSERT_EQ_UINT64(120000, (unsigned long long)allotment, "allotment_ms"); + } + + printf("\n--- parse_session wrong kind ---\n"); + { + const char *json = "{\"kind\":10021}"; + int64_t allotment = 0; + bool ok = tollgate_core_client_parse_session(json, &allotment); + ASSERT(!ok, "wrong kind returns false"); + } + + printf("\n--- parse_session no allotment tag ---\n"); + { + const char *json = "{\"kind\":1022,\"tags\":[]}"; + int64_t allotment = 999; + bool ok = tollgate_core_client_parse_session(json, &allotment); + ASSERT(ok, "returns true"); + ASSERT_EQ_UINT64(999, (unsigned long long)allotment, "allotment unchanged when no tag"); + } + + printf("\n--- parse_usage valid ---\n"); + { + int64_t remaining = 0, total = 0; + bool ok = tollgate_core_client_parse_usage("45000/60000", &remaining, &total); + ASSERT(ok, "parse_usage returns true"); + ASSERT_EQ_UINT64(45000, (unsigned long long)remaining, "remaining"); + ASSERT_EQ_UINT64(60000, (unsigned long long)total, "total"); + } + + printf("\n--- parse_usage no slash ---\n"); + { + int64_t remaining = 0, total = 0; + bool ok = tollgate_core_client_parse_usage("45000", &remaining, &total); + ASSERT(!ok, "no slash returns false"); + } + + printf("\n--- parse_usage zero values ---\n"); + { + int64_t remaining = 0, total = 0; + bool ok = tollgate_core_client_parse_usage("0/0", &remaining, &total); + ASSERT(ok, "parse_usage zero values"); + ASSERT_EQ_UINT64(0, (unsigned long long)remaining, "remaining is 0"); + ASSERT_EQ_UINT64(0, (unsigned long long)total, "total is 0"); + } + + printf("\n--- should_renew ---\n"); + { + ASSERT(tollgate_core_client_should_renew(1000, 60000, 20), + "1.6% remaining → should renew"); + ASSERT(tollgate_core_client_should_renew(0, 60000, 20), + "0% remaining → should renew"); + ASSERT(!tollgate_core_client_should_renew(30000, 60000, 20), + "50% remaining → should not renew"); + ASSERT(!tollgate_core_client_should_renew(60000, 60000, 20), + "100% remaining → should not renew"); + ASSERT(tollgate_core_client_should_renew(11999, 60000, 20), + "19.99% → should renew"); + ASSERT(tollgate_core_client_should_renew(12000, 60000, 20), + "20% exactly at threshold → should renew"); + } + + printf("\n--- should_renew default threshold ---\n"); + { + ASSERT(tollgate_core_client_should_renew(1000, 60000, 0), + "threshold=0 defaults to 20%"); + ASSERT(tollgate_core_client_should_renew(1000, 60000, -1), + "negative threshold defaults to 20%"); + } + + printf("\n--- should_renew edge cases ---\n"); + { + ASSERT(!tollgate_core_client_should_renew(-1, 60000, 20), + "negative remaining → false"); + ASSERT(!tollgate_core_client_should_renew(1000, 0, 20), + "zero allotment → false"); + } + + printf("\n--- calc_price_per_min ---\n"); + { + ASSERT_EQ_INT(21, tollgate_core_client_calc_price_per_min(21, 60000), + "21 sats/60s = 21 sats/min"); + ASSERT_EQ_INT(42, tollgate_core_client_calc_price_per_min(21, 30000), + "21 sats/30s = 42 sats/min"); + ASSERT_EQ_INT(21000, tollgate_core_client_calc_price_per_min(21, 60), + "21 sats/60ms effective"); + } + + printf("\n--- calc_price_per_min zero step ---\n"); + { + int result = tollgate_core_client_calc_price_per_min(21, 0); + ASSERT(result > 0, "zero step_size_ms doesn't crash"); + } + + printf("\n--- calc_steps ---\n"); + { + ASSERT_EQ_INT(21, tollgate_core_client_calc_steps(1, 21, 21), + "1 step * 21 = 21"); + ASSERT_EQ_INT(42, tollgate_core_client_calc_steps(2, 21, 21), + "2 steps * 21 = 42"); + ASSERT_EQ_INT(21, tollgate_core_client_calc_steps(0, 21, 21), + "0 steps defaults to 1"); + ASSERT_EQ_INT(21, tollgate_core_client_calc_steps(-1, 21, 21), + "negative steps defaults to 1"); + } + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_mint_health_core.c b/tests/unit/test_mint_health_core.c new file mode 100644 index 0000000..9727edb --- /dev/null +++ b/tests/unit/test_mint_health_core.c @@ -0,0 +1,130 @@ +#include "test_framework.h" +#include "../../components/tollgate_core/src/tollgate_core_mint_health.h" +#include +#include + +int main(void) +{ + printf("=== test_mint_health_core ===\n"); + + printf("\n--- update: probe success ---\n"); + { + tollgate_mint_health_t state = {0}; + strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); + state.count = 1; + + tollgate_core_mint_health_update(&state, 0, true, 200, 0, 1000); + ASSERT_EQ_INT(1, state.mints[0].consecutive_successes, "1st success"); + ASSERT(!state.mints[0].reachable, "not yet reachable (threshold=3)"); + + tollgate_core_mint_health_update(&state, 0, true, 200, 0, 2000); + ASSERT_EQ_INT(2, state.mints[0].consecutive_successes, "2nd success"); + ASSERT(!state.mints[0].reachable, "still not reachable"); + + tollgate_core_mint_health_update(&state, 0, true, 200, 0, 3000); + ASSERT_EQ_INT(3, state.mints[0].consecutive_successes, "3rd success"); + ASSERT(state.mints[0].reachable, "now reachable after threshold"); + } + + printf("\n--- update: failure resets ---\n"); + { + tollgate_mint_health_t state = {0}; + strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); + state.count = 1; + state.mints[0].reachable = true; + state.mints[0].consecutive_successes = 5; + + tollgate_core_mint_health_update(&state, 0, false, 0, -1, 1000); + ASSERT(!state.mints[0].reachable, "failure marks unreachable"); + ASSERT_EQ_INT(0, state.mints[0].consecutive_successes, "successes reset to 0"); + ASSERT_EQ_INT(-1, state.mints[0].last_err, "error code recorded"); + } + + printf("\n--- update_initial: success ---\n"); + { + tollgate_mint_health_t state = {0}; + strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); + state.count = 1; + + tollgate_core_mint_health_update_initial(&state, 0, true, 200, 0, 1000); + ASSERT(state.mints[0].reachable, "immediately reachable on initial success"); + ASSERT_EQ_INT(TG_MINT_HEALTH_RECOVERY_THRESHOLD, + state.mints[0].consecutive_successes, "successes set to threshold"); + } + + printf("\n--- update_initial: failure ---\n"); + { + tollgate_mint_health_t state = {0}; + strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); + state.count = 1; + + tollgate_core_mint_health_update_initial(&state, 0, false, 0, -2, 1000); + ASSERT(!state.mints[0].reachable, "not reachable on initial failure"); + ASSERT_EQ_INT(0, state.mints[0].consecutive_successes, "successes is 0"); + } + + printf("\n--- is_reachable ---\n"); + { + tollgate_mint_health_t state = {0}; + strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); + state.count = 1; + state.mints[0].reachable = true; + + ASSERT(tollgate_core_mint_health_is_reachable(&state, "https://test.mint"), + "exact URL match"); + ASSERT(tollgate_core_mint_health_is_reachable(&state, "https://test.mint/v1/info"), + "substring match"); + ASSERT(!tollgate_core_mint_health_is_reachable(&state, "https://other.mint"), + "no match for other URL"); + ASSERT(!tollgate_core_mint_health_is_reachable(NULL, "https://test.mint"), + "NULL state"); + ASSERT(!tollgate_core_mint_health_is_reachable(&state, NULL), + "NULL url"); + } + + printf("\n--- mark_unreachable ---\n"); + { + tollgate_mint_health_t state = {0}; + strncpy(state.mints[0].url, "https://test.mint", sizeof(state.mints[0].url) - 1); + state.count = 1; + state.mints[0].reachable = true; + state.mints[0].consecutive_successes = 5; + + tollgate_core_mint_health_mark_unreachable(&state, "https://test.mint"); + ASSERT(!state.mints[0].reachable, "marked unreachable"); + ASSERT_EQ_INT(0, state.mints[0].consecutive_successes, "successes cleared"); + + tollgate_core_mint_health_mark_unreachable(&state, "https://test.mint"); + ASSERT(!state.mints[0].reachable, "idempotent"); + } + + printf("\n--- count_reachable ---\n"); + { + tollgate_mint_health_t state = {0}; + state.count = 3; + strncpy(state.mints[0].url, "https://a.mint", sizeof(state.mints[0].url) - 1); + strncpy(state.mints[1].url, "https://b.mint", sizeof(state.mints[1].url) - 1); + strncpy(state.mints[2].url, "https://c.mint", sizeof(state.mints[2].url) - 1); + state.mints[0].reachable = true; + state.mints[1].reachable = false; + state.mints[2].reachable = true; + + ASSERT_EQ_INT(2, tollgate_core_mint_health_count_reachable(&state), + "2 out of 3 reachable"); + ASSERT_EQ_INT(0, tollgate_core_mint_health_count_reachable(NULL), + "NULL returns 0"); + } + + printf("\n--- update with invalid index ---\n"); + { + tollgate_mint_health_t state = {0}; + state.count = 1; + + tollgate_core_mint_health_update(&state, -1, true, 200, 0, 1000); + tollgate_core_mint_health_update(&state, 5, true, 200, 0, 1000); + tollgate_core_mint_health_update(NULL, 0, true, 200, 0, 1000); + ASSERT(true, "no crash on invalid inputs"); + } + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_portal.c b/tests/unit/test_portal.c new file mode 100644 index 0000000..575921e --- /dev/null +++ b/tests/unit/test_portal.c @@ -0,0 +1,153 @@ +#include "test_framework.h" +#include "../../components/tollgate_core/src/tollgate_core_portal.h" +#include "../../components/tollgate_core/src/tollgate_core_session.h" +#include +#include +#include + +int main(void) +{ + printf("=== test_portal ===\n"); + + printf("\n--- template_replace single key ---\n"); + { + char *result = tollgate_core_portal_template_replace( + "Hello __NAME__!", "__NAME__", "World"); + ASSERT(result != NULL, "result not NULL"); + ASSERT(strcmp(result, "Hello World!") == 0, "single substitution works"); + free(result); + } + + printf("\n--- template_replace no match ---\n"); + { + char *result = tollgate_core_portal_template_replace( + "Hello World!", "__NAME__", "Test"); + ASSERT(result != NULL, "result not NULL"); + ASSERT(strcmp(result, "Hello World!") == 0, "no match returns original"); + free(result); + } + + printf("\n--- template_replace multiple occurrences ---\n"); + { + char *result = tollgate_core_portal_template_replace( + "__IP__:2121 and __IP__:80", "__IP__", "10.0.0.1"); + ASSERT(result != NULL, "result not NULL"); + ASSERT(strcmp(result, "10.0.0.1:2121 and 10.0.0.1:80") == 0, + "multiple occurrences replaced"); + free(result); + } + + printf("\n--- template_replace key longer than value ---\n"); + { + char *result = tollgate_core_portal_template_replace( + "A __LONGKEY__ B", "__LONGKEY__", "X"); + ASSERT(result != NULL, "result not NULL"); + ASSERT(strcmp(result, "A X B") == 0, "shrinks correctly"); + free(result); + } + + printf("\n--- render multi-key substitution ---\n"); + { + tollgate_portal_sub_t subs[] = { + { "__AP_IP__", "10.0.0.1" }, + { "__PRICE__", "21" }, + { "__MINT_URL__", "https://test.mint" }, + }; + char *result = tollgate_core_portal_render( + "IP=__AP_IP__ price=__PRICE__ mint=__MINT_URL__", subs, 3); + ASSERT(result != NULL, "result not NULL"); + ASSERT(strcmp(result, "IP=10.0.0.1 price=21 mint=https://test.mint") == 0, + "multi-key render works"); + free(result); + } + + printf("\n--- render with no matches ---\n"); + { + tollgate_portal_sub_t subs[] = { + { "__AP_IP__", "10.0.0.1" }, + }; + char *result = tollgate_core_portal_render("no placeholders", subs, 1); + ASSERT(result != NULL, "result not NULL"); + ASSERT(strcmp(result, "no placeholders") == 0, "no matches returns original"); + free(result); + } + + printf("\n--- calc_usage time metric ---\n"); + { + tg_session_t session = {0}; + session.active = true; + session.allotment_ms = 60000; + session.start_time_ms = 10000; + + int64_t remaining = 0, total = 0; + bool ok = tollgate_core_portal_calc_usage(&session, "milliseconds", 30000, &remaining, &total); + ASSERT(ok, "calc_usage returns true for active session"); + ASSERT_EQ_UINT64(40000, (unsigned long long)remaining, "remaining = 60000 - 20000"); + ASSERT_EQ_UINT64(60000, (unsigned long long)total, "total = allotment_ms"); + } + + printf("\n--- calc_usage bytes metric ---\n"); + { + tg_session_t session = {0}; + session.active = true; + session.allotment_bytes = 1000000; + session.bytes_consumed = 300000; + + int64_t remaining = 0, total = 0; + bool ok = tollgate_core_portal_calc_usage(&session, "bytes", 0, &remaining, &total); + ASSERT(ok, "calc_usage returns true for bytes"); + ASSERT_EQ_UINT64(700000, (unsigned long long)remaining, "remaining = 1000000 - 300000"); + ASSERT_EQ_UINT64(1000000, (unsigned long long)total, "total = allotment_bytes"); + } + + printf("\n--- calc_usage expired time ---\n"); + { + tg_session_t session = {0}; + session.active = true; + session.allotment_ms = 60000; + session.start_time_ms = 0; + + int64_t remaining = 0, total = 0; + tollgate_core_portal_calc_usage(&session, "milliseconds", 120000, &remaining, &total); + ASSERT_EQ_UINT64(0, (unsigned long long)remaining, "remaining clamped to 0 when expired"); + } + + printf("\n--- calc_usage inactive session ---\n"); + { + tg_session_t session = {0}; + session.active = false; + + int64_t remaining = 0, total = 0; + bool ok = tollgate_core_portal_calc_usage(&session, "milliseconds", 0, &remaining, &total); + ASSERT(!ok, "inactive session returns false"); + } + + printf("\n--- format_usage ---\n"); + { + tg_session_t session = {0}; + session.active = true; + session.allotment_ms = 60000; + session.start_time_ms = 10000; + + char buf[64]; + int len = tollgate_core_portal_format_usage(&session, "milliseconds", 30000, buf, sizeof(buf)); + ASSERT(len > 0, "format_usage returns positive length"); + ASSERT(strstr(buf, "/") != NULL, "contains separator"); + ASSERT(strstr(buf, "60000") != NULL, "contains total"); + } + + printf("\n--- is_captive_uri ---\n"); + { + ASSERT(tollgate_core_portal_is_captive_uri("/generate_204"), "generate_204 is captive"); + ASSERT(tollgate_core_portal_is_captive_uri("/hotspot-detect.html"), "hotspot-detect is captive"); + ASSERT(tollgate_core_portal_is_captive_uri("/success.txt"), "success.txt is captive"); + ASSERT(tollgate_core_portal_is_captive_uri("/ncsi.txt"), "ncsi.txt is captive"); + ASSERT(tollgate_core_portal_is_captive_uri("/connecttest.txt"), "connecttest is captive"); + ASSERT(tollgate_core_portal_is_captive_uri("/wpad.dat"), "wpad.dat is captive"); + ASSERT(!tollgate_core_portal_is_captive_uri("/"), "root is not captive"); + ASSERT(!tollgate_core_portal_is_captive_uri("/setup"), "setup is not captive"); + ASSERT(!tollgate_core_portal_is_captive_uri(NULL), "NULL is not captive"); + } + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_stratum_client.c b/tests/unit/test_stratum_client.c new file mode 100644 index 0000000..302fcba --- /dev/null +++ b/tests/unit/test_stratum_client.c @@ -0,0 +1,125 @@ +#include "test_framework.h" +#include "../../components/tollgate_core/src/tollgate_core_stratum_client.h" +#include +#include +#include +#include + +int main(void) +{ + printf("=== test_stratum_client ===\n"); + + printf("\n--- hex_to_bytes ---\n"); + { + const char *hex = "0a1b2c3d"; + uint8_t out[4] = {0}; + tollgate_core_stratum_hex_to_bytes(hex, out, 4); + ASSERT_EQ_INT(0x0a, (int)out[0], "byte 0"); + ASSERT_EQ_INT(0x1b, (int)out[1], "byte 1"); + ASSERT_EQ_INT(0x2c, (int)out[2], "byte 2"); + ASSERT_EQ_INT(0x3d, (int)out[3], "byte 3"); + } + + printf("\n--- hex_to_bytes zeros ---\n"); + { + const char *hex = "00000000"; + uint8_t out[4] = {0xff, 0xff, 0xff, 0xff}; + tollgate_core_stratum_hex_to_bytes(hex, out, 4); + ASSERT_EQ_INT(0, (int)out[0], "zero byte 0"); + ASSERT_EQ_INT(0, (int)out[1], "zero byte 1"); + } + + printf("\n--- parse_notify valid ---\n"); + { + cJSON *params = cJSON_Parse("[\"123\",\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"\"" + ",\"\"" + ",\"\"" + ",\"20000000\"" + ",\"1705e3c4\"" + ",\"65a3b2c1\"]"); + tollgate_stratum_job_t job = {0}; + uint32_t nbits = 0; + bool ok = tollgate_core_stratum_parse_notify(params, &job, &nbits); + ASSERT(ok, "parse_notify returns true"); + ASSERT_EQ_INT(123, (int)job.job_id, "job_id parsed"); + ASSERT(job.valid, "job is valid"); + ASSERT_EQ_INT(0x20000000, (int)job.version, "version parsed"); + ASSERT_EQ_INT(0x1705e3c4, (int)job.nbits, "nbits parsed"); + ASSERT_EQ_INT(0x65a3b2c1, (int)job.ntime, "ntime parsed"); + ASSERT_EQ_INT(0x1705e3c4, (int)nbits, "out_nbits set"); + ASSERT_EQ_INT(32, job.target_len, "target_len is 32"); + cJSON_Delete(params); + } + + printf("\n--- parse_notify too few params ---\n"); + { + cJSON *params = cJSON_Parse("[\"123\",\"abcd\"]"); + tollgate_stratum_job_t job = {0}; + bool ok = tollgate_core_stratum_parse_notify(params, &job, NULL); + ASSERT(!ok, "parse_notify returns false for insufficient params"); + cJSON_Delete(params); + } + + printf("\n--- parse_notify NULL ---\n"); + { + tollgate_stratum_job_t job = {0}; + bool ok = tollgate_core_stratum_parse_notify(NULL, &job, NULL); + ASSERT(!ok, "parse_notify returns false for NULL"); + } + + printf("\n--- parse_difficulty valid ---\n"); + { + cJSON *params = cJSON_Parse("[512]"); + uint64_t diff = 0; + bool ok = tollgate_core_stratum_parse_difficulty(params, &diff); + ASSERT(ok, "parse_difficulty returns true"); + ASSERT_EQ_UINT64(512, (unsigned long long)diff, "difficulty parsed"); + cJSON_Delete(params); + } + + printf("\n--- parse_difficulty invalid ---\n"); + { + cJSON *params = cJSON_Parse("[]"); + uint64_t diff = 0; + bool ok = tollgate_core_stratum_parse_difficulty(params, &diff); + ASSERT(!ok, "parse_difficulty returns false for empty array"); + cJSON_Delete(params); + } + + printf("\n--- build_subscribe ---\n"); + { + char buf[256]; + int len = tollgate_core_stratum_build_subscribe(buf, sizeof(buf), 1); + ASSERT(len > 0, "build_subscribe returns positive length"); + ASSERT(strstr(buf, "mining.subscribe") != NULL, "contains method"); + ASSERT(strstr(buf, "\"id\":1") != NULL, "contains id"); + ASSERT(strstr(buf, "TollGate/1.0") != NULL, "contains user agent"); + ASSERT(buf[len - 1] == '\n', "ends with newline"); + } + + printf("\n--- build_authorize ---\n"); + { + char buf[512]; + int len = tollgate_core_stratum_build_authorize(buf, sizeof(buf), 2, "user1", "pass1"); + ASSERT(len > 0, "build_authorize returns positive length"); + ASSERT(strstr(buf, "mining.authorize") != NULL, "contains method"); + ASSERT(strstr(buf, "user1") != NULL, "contains user"); + ASSERT(strstr(buf, "pass1") != NULL, "contains pass"); + } + + printf("\n--- build_submit ---\n"); + { + char buf[512]; + int len = tollgate_core_stratum_build_submit(buf, sizeof(buf), 3, "worker1", + 100, 0x65a3b2c1, 0x12345678, 0x20000000); + ASSERT(len > 0, "build_submit returns positive length"); + ASSERT(strstr(buf, "mining.submit") != NULL, "contains method"); + ASSERT(strstr(buf, "worker1") != NULL, "contains worker"); + ASSERT(strstr(buf, "100") != NULL, "contains job_id"); + ASSERT(strstr(buf, "65a3b2c1") != NULL, "contains ntime"); + ASSERT(strstr(buf, "12345678") != NULL, "contains nonce"); + } + + TEST_SUMMARY(); +} -- cgit v1.2.3