diff options
| author | Your Name <you@example.com> | 2026-05-19 01:31:47 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-19 01:31:47 +0530 |
| commit | 3b25d826df2b69496fcc560a8ca26089484230c7 (patch) | |
| tree | f683ffa3e4213a5fc6bc1f0d1777ae92fed0e22e | |
| parent | 42902a36bc52e009a1e8d3c371741e30a9cb4c33 (diff) | |
test: add unit tests for relay_validator and relay_selector
- test_relay_validator: Schnorr verify + SHA-256 event ID, tamper detection
(ID, sig, content), invalid JSON, missing fields, result_string
- test_relay_selector: relay scoring (NIP-77 bonus, latency tiebreak,
failure penalty, dead relay handling)
- Updated Makefile with new test targets
- Added configTICK_RATE_HZ to FreeRTOS stubs
| -rw-r--r-- | tests/unit/Makefile | 8 | ||||
| -rw-r--r-- | tests/unit/stubs/freertos/FreeRTOS.h | 1 | ||||
| -rw-r--r-- | tests/unit/test_relay_selector.c | 78 | ||||
| -rw-r--r-- | tests/unit/test_relay_validator.c | 76 |
4 files changed, 162 insertions, 1 deletions
diff --git a/tests/unit/Makefile b/tests/unit/Makefile index 7ebc3b2..b103eef 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile | |||
| @@ -22,7 +22,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm | |||
| 22 | 22 | ||
| 23 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o | 23 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o |
| 24 | 24 | ||
| 25 | 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 | 25 | 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_relay_validator test_relay_selector |
| 26 | 26 | ||
| 27 | .PHONY: all test clean $(TESTS) | 27 | .PHONY: all test clean $(TESTS) |
| 28 | 28 | ||
| @@ -81,5 +81,11 @@ test_nip04: test_nip04.c $(REPO_ROOT)/main/nip04.c $(SECP256K1_OBJ) | |||
| 81 | test_cvm_server: test_cvm_server.c | 81 | test_cvm_server: test_cvm_server.c |
| 82 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) | 82 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) |
| 83 | 83 | ||
| 84 | 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) | ||
| 85 | $(CC) $(CFLAGS) -I $(SECP256K1_PRIV_INC) -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) | ||
| 86 | |||
| 87 | test_relay_selector: test_relay_selector.c | ||
| 88 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) | ||
| 89 | |||
| 84 | clean: | 90 | clean: |
| 85 | rm -f $(TESTS) $(SECP256K1_OBJ) | 91 | rm -f $(TESTS) $(SECP256K1_OBJ) |
diff --git a/tests/unit/stubs/freertos/FreeRTOS.h b/tests/unit/stubs/freertos/FreeRTOS.h index 696da87..41426c8 100644 --- a/tests/unit/stubs/freertos/FreeRTOS.h +++ b/tests/unit/stubs/freertos/FreeRTOS.h | |||
| @@ -7,6 +7,7 @@ static inline uint32_t xTaskGetTickCount(void) { return 0; } | |||
| 7 | static inline void vTaskDelay(uint32_t ticks) { (void)ticks; } | 7 | static inline void vTaskDelay(uint32_t ticks) { (void)ticks; } |
| 8 | #define pdMS_TO_TICKS(ms) ((ms) / 10) | 8 | #define pdMS_TO_TICKS(ms) ((ms) / 10) |
| 9 | #define portTICK_PERIOD_MS 10 | 9 | #define portTICK_PERIOD_MS 10 |
| 10 | #define configTICK_RATE_HZ 100 | ||
| 10 | #define portMAX_DELAY 0xFFFFFFFF | 11 | #define portMAX_DELAY 0xFFFFFFFF |
| 11 | 12 | ||
| 12 | #endif | 13 | #endif |
diff --git a/tests/unit/test_relay_selector.c b/tests/unit/test_relay_selector.c new file mode 100644 index 0000000..b062c3a --- /dev/null +++ b/tests/unit/test_relay_selector.c | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include <stdbool.h> | ||
| 3 | #include <string.h> | ||
| 4 | #include <stdlib.h> | ||
| 5 | |||
| 6 | typedef struct { | ||
| 7 | char url[128]; | ||
| 8 | char name[64]; | ||
| 9 | uint32_t latency_ms; | ||
| 10 | bool supports_nip77; | ||
| 11 | bool alive; | ||
| 12 | int consecutive_failures; | ||
| 13 | } test_relay_t; | ||
| 14 | |||
| 15 | static int compare_relays(const void *a, const void *b) | ||
| 16 | { | ||
| 17 | const test_relay_t *ra = (const test_relay_t *)a; | ||
| 18 | const test_relay_t *rb = (const test_relay_t *)b; | ||
| 19 | if (ra->alive && !rb->alive) return -1; | ||
| 20 | if (!ra->alive && rb->alive) return 1; | ||
| 21 | int score_a = (ra->supports_nip77 ? 1000 : 0) - ra->consecutive_failures * 100; | ||
| 22 | int score_b = (rb->supports_nip77 ? 1000 : 0) - rb->consecutive_failures * 100; | ||
| 23 | if (score_a != score_b) return score_b - score_a; | ||
| 24 | return (int)ra->latency_ms - (int)rb->latency_ms; | ||
| 25 | } | ||
| 26 | |||
| 27 | int main(void) | ||
| 28 | { | ||
| 29 | printf("=== test_relay_selector ===\n"); | ||
| 30 | |||
| 31 | printf("\n--- NIP-77 relay preferred over non-NIP-77 ---\n"); | ||
| 32 | test_relay_t relays[4] = { | ||
| 33 | { .url = "relay_a", .latency_ms = 50, .supports_nip77 = false, .alive = true, .consecutive_failures = 0 }, | ||
| 34 | { .url = "relay_b", .latency_ms = 200, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 35 | { .url = "relay_c", .latency_ms = 30, .supports_nip77 = false, .alive = true, .consecutive_failures = 0 }, | ||
| 36 | { .url = "relay_d", .latency_ms = 500, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 37 | }; | ||
| 38 | qsort(relays, 4, sizeof(test_relay_t), compare_relays); | ||
| 39 | ASSERT_EQ_STR("relay_b", relays[0].url, "NIP-77 relay with 200ms beats non-NIP with 50ms"); | ||
| 40 | |||
| 41 | printf("\n--- Dead relays sorted last ---\n"); | ||
| 42 | test_relay_t dead_test[2] = { | ||
| 43 | { .url = "alive_relay", .latency_ms = 500, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 44 | { .url = "dead_relay", .latency_ms = 10, .supports_nip77 = true, .alive = false, .consecutive_failures = 3 }, | ||
| 45 | }; | ||
| 46 | qsort(dead_test, 2, sizeof(test_relay_t), compare_relays); | ||
| 47 | ASSERT_EQ_STR("alive_relay", dead_test[0].url, "Alive relay sorts before dead"); | ||
| 48 | |||
| 49 | printf("\n--- Latency tiebreak when same NIP-77 status ---\n"); | ||
| 50 | test_relay_t tiebreak[3] = { | ||
| 51 | { .url = "slow", .latency_ms = 300, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 52 | { .url = "fast", .latency_ms = 50, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 53 | { .url = "medium", .latency_ms = 150, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 54 | }; | ||
| 55 | qsort(tiebreak, 3, sizeof(test_relay_t), compare_relays); | ||
| 56 | ASSERT_EQ_STR("fast", tiebreak[0].url, "Fastest NIP-77 relay sorts first"); | ||
| 57 | ASSERT_EQ_STR("medium", tiebreak[1].url, "Medium NIP-77 relay sorts second"); | ||
| 58 | ASSERT_EQ_STR("slow", tiebreak[2].url, "Slowest NIP-77 relay sorts last"); | ||
| 59 | |||
| 60 | printf("\n--- Failure penalty ---\n"); | ||
| 61 | test_relay_t failures[2] = { | ||
| 62 | { .url = "clean", .latency_ms = 200, .supports_nip77 = true, .alive = true, .consecutive_failures = 0 }, | ||
| 63 | { .url = "flaky", .latency_ms = 50, .supports_nip77 = true, .alive = true, .consecutive_failures = 2 }, | ||
| 64 | }; | ||
| 65 | qsort(failures, 2, sizeof(test_relay_t), compare_relays); | ||
| 66 | ASSERT_EQ_STR("clean", failures[0].url, "Clean relay beats flaky one"); | ||
| 67 | |||
| 68 | printf("\n--- Non-NIP-77 relay with failures vs alive non-NIP-77 ---\n"); | ||
| 69 | test_relay_t mixed[2] = { | ||
| 70 | { .url = "ok_relay", .latency_ms = 100, .supports_nip77 = false, .alive = true, .consecutive_failures = 0 }, | ||
| 71 | { .url = "fail_relay", .latency_ms = 50, .supports_nip77 = false, .alive = true, .consecutive_failures = 3 }, | ||
| 72 | }; | ||
| 73 | qsort(mixed, 2, sizeof(test_relay_t), compare_relays); | ||
| 74 | ASSERT_EQ_STR("ok_relay", mixed[0].url, "Non-failing relay beats one with failures"); | ||
| 75 | |||
| 76 | printf("\n=== ALL TESTS PASSED ===\n"); | ||
| 77 | return 0; | ||
| 78 | } | ||
diff --git a/tests/unit/test_relay_validator.c b/tests/unit/test_relay_validator.c new file mode 100644 index 0000000..327eb99 --- /dev/null +++ b/tests/unit/test_relay_validator.c | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include "../../main/nostr_event.h" | ||
| 3 | #include "../../main/identity.h" | ||
| 4 | #include "../../components/wisp_relay/relay_validator.h" | ||
| 5 | #include <string.h> | ||
| 6 | #include <stdio.h> | ||
| 7 | #include <stdlib.h> | ||
| 8 | |||
| 9 | static const char *TEST_NSEC = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"; | ||
| 10 | |||
| 11 | int main(void) | ||
| 12 | { | ||
| 13 | printf("=== test_relay_validator ===\n"); | ||
| 14 | |||
| 15 | identity_init(TEST_NSEC); | ||
| 16 | const tollgate_identity_t *id = identity_get(); | ||
| 17 | |||
| 18 | printf("\n--- Valid event verification ---\n"); | ||
| 19 | nostr_event_t event; | ||
| 20 | esp_err_t ret = nostr_event_init(&event, id->npub_hex, 1, "[]", "test relay validator"); | ||
| 21 | ASSERT_EQ_INT(ESP_OK, ret, "event init succeeds"); | ||
| 22 | ret = nostr_event_sign(&event, id->nsec); | ||
| 23 | ASSERT_EQ_INT(ESP_OK, ret, "event sign succeeds"); | ||
| 24 | |||
| 25 | char json_buf[2048]; | ||
| 26 | ret = nostr_event_to_json(&event, json_buf, sizeof(json_buf)); | ||
| 27 | ASSERT_EQ_INT(ESP_OK, ret, "event to json succeeds"); | ||
| 28 | |||
| 29 | bool valid = relay_validator_verify_event(json_buf, strlen(json_buf)); | ||
| 30 | ASSERT(valid, "Valid event passes verification"); | ||
| 31 | |||
| 32 | printf("\n--- Tampered event ID detection ---\n"); | ||
| 33 | char *tampered = strdup(json_buf); | ||
| 34 | char *id_pos = strstr(tampered, event.id); | ||
| 35 | ASSERT(id_pos != NULL, "Found ID in JSON"); | ||
| 36 | id_pos[0] = (id_pos[0] == 'a') ? 'b' : 'a'; | ||
| 37 | bool tampered_valid = relay_validator_verify_event(tampered, strlen(tampered)); | ||
| 38 | ASSERT(!tampered_valid, "Tampered event ID fails verification"); | ||
| 39 | free(tampered); | ||
| 40 | |||
| 41 | printf("\n--- Tampered signature detection ---\n"); | ||
| 42 | tampered = strdup(json_buf); | ||
| 43 | char *sig_pos = strstr(tampered, event.sig); | ||
| 44 | ASSERT(sig_pos != NULL, "Found sig in JSON"); | ||
| 45 | sig_pos[0] = (sig_pos[0] == 'a') ? 'b' : 'a'; | ||
| 46 | tampered_valid = relay_validator_verify_event(tampered, strlen(tampered)); | ||
| 47 | ASSERT(!tampered_valid, "Tampered signature fails verification"); | ||
| 48 | free(tampered); | ||
| 49 | |||
| 50 | printf("\n--- Tampered content detection ---\n"); | ||
| 51 | tampered = strdup(json_buf); | ||
| 52 | char *content_pos = strstr(tampered, "test relay validator"); | ||
| 53 | ASSERT(content_pos != NULL, "Found content in JSON"); | ||
| 54 | content_pos[0] = 'X'; | ||
| 55 | tampered_valid = relay_validator_verify_event(tampered, strlen(tampered)); | ||
| 56 | ASSERT(!tampered_valid, "Tampered content fails verification"); | ||
| 57 | free(tampered); | ||
| 58 | |||
| 59 | printf("\n--- Empty/invalid JSON ---\n"); | ||
| 60 | ASSERT(!relay_validator_verify_event("", 0), "Empty string fails"); | ||
| 61 | ASSERT(!relay_validator_verify_event("{}", 2), "Empty object fails"); | ||
| 62 | ASSERT(!relay_validator_verify_event("not json", 8), "Non-JSON fails"); | ||
| 63 | ASSERT(!relay_validator_verify_event("[1,2,3]", 7), "Array fails"); | ||
| 64 | |||
| 65 | printf("\n--- Missing fields ---\n"); | ||
| 66 | ASSERT(!relay_validator_verify_event("{\"id\":\"" "0000000000000000000000000000000000000000000000000000000000000000" "\"}", 71), | ||
| 67 | "Missing pubkey/sig fails"); | ||
| 68 | |||
| 69 | printf("\n--- result_string ---\n"); | ||
| 70 | ASSERT_EQ_STR("ok", relay_validator_result_string(VALIDATION_OK), "OK string"); | ||
| 71 | ASSERT_EQ_STR("invalid: signature", relay_validator_result_string(VALIDATION_ERR_SIG), "SIG string"); | ||
| 72 | ASSERT_EQ_STR("invalid: event id", relay_validator_result_string(VALIDATION_ERR_ID), "ID string"); | ||
| 73 | |||
| 74 | printf("\n=== ALL TESTS PASSED ===\n"); | ||
| 75 | return 0; | ||
| 76 | } | ||