upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-17 04:21:39 +0530
committerYour Name <you@example.com>2026-05-17 04:21:39 +0530
commit78dd599277b8e8b2ddc39a4ae710ec91d737272e (patch)
tree9fbd89695cede00b8ff3b12ce428e96a2aa70e9b /tests
parentb0d7394e089f00a9ffa67a2b33a502e47b778a93 (diff)
Phase 4: TollGate client detection + auto-payment
- New tollgate_client.c/h: detect upstream TollGate (kind=10021), auto-pay via nucula wallet, session monitoring with 20% renewal - State machine: IDLE→DETECTING→NEEDS_PAY→PAYING→PAID→RENEWING - Blocking: upstream payment before local services start - Synchronous wallet init (was async task) - Client config: enabled, steps_to_buy, renewal_threshold_pct - Updated PLAN.md with Phases 4-7 (client, payout, bytes, CVM) - Updated CHECKLIST.md with all new phase items - 30 new unit tests (all passing), 116 total
Diffstat (limited to 'tests')
-rw-r--r--tests/unit/Makefile5
-rw-r--r--tests/unit/stubs/esp_err.h1
-rw-r--r--tests/unit/stubs/nucula_wallet.h17
-rw-r--r--tests/unit/test_tollgate_client.c186
4 files changed, 208 insertions, 1 deletions
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index ab41175..e4ea388 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -21,7 +21,7 @@ LDFLAGS := -lmbedcrypto -lcjson
21 21
22SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o 22SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o
23 23
24TESTS := test_geohash test_identity test_nostr_event test_cashu test_session 24TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client
25 25
26.PHONY: all test clean $(TESTS) 26.PHONY: all test clean $(TESTS)
27 27
@@ -62,5 +62,8 @@ test_cashu: test_cashu.c $(REPO_ROOT)/main/cashu.c
62test_session: test_session.c $(REPO_ROOT)/main/session.c 62test_session: test_session.c $(REPO_ROOT)/main/session.c
63 $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/session.c -o $@ $(LDFLAGS) 63 $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/session.c -o $@ $(LDFLAGS)
64 64
65test_tollgate_client: test_tollgate_client.c
66 $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
67
65clean: 68clean:
66 rm -f $(TESTS) $(SECP256K1_OBJ) 69 rm -f $(TESTS) $(SECP256K1_OBJ)
diff --git a/tests/unit/stubs/esp_err.h b/tests/unit/stubs/esp_err.h
index 9bedb72..2a8e216 100644
--- a/tests/unit/stubs/esp_err.h
+++ b/tests/unit/stubs/esp_err.h
@@ -12,6 +12,7 @@ typedef int esp_err_t;
12#define ESP_ERR_INVALID_ARG 0x102 12#define ESP_ERR_INVALID_ARG 0x102
13#define ESP_ERR_NO_MEM 0x101 13#define ESP_ERR_NO_MEM 0x101
14#define ESP_ERR_NOT_FOUND 0x104 14#define ESP_ERR_NOT_FOUND 0x104
15#define ESP_ERR_INVALID_STATE 0x103
15 16
16static inline const char *esp_err_to_name(esp_err_t err) { (void)err; return "ESP_OK"; } 17static inline const char *esp_err_to_name(esp_err_t err) { (void)err; return "ESP_OK"; }
17 18
diff --git a/tests/unit/stubs/nucula_wallet.h b/tests/unit/stubs/nucula_wallet.h
new file mode 100644
index 0000000..260ec35
--- /dev/null
+++ b/tests/unit/stubs/nucula_wallet.h
@@ -0,0 +1,17 @@
1#ifndef STUBS_NUCULA_WALLET_H
2#define STUBS_NUCULA_WALLET_H
3
4#include "esp_err.h"
5#include <stdint.h>
6#include <stddef.h>
7
8esp_err_t nucula_wallet_init(const char *mint_url);
9esp_err_t nucula_wallet_receive(const char *token_str);
10esp_err_t nucula_wallet_send(uint64_t amount_sat, char *token_out, size_t token_out_size);
11uint64_t nucula_wallet_balance(void);
12int nucula_wallet_proof_count(void);
13char *nucula_wallet_proofs_json(void);
14esp_err_t nucula_wallet_swap_all(void);
15void nucula_wallet_print_status(void);
16
17#endif
diff --git a/tests/unit/test_tollgate_client.c b/tests/unit/test_tollgate_client.c
new file mode 100644
index 0000000..686ad19
--- /dev/null
+++ b/tests/unit/test_tollgate_client.c
@@ -0,0 +1,186 @@
1#include "test_framework.h"
2#include "../../main/config.h"
3#include <string.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <cjson/cJSON.h>
7
8static tollgate_config_t g_test_config;
9
10const tollgate_config_t *tollgate_config_get(void) {
11 return &g_test_config;
12}
13
14uint64_t nucula_wallet_balance(void) { return 100; }
15esp_err_t nucula_wallet_send(uint64_t a, char *b, size_t c) { (void)a; (void)b; (void)c; return ESP_OK; }
16
17#include "freertos/FreeRTOS.h"
18
19#include "../../main/tollgate_client.c"
20
21static void reset_state(void) {
22 s_state = TG_CLIENT_IDLE;
23 memset(&s_discovery, 0, sizeof(s_discovery));
24 memset(s_gw_ip, 0, sizeof(s_gw_ip));
25 s_allotment_ms = 0;
26 s_remaining_ms = -1;
27 s_last_pay_time_ms = 0;
28 s_retry_count = 0;
29}
30
31int main(void)
32{
33 printf("=== test_tollgate_client ===\n");
34
35 memset(&g_test_config, 0, sizeof(g_test_config));
36 g_test_config.client_enabled = true;
37 g_test_config.client_steps_to_buy = 1;
38 g_test_config.client_renewal_threshold_pct = 20;
39 g_test_config.client_retry_interval_ms = 30000;
40
41 printf("\n--- parse_discovery_response (valid kind=10021) ---\n");
42 {
43 const char *json = "{\"kind\":10021,\"pubkey\":\"abcdef\",\"tags\":["
44 "[\"metric\",\"milliseconds\"],"
45 "[\"step_size\",\"60000\"],"
46 "[\"price_per_step\",\"cashu\",\"21\",\"sat\",\"https://testnut.cashu.space\",\"1\"],"
47 "[\"tips\",\"1\",\"2\",\"5\"]"
48 "],\"content\":\"\"}";
49
50 tollgate_discovery_t disc;
51 bool ok = parse_discovery_response(json, &disc);
52 ASSERT(ok, "valid discovery parsed");
53 ASSERT(disc.is_tollgate, "is_tollgate=true");
54 ASSERT_EQ_INT(21, disc.price_per_step, "price_per_step=21");
55 ASSERT_EQ_INT(60000, disc.step_size_ms, "step_size_ms=60000");
56 ASSERT_EQ_STR("milliseconds", disc.metric, "metric=milliseconds");
57 ASSERT_EQ_STR("https://testnut.cashu.space", disc.mint_url, "mint_url");
58 }
59
60 printf("\n--- parse_discovery_response (wrong kind) ---\n");
61 {
62 const char *json = "{\"kind\":1,\"tags\":[]}";
63 tollgate_discovery_t disc;
64 bool ok = parse_discovery_response(json, &disc);
65 ASSERT(!ok, "wrong kind rejected");
66 }
67
68 printf("\n--- parse_discovery_response (no tags) ---\n");
69 {
70 const char *json = "{\"kind\":10021,\"content\":\"\"}";
71 tollgate_discovery_t disc = {0};
72 bool ok = parse_discovery_response(json, &disc);
73 ASSERT(ok, "no tags still parses");
74 ASSERT(disc.is_tollgate, "is_tollgate=true even without tags");
75 ASSERT_EQ_INT(0, disc.price_per_step, "price=0 when no tags");
76 }
77
78 printf("\n--- parse_discovery_response (garbage) ---\n");
79 {
80 tollgate_discovery_t disc;
81 bool ok = parse_discovery_response("not json", &disc);
82 ASSERT(!ok, "garbage rejected");
83 }
84
85 printf("\n--- parse_session_response (valid kind=1022) ---\n");
86 {
87 const char *json = "{\"kind\":1022,\"pubkey\":\"abcdef\",\"tags\":["
88 "[\"p\",\"unknown\"],"
89 "[\"device-identifier\",\"mac\",\"10.0.0.2\"],"
90 "[\"allotment\",\"60000\"],"
91 "[\"metric\",\"milliseconds\"]"
92 "],\"content\":\"\"}";
93
94 int64_t allotment = 0;
95 bool ok = parse_session_response(json, &allotment);
96 ASSERT(ok, "valid session parsed");
97 ASSERT_EQ_INT(60000, (int)allotment, "allotment=60000");
98 }
99
100 printf("\n--- parse_session_response (error kind=21023) ---\n");
101 {
102 const char *json = "{\"kind\":21023,\"tags\":[[\"level\",\"error\"],[\"code\",\"payment-error-token-spent\"]],\"content\":\"Token spent\"}";
103 int64_t allotment = 999;
104 bool ok = parse_session_response(json, &allotment);
105 ASSERT(!ok, "error kind rejected");
106 ASSERT_EQ_INT(999, (int)allotment, "allotment unchanged on error");
107 }
108
109 printf("\n--- parse_usage_response ---\n");
110 {
111 int64_t remaining = 0, total = 0;
112 bool ok = parse_usage_response("30000/60000", &remaining, &total);
113 ASSERT(ok, "valid usage parsed");
114 ASSERT_EQ_INT(30000, (int)remaining, "remaining=30000");
115 ASSERT_EQ_INT(60000, (int)total, "total=60000");
116 }
117
118 printf("\n--- parse_usage_response (-1/-1) ---\n");
119 {
120 int64_t remaining = 0, total = 0;
121 bool ok = parse_usage_response("-1/-1", &remaining, &total);
122 ASSERT(ok, "no-session usage parsed");
123 ASSERT_EQ_INT(-1, (int)remaining, "remaining=-1");
124 ASSERT_EQ_INT(-1, (int)total, "total=-1");
125 }
126
127 printf("\n--- parse_usage_response (garbage) ---\n");
128 {
129 int64_t remaining = 0, total = 0;
130 bool ok = parse_usage_response("garbage", &remaining, &total);
131 ASSERT(!ok, "no slash rejected");
132 }
133
134 printf("\n--- renewal threshold calculation ---\n");
135 {
136 reset_state();
137 s_state = TG_CLIENT_PAID;
138 s_allotment_ms = 60000;
139 s_remaining_ms = 10000;
140 strncpy(s_gw_ip, "10.0.0.1", sizeof(s_gw_ip) - 1);
141
142 int remaining_pct = (int)((s_remaining_ms * 100) / s_allotment_ms);
143 ASSERT(remaining_pct <= 20, "10/60 = 16% <= 20% triggers renewal");
144 }
145
146 printf("\n--- renewal threshold no-renew (above 20%%) ---\n");
147 {
148 reset_state();
149 s_allotment_ms = 60000;
150 s_remaining_ms = 50000;
151 int remaining_pct = (int)((s_remaining_ms * 100) / s_allotment_ms);
152 ASSERT(remaining_pct > 20, "50/60 = 83% > 20% no renewal");
153 }
154
155 printf("\n--- state machine: init ---\n");
156 {
157 reset_state();
158 tollgate_client_init();
159 ASSERT_EQ_INT(TG_CLIENT_IDLE, (int)tollgate_client_get_state(), "init sets IDLE");
160 }
161
162 printf("\n--- config: client_enabled=false ---\n");
163 {
164 reset_state();
165 g_test_config.client_enabled = false;
166 esp_err_t ret = tollgate_client_on_sta_connected("10.0.0.1");
167 ASSERT_EQ_INT(ESP_OK, (int)ret, "returns OK when disabled");
168 ASSERT_EQ_INT(TG_CLIENT_IDLE, (int)tollgate_client_get_state(), "stays IDLE when disabled");
169 g_test_config.client_enabled = true;
170 }
171
172 printf("\n--- state machine: disconnect resets ---\n");
173 {
174 reset_state();
175 s_state = TG_CLIENT_PAID;
176 strncpy(s_gw_ip, "10.0.0.1", sizeof(s_gw_ip) - 1);
177 s_allotment_ms = 60000;
178 s_remaining_ms = 30000;
179 tollgate_client_on_sta_disconnected();
180 ASSERT_EQ_INT(TG_CLIENT_IDLE, (int)tollgate_client_get_state(), "disconnect resets to IDLE");
181 ASSERT_EQ_INT(-1, (int)tollgate_client_get_remaining_ms(), "remaining reset to -1");
182 ASSERT_EQ_INT(0, (int)tollgate_client_get_allotment_ms(), "allotment reset to 0");
183 }
184
185 TEST_SUMMARY();
186}