upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit')
-rw-r--r--tests/unit/Makefile9
-rw-r--r--tests/unit/stubs/esp_log.h1
-rw-r--r--tests/unit/stubs/esp_system.h10
-rw-r--r--tests/unit/test_mcp_handler.c148
-rw-r--r--tests/unit/test_nip04.c107
5 files changed, 274 insertions, 1 deletions
diff --git a/tests/unit/Makefile b/tests/unit/Makefile
index 53bcc2c..5dee0d7 100644
--- a/tests/unit/Makefile
+++ b/tests/unit/Makefile
@@ -10,6 +10,7 @@ CFLAGS := -Wall -Wextra -Wno-unused-parameter -Wno-unused-function -Wno-sign-com
10 -std=gnu17 -g -O0 \ 10 -std=gnu17 -g -O0 \
11 -DTEST_HOST \ 11 -DTEST_HOST \
12 -DENABLE_MODULE_SCHNORRSIG=1 -DENABLE_MODULE_EXTRAKEYS=1 \ 12 -DENABLE_MODULE_SCHNORRSIG=1 -DENABLE_MODULE_EXTRAKEYS=1 \
13 -DENABLE_MODULE_ECDH=1 \
13 -DECMULT_WINDOW_SIZE=8 -DECMULT_GEN_PREC_BITS=4 \ 14 -DECMULT_WINDOW_SIZE=8 -DECMULT_GEN_PREC_BITS=4 \
14 -include stubs/esp_err.h \ 15 -include stubs/esp_err.h \
15 -I stubs \ 16 -I stubs \
@@ -21,7 +22,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm
21 22
22SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o 23SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o
23 24
24TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout 25TESTS := 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
25 26
26.PHONY: all test clean $(TESTS) 27.PHONY: all test clean $(TESTS)
27 28
@@ -71,5 +72,11 @@ test_lnurl_pay: test_lnurl_pay.c
71test_lightning_payout: test_lightning_payout.c 72test_lightning_payout: test_lightning_payout.c
72 $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) 73 $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
73 74
75test_mcp_handler: test_mcp_handler.c $(REPO_ROOT)/main/mcp_handler.c
76 $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/mcp_handler.c -o $@ $(LDFLAGS)
77
78test_nip04: test_nip04.c $(REPO_ROOT)/main/nip04.c $(SECP256K1_OBJ)
79 $(CC) $(CFLAGS) -I $(SECP256K1_PRIV_INC) $< $(REPO_ROOT)/main/nip04.c $(SECP256K1_OBJ) -o $@ $(LDFLAGS)
80
74clean: 81clean:
75 rm -f $(TESTS) $(SECP256K1_OBJ) 82 rm -f $(TESTS) $(SECP256K1_OBJ)
diff --git a/tests/unit/stubs/esp_log.h b/tests/unit/stubs/esp_log.h
index f353fe9..b9d44b3 100644
--- a/tests/unit/stubs/esp_log.h
+++ b/tests/unit/stubs/esp_log.h
@@ -6,5 +6,6 @@
6#define ESP_LOGI(tag, fmt, ...) do { printf("I %s: " fmt "\n", tag, ##__VA_ARGS__); } while(0) 6#define ESP_LOGI(tag, fmt, ...) do { printf("I %s: " fmt "\n", tag, ##__VA_ARGS__); } while(0)
7#define ESP_LOGW(tag, fmt, ...) do { printf("W %s: " fmt "\n", tag, ##__VA_ARGS__); } while(0) 7#define ESP_LOGW(tag, fmt, ...) do { printf("W %s: " fmt "\n", tag, ##__VA_ARGS__); } while(0)
8#define ESP_LOGE(tag, fmt, ...) do { fprintf(stderr, "E %s: " fmt "\n", tag, ##__VA_ARGS__); } while(0) 8#define ESP_LOGE(tag, fmt, ...) do { fprintf(stderr, "E %s: " fmt "\n", tag, ##__VA_ARGS__); } while(0)
9#define ESP_LOGD(tag, fmt, ...) do { } while(0)
9 10
10#endif 11#endif
diff --git a/tests/unit/stubs/esp_system.h b/tests/unit/stubs/esp_system.h
index 8e63c80..cd54743 100644
--- a/tests/unit/stubs/esp_system.h
+++ b/tests/unit/stubs/esp_system.h
@@ -1,4 +1,14 @@
1#ifndef STUBS_ESP_SYSTEM_H 1#ifndef STUBS_ESP_SYSTEM_H
2#define STUBS_ESP_SYSTEM_H 2#define STUBS_ESP_SYSTEM_H
3 3
4#include <stdlib.h>
5#include <stdint.h>
6
7static inline void esp_fill_random(uint8_t *buf, size_t len)
8{
9 for (size_t i = 0; i < len; i++) {
10 buf[i] = (uint8_t)(rand() & 0xFF);
11 }
12}
13
4#endif 14#endif
diff --git a/tests/unit/test_mcp_handler.c b/tests/unit/test_mcp_handler.c
new file mode 100644
index 0000000..aaa199d
--- /dev/null
+++ b/tests/unit/test_mcp_handler.c
@@ -0,0 +1,148 @@
1#include "test_framework.h"
2#include "mcp_handler.h"
3#include "config.h"
4#include "nucula_wallet.h"
5#include "cJSON.h"
6#include <string.h>
7#include <stdio.h>
8
9static tollgate_config_t g_test_config;
10static uint64_t g_wallet_balance = 0;
11static int g_wallet_proof_count = 0;
12static int g_wallet_send_rc = 0;
13static char g_wallet_send_token[256] = "cashuA_test_token";
14
15const tollgate_config_t *tollgate_config_get(void) {
16 return &g_test_config;
17}
18
19uint64_t nucula_wallet_balance(void) {
20 return g_wallet_balance;
21}
22
23int nucula_wallet_proof_count(void) {
24 return g_wallet_proof_count;
25}
26
27int nucula_wallet_send(uint64_t amount, char *token_out, size_t token_max) {
28 (void)amount;
29 (void)token_max;
30 if (g_wallet_send_rc == 0) {
31 strncpy(token_out, g_wallet_send_token, token_max - 1);
32 }
33 return g_wallet_send_rc;
34}
35
36static void test_mcp_parse_tool(void)
37{
38 printf("\n=== MCP tool parsing ===\n");
39 ASSERT_EQ_INT(MCP_TOOL_GET_CONFIG, mcp_parse_tool("get_config"), "get_config");
40 ASSERT_EQ_INT(MCP_TOOL_SET_CONFIG, mcp_parse_tool("set_config"), "set_config");
41 ASSERT_EQ_INT(MCP_TOOL_GET_BALANCE, mcp_parse_tool("get_balance"), "get_balance");
42 ASSERT_EQ_INT(MCP_TOOL_WALLET_SEND, mcp_parse_tool("wallet_send"), "wallet_send");
43 ASSERT_EQ_INT(MCP_TOOL_UNKNOWN, mcp_parse_tool("foo"), "unknown tool");
44 ASSERT_EQ_INT(MCP_TOOL_UNKNOWN, mcp_parse_tool(NULL), "NULL tool");
45}
46
47static void test_mcp_get_config(void)
48{
49 printf("\n=== MCP get_config ===\n");
50 memset(&g_test_config, 0, sizeof(g_test_config));
51 strncpy(g_test_config.ap_ssid, "TollGate-TEST", sizeof(g_test_config.ap_ssid) - 1);
52 strncpy(g_test_config.metric, "bytes", sizeof(g_test_config.metric) - 1);
53 g_test_config.price_per_step = 21;
54 g_test_config.step_size_ms = 60000;
55 g_test_config.step_size_bytes = 22020096;
56 strncpy(g_test_config.mint_url, "https://testnut.cashu.space", sizeof(g_test_config.mint_url) - 1);
57
58 mcp_response_t resp = mcp_handle_get_config();
59 ASSERT(resp.success, "get_config succeeds");
60
61 cJSON *result = cJSON_Parse(resp.result_json);
62 ASSERT(result != NULL, "result is valid JSON");
63 ASSERT_EQ_STR("bytes", cJSON_GetObjectItem(result, "metric")->valuestring, "metric=bytes");
64 ASSERT_EQ_INT(21, cJSON_GetObjectItem(result, "price_per_step")->valueint, "price=21");
65 cJSON_Delete(result);
66}
67
68static void test_mcp_set_config(void)
69{
70 printf("\n=== MCP set_config ===\n");
71 memset(&g_test_config, 0, sizeof(g_test_config));
72 g_test_config.price_per_step = 21;
73
74 const char *params = "{\"price_per_step\":42,\"metric\":\"milliseconds\"}";
75 mcp_response_t resp = mcp_handle_set_config(params);
76 ASSERT(resp.success, "set_config succeeds");
77 ASSERT_EQ_INT(42, g_test_config.price_per_step, "price updated to 42");
78 ASSERT_EQ_STR("milliseconds", g_test_config.metric, "metric updated");
79
80 resp = mcp_handle_set_config("not json");
81 ASSERT(!resp.success, "invalid JSON fails");
82}
83
84static void test_mcp_get_balance(void)
85{
86 printf("\n=== MCP get_balance ===\n");
87 g_wallet_balance = 500;
88 g_wallet_proof_count = 8;
89
90 mcp_response_t resp = mcp_handle_get_balance();
91 ASSERT(resp.success, "get_balance succeeds");
92
93 cJSON *result = cJSON_Parse(resp.result_json);
94 ASSERT(result != NULL, "result is valid JSON");
95 ASSERT_EQ_INT(500, (int)cJSON_GetObjectItem(result, "balance_sats")->valuedouble, "balance=500");
96 ASSERT_EQ_INT(8, cJSON_GetObjectItem(result, "proof_count")->valueint, "proofs=8");
97 cJSON_Delete(result);
98}
99
100static void test_mcp_wallet_send(void)
101{
102 printf("\n=== MCP wallet_send ===\n");
103 g_wallet_send_rc = 0;
104 strncpy(g_wallet_send_token, "cashuA_send_test", sizeof(g_wallet_send_token) - 1);
105
106 const char *params = "{\"amount\":21}";
107 mcp_response_t resp = mcp_handle_wallet_send(params);
108 ASSERT(resp.success, "wallet_send succeeds");
109
110 cJSON *result = cJSON_Parse(resp.result_json);
111 ASSERT(result != NULL, "result is valid JSON");
112 ASSERT_EQ_STR("cashuA_send_test", cJSON_GetObjectItem(result, "token")->valuestring, "token matches");
113 cJSON_Delete(result);
114
115 printf("\n--- wallet_send missing amount ---\n");
116 resp = mcp_handle_wallet_send("{}");
117 ASSERT(!resp.success, "missing amount fails");
118
119 printf("\n--- wallet_send send fails ---\n");
120 g_wallet_send_rc = -1;
121 resp = mcp_handle_wallet_send("{\"amount\":100}");
122 ASSERT(!resp.success, "send failure reported");
123}
124
125static void test_mcp_dispatch(void)
126{
127 printf("\n=== MCP dispatch ===\n");
128 mcp_request_t req = {0};
129 req.tool = MCP_TOOL_UNKNOWN;
130 strncpy(req.method, "bogus", sizeof(req.method) - 1);
131 mcp_response_t resp = mcp_dispatch(&req);
132 ASSERT(!resp.success, "unknown tool dispatch fails");
133
134 resp = mcp_dispatch(NULL);
135 ASSERT(!resp.success, "NULL request dispatch fails");
136}
137
138int main(void)
139{
140 printf("=== test_mcp_handler ===\n");
141 test_mcp_parse_tool();
142 test_mcp_get_config();
143 test_mcp_set_config();
144 test_mcp_get_balance();
145 test_mcp_wallet_send();
146 test_mcp_dispatch();
147 TEST_SUMMARY();
148}
diff --git a/tests/unit/test_nip04.c b/tests/unit/test_nip04.c
new file mode 100644
index 0000000..27eb13c
--- /dev/null
+++ b/tests/unit/test_nip04.c
@@ -0,0 +1,107 @@
1#include "test_framework.h"
2#include "../../main/nip04.h"
3#include <secp256k1.h>
4#include <secp256k1_ecdh.h>
5#include <secp256k1_extrakeys.h>
6#include <string.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <stdbool.h>
10
11static void test_nip04_roundtrip(void)
12{
13 printf("\n=== NIP-04 encrypt/decrypt roundtrip ===\n");
14
15 secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
16
17 uint8_t alice_sec[32], bob_sec[32];
18 memset(alice_sec, 0x01, 32);
19 memset(bob_sec, 0x02, 32);
20
21 secp256k1_pubkey alice_pk, bob_pk;
22 secp256k1_ec_pubkey_create(ctx, &alice_pk, alice_sec);
23 secp256k1_ec_pubkey_create(ctx, &bob_pk, bob_sec);
24
25 uint8_t alice_xonly[32], bob_xonly[32];
26 secp256k1_xonly_pubkey alice_xpk, bob_xpk;
27 secp256k1_xonly_pubkey_from_pubkey(ctx, &alice_xpk, NULL, &alice_pk);
28 secp256k1_xonly_pubkey_from_pubkey(ctx, &bob_xpk, NULL, &bob_pk);
29 secp256k1_xonly_pubkey_serialize(ctx, alice_xonly, &alice_xpk);
30 secp256k1_xonly_pubkey_serialize(ctx, bob_xonly, &bob_xpk);
31
32 const char *message = "Hello, ContextVM! This is a test message.";
33
34 uint8_t ciphertext[4096];
35 size_t ct_len = 0;
36 nip04_encrypt(alice_sec, bob_xonly, message, ciphertext, &ct_len);
37 ASSERT(ct_len > 0, "encryption produced output");
38 ASSERT(ct_len > strlen(message), "ciphertext longer than plaintext");
39
40 char plaintext[2048];
41 int pt_len = nip04_decrypt(bob_sec, alice_xonly, (const char *)ciphertext, plaintext, sizeof(plaintext));
42 ASSERT(pt_len > 0, "decryption succeeded");
43 ASSERT_EQ_STR(message, plaintext, "decrypted message matches original");
44
45 printf("\n--- Different key produces garbage ---\n");
46 uint8_t eve_sec[32];
47 memset(eve_sec, 0x03, 32);
48 pt_len = nip04_decrypt(eve_sec, alice_xonly, (const char *)ciphertext, plaintext, sizeof(plaintext));
49 if (pt_len > 0) {
50 ASSERT(strcmp(message, plaintext) != 0, "wrong key produces different output");
51 } else {
52 ASSERT(true, "wrong key fails to decrypt");
53 }
54
55 printf("\n--- Second roundtrip (different message) ---\n");
56 const char *msg2 = "Short";
57 nip04_encrypt(alice_sec, bob_xonly, msg2, ciphertext, &ct_len);
58 pt_len = nip04_decrypt(bob_sec, alice_xonly, (const char *)ciphertext, plaintext, sizeof(plaintext));
59 ASSERT(pt_len > 0, "short message decrypts");
60 ASSERT_EQ_STR(msg2, plaintext, "short message matches");
61
62 printf("\n--- Long message roundtrip ---\n");
63 char long_msg[512];
64 memset(long_msg, 'X', 511);
65 long_msg[511] = '\0';
66 nip04_encrypt(alice_sec, bob_xonly, long_msg, ciphertext, &ct_len);
67 pt_len = nip04_decrypt(bob_sec, alice_xonly, (const char *)ciphertext, plaintext, sizeof(plaintext));
68 ASSERT(pt_len > 0, "long message decrypts");
69 ASSERT_EQ_INT(511, pt_len, "long message length matches");
70
71 printf("\n--- Bob encrypts, Alice decrypts ---\n");
72 nip04_encrypt(bob_sec, alice_xonly, message, ciphertext, &ct_len);
73 pt_len = nip04_decrypt(alice_sec, bob_xonly, (const char *)ciphertext, plaintext, sizeof(plaintext));
74 ASSERT(pt_len > 0, "reverse direction decrypts");
75 ASSERT_EQ_STR(message, plaintext, "reverse direction matches");
76
77 secp256k1_context_destroy(ctx);
78}
79
80static void test_nip04_invalid_input(void)
81{
82 printf("\n=== NIP-04 invalid inputs ===\n");
83 char plaintext[256];
84 int rc = nip04_decrypt(NULL, NULL, "AAAA", plaintext, sizeof(plaintext));
85 ASSERT(rc < 0, "NULL keys fails");
86
87 uint8_t dummy_sec[32];
88 memset(dummy_sec, 0xAA, 32);
89 rc = nip04_decrypt(dummy_sec, NULL, "AAAA", plaintext, sizeof(plaintext));
90 ASSERT(rc < 0, "NULL pubkey fails");
91
92 uint8_t dummy_pub[32];
93 memset(dummy_pub, 0xBB, 32);
94 rc = nip04_decrypt(dummy_sec, dummy_pub, "", plaintext, sizeof(plaintext));
95 ASSERT(rc < 0, "empty ciphertext fails");
96
97 rc = nip04_decrypt(dummy_sec, dummy_pub, "AAAA", plaintext, sizeof(plaintext));
98 ASSERT(rc < 0, "garbage ciphertext fails");
99}
100
101int main(void)
102{
103 printf("=== test_nip04 ===\n");
104 test_nip04_roundtrip();
105 test_nip04_invalid_input();
106 TEST_SUMMARY();
107}