diff options
Diffstat (limited to 'tests/unit')
| -rw-r--r-- | tests/unit/Makefile | 5 | ||||
| -rw-r--r-- | tests/unit/stubs/esp_err.h | 1 | ||||
| -rw-r--r-- | tests/unit/stubs/nucula_wallet.h | 17 | ||||
| -rw-r--r-- | tests/unit/test_tollgate_client.c | 186 |
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 | ||
| 22 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o | 22 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o |
| 23 | 23 | ||
| 24 | TESTS := test_geohash test_identity test_nostr_event test_cashu test_session | 24 | TESTS := 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 | |||
| 62 | test_session: test_session.c $(REPO_ROOT)/main/session.c | 62 | test_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 | ||
| 65 | test_tollgate_client: test_tollgate_client.c | ||
| 66 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) | ||
| 67 | |||
| 65 | clean: | 68 | clean: |
| 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 | ||
| 16 | static inline const char *esp_err_to_name(esp_err_t err) { (void)err; return "ESP_OK"; } | 17 | static 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 | |||
| 8 | esp_err_t nucula_wallet_init(const char *mint_url); | ||
| 9 | esp_err_t nucula_wallet_receive(const char *token_str); | ||
| 10 | esp_err_t nucula_wallet_send(uint64_t amount_sat, char *token_out, size_t token_out_size); | ||
| 11 | uint64_t nucula_wallet_balance(void); | ||
| 12 | int nucula_wallet_proof_count(void); | ||
| 13 | char *nucula_wallet_proofs_json(void); | ||
| 14 | esp_err_t nucula_wallet_swap_all(void); | ||
| 15 | void 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 | |||
| 8 | static tollgate_config_t g_test_config; | ||
| 9 | |||
| 10 | const tollgate_config_t *tollgate_config_get(void) { | ||
| 11 | return &g_test_config; | ||
| 12 | } | ||
| 13 | |||
| 14 | uint64_t nucula_wallet_balance(void) { return 100; } | ||
| 15 | esp_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 | |||
| 21 | static 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 | |||
| 31 | int 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 | } | ||