upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/unit/test_cvm_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/test_cvm_server.c')
-rw-r--r--tests/unit/test_cvm_server.c434
1 files changed, 434 insertions, 0 deletions
diff --git a/tests/unit/test_cvm_server.c b/tests/unit/test_cvm_server.c
new file mode 100644
index 0000000..84583c6
--- /dev/null
+++ b/tests/unit/test_cvm_server.c
@@ -0,0 +1,434 @@
1#include "test_framework.h"
2#include "cJSON.h"
3#include <stdbool.h>
4#include <string.h>
5#include <stdio.h>
6#include <stdlib.h>
7
8static char *build_initialize_response_test(const char *request_id_str)
9{
10 cJSON *response = cJSON_CreateObject();
11 cJSON_AddStringToObject(response, "jsonrpc", "2.0");
12 cJSON_AddNumberToObject(response, "id", request_id_str ? atof(request_id_str) : 0);
13
14 cJSON *result = cJSON_CreateObject();
15 cJSON_AddStringToObject(result, "protocolVersion", "2025-07-02");
16
17 cJSON *capabilities = cJSON_CreateObject();
18 cJSON_AddItemToObject(capabilities, "tools", cJSON_CreateObject());
19 cJSON_AddItemToObject(result, "capabilities", capabilities);
20
21 cJSON *serverInfo = cJSON_CreateObject();
22 cJSON_AddStringToObject(serverInfo, "name", "TollGate");
23 cJSON_AddStringToObject(serverInfo, "version", "1.0.0");
24 cJSON_AddItemToObject(result, "serverInfo", serverInfo);
25
26 cJSON_AddItemToObject(response, "result", result);
27
28 char *json = cJSON_PrintUnformatted(response);
29 cJSON_Delete(response);
30 return json;
31}
32
33static char *build_tools_list_response_test(const char *request_id_str)
34{
35 cJSON *response = cJSON_CreateObject();
36 cJSON_AddStringToObject(response, "jsonrpc", "2.0");
37 cJSON_AddNumberToObject(response, "id", request_id_str ? atof(request_id_str) : 1);
38
39 cJSON *result = cJSON_CreateObject();
40 cJSON *tools = cJSON_CreateArray();
41
42 const char *tool_names[] = {
43 "get_config", "set_config", "get_balance", "wallet_send",
44 "get_sessions", "get_usage", "set_payout", "set_metric",
45 "set_price", "wallet_melt"
46 };
47
48 for (int i = 0; i < 10; i++) {
49 cJSON *tool = cJSON_CreateObject();
50 cJSON_AddStringToObject(tool, "name", tool_names[i]);
51 cJSON_AddItemToArray(tools, tool);
52 }
53
54 cJSON_AddItemToObject(result, "tools", tools);
55 cJSON_AddItemToObject(response, "result", result);
56
57 char *json = cJSON_PrintUnformatted(response);
58 cJSON_Delete(response);
59 return json;
60}
61
62static char *build_tool_call_response_test(const char *request_id_str,
63 bool success, const char *result_or_error)
64{
65 cJSON *response = cJSON_CreateObject();
66 cJSON_AddStringToObject(response, "jsonrpc", "2.0");
67 cJSON_AddNumberToObject(response, "id", request_id_str ? atof(request_id_str) : 2);
68
69 if (success) {
70 cJSON *result = cJSON_CreateObject();
71 cJSON *content_arr = cJSON_CreateArray();
72 cJSON *text_item = cJSON_CreateObject();
73 cJSON_AddStringToObject(text_item, "type", "text");
74 cJSON_AddStringToObject(text_item, "text", result_or_error);
75 cJSON_AddItemToArray(content_arr, text_item);
76 cJSON_AddItemToObject(result, "content", content_arr);
77 cJSON_AddBoolToObject(result, "isError", false);
78 cJSON_AddItemToObject(response, "result", result);
79 } else {
80 cJSON *error = cJSON_CreateObject();
81 cJSON_AddNumberToObject(error, "code", -32603);
82 cJSON_AddStringToObject(error, "message", result_or_error);
83 cJSON_AddItemToObject(response, "error", error);
84 }
85
86 char *json = cJSON_PrintUnformatted(response);
87 cJSON_Delete(response);
88 return json;
89}
90
91static char *build_ping_response_test(const char *request_id_str)
92{
93 cJSON *response = cJSON_CreateObject();
94 cJSON_AddStringToObject(response, "jsonrpc", "2.0");
95 cJSON_AddNumberToObject(response, "id", request_id_str ? atof(request_id_str) : 0);
96 cJSON *result = cJSON_CreateObject();
97 cJSON_AddItemToObject(response, "result", result);
98 char *json = cJSON_PrintUnformatted(response);
99 cJSON_Delete(response);
100 return json;
101}
102
103static char *build_announcement_11316_test(void)
104{
105 cJSON *ann = cJSON_CreateObject();
106 cJSON_AddStringToObject(ann, "protocolVersion", "2025-07-02");
107
108 cJSON *caps = cJSON_CreateObject();
109 cJSON *tools = cJSON_CreateObject();
110 cJSON_AddBoolToObject(tools, "listChanged", true);
111 cJSON_AddItemToObject(caps, "tools", tools);
112 cJSON_AddItemToObject(ann, "capabilities", caps);
113
114 cJSON *info = cJSON_CreateObject();
115 cJSON_AddStringToObject(info, "name", "TollGate");
116 cJSON_AddStringToObject(info, "version", "1.0.0");
117 cJSON_AddItemToObject(ann, "serverInfo", info);
118
119 char *json = cJSON_PrintUnformatted(ann);
120 cJSON_Delete(ann);
121 return json;
122}
123
124static char *build_announcement_11317_test(void)
125{
126 cJSON *root = cJSON_CreateObject();
127 cJSON *tools = cJSON_CreateArray();
128
129 const char *names[] = {
130 "get_config", "set_config", "get_balance", "wallet_send",
131 "get_sessions", "get_usage", "set_payout", "set_metric",
132 "set_price", "wallet_melt"
133 };
134
135 for (int i = 0; i < 10; i++) {
136 cJSON *t = cJSON_CreateObject();
137 cJSON_AddStringToObject(t, "name", names[i]);
138 cJSON_AddStringToObject(t, "description", "test");
139 cJSON *schema = cJSON_CreateObject();
140 cJSON_AddStringToObject(schema, "type", "object");
141 cJSON_AddItemToObject(t, "inputSchema", schema);
142 cJSON_AddItemToArray(tools, t);
143 }
144
145 cJSON_AddItemToObject(root, "tools", tools);
146 char *json = cJSON_PrintUnformatted(root);
147 cJSON_Delete(root);
148 return json;
149}
150
151static char *build_relay_list_10002_test(void)
152{
153 cJSON *tags = cJSON_CreateArray();
154 const char *relays[] = {"wss://relay.damus.io", "wss://nos.lol"};
155 for (int i = 0; i < 2; i++) {
156 cJSON *r = cJSON_CreateArray();
157 cJSON_AddItemToArray(r, cJSON_CreateString("r"));
158 cJSON_AddItemToArray(r, cJSON_CreateString(relays[i]));
159 cJSON_AddItemToArray(tags, r);
160 }
161 char *json = cJSON_PrintUnformatted(tags);
162 cJSON_Delete(tags);
163 return json;
164}
165
166static bool parse_mcp_from_25910(const char *content, char *method_out, size_t method_max,
167 char *params_out, size_t params_max)
168{
169 cJSON *msg = cJSON_Parse(content);
170 if (!msg) return false;
171
172 cJSON *method = cJSON_GetObjectItem(msg, "method");
173 if (!method || !cJSON_IsString(method)) {
174 cJSON_Delete(msg);
175 return false;
176 }
177
178 strncpy(method_out, method->valuestring, method_max - 1);
179
180 cJSON *params = cJSON_GetObjectItem(msg, "params");
181 if (params) {
182 char *pjson = cJSON_PrintUnformatted(params);
183 strncpy(params_out, pjson, params_max - 1);
184 cJSON_free(pjson);
185 }
186
187 cJSON_Delete(msg);
188 return true;
189}
190
191static void test_initialize_response(void)
192{
193 printf("\n=== MCP initialize response ===\n");
194 char *json = build_initialize_response_test("0");
195 ASSERT(json != NULL, "response created");
196
197 cJSON *root = cJSON_Parse(json);
198 ASSERT(root != NULL, "valid JSON");
199 ASSERT_EQ_STR("2.0", cJSON_GetObjectItem(root, "jsonrpc")->valuestring, "jsonrpc version");
200 ASSERT_EQ_INT(0, (int)cJSON_GetObjectItem(root, "id")->valuedouble, "id=0");
201
202 cJSON *result = cJSON_GetObjectItem(root, "result");
203 ASSERT(result != NULL, "has result");
204 ASSERT_EQ_STR("2025-07-02", cJSON_GetObjectItem(result, "protocolVersion")->valuestring, "protocol version");
205
206 cJSON *caps = cJSON_GetObjectItem(result, "capabilities");
207 ASSERT(caps != NULL, "has capabilities");
208 ASSERT(cJSON_GetObjectItem(caps, "tools") != NULL, "has tools capability");
209
210 cJSON *info = cJSON_GetObjectItem(result, "serverInfo");
211 ASSERT(info != NULL, "has serverInfo");
212 ASSERT_EQ_STR("TollGate", cJSON_GetObjectItem(info, "name")->valuestring, "server name");
213 ASSERT_EQ_STR("1.0.0", cJSON_GetObjectItem(info, "version")->valuestring, "server version");
214
215 cJSON_Delete(root);
216 free(json);
217}
218
219static void test_tools_list_response(void)
220{
221 printf("\n=== MCP tools/list response ===\n");
222 char *json = build_tools_list_response_test("1");
223 ASSERT(json != NULL, "response created");
224
225 cJSON *root = cJSON_Parse(json);
226 ASSERT_EQ_STR("2.0", cJSON_GetObjectItem(root, "jsonrpc")->valuestring, "jsonrpc version");
227
228 cJSON *result = cJSON_GetObjectItem(root, "result");
229 cJSON *tools = cJSON_GetObjectItem(result, "tools");
230 ASSERT(tools != NULL, "has tools array");
231 ASSERT_EQ_INT(10, cJSON_GetArraySize(tools), "10 tools");
232
233 ASSERT_EQ_STR("get_config", cJSON_GetObjectItem(cJSON_GetArrayItem(tools, 0), "name")->valuestring, "tool 0");
234 ASSERT_EQ_STR("wallet_melt", cJSON_GetObjectItem(cJSON_GetArrayItem(tools, 9), "name")->valuestring, "tool 9");
235
236 cJSON_Delete(root);
237 free(json);
238}
239
240static void test_tool_call_response_success(void)
241{
242 printf("\n=== MCP tools/call success response ===\n");
243 char *json = build_tool_call_response_test("2", true, "{\"balance\":500}");
244 ASSERT(json != NULL, "response created");
245
246 cJSON *root = cJSON_Parse(json);
247 cJSON *result = cJSON_GetObjectItem(root, "result");
248 ASSERT(result != NULL, "has result");
249 ASSERT(cJSON_GetObjectItem(result, "content") != NULL, "has content");
250 ASSERT_EQ_INT(0, cJSON_GetObjectItem(result, "isError")->valueint, "isError=false");
251
252 cJSON *content = cJSON_GetObjectItem(result, "content");
253 cJSON *text = cJSON_GetArrayItem(content, 0);
254 ASSERT_EQ_STR("text", cJSON_GetObjectItem(text, "type")->valuestring, "content type=text");
255 ASSERT(strstr(cJSON_GetObjectItem(text, "text")->valuestring, "balance") != NULL, "contains balance");
256
257 cJSON_Delete(root);
258 free(json);
259}
260
261static void test_tool_call_response_error(void)
262{
263 printf("\n=== MCP tools/call error response ===\n");
264 char *json = build_tool_call_response_test("3", false, "Tool not found");
265 ASSERT(json != NULL, "response created");
266
267 cJSON *root = cJSON_Parse(json);
268 cJSON *error = cJSON_GetObjectItem(root, "error");
269 ASSERT(error != NULL, "has error");
270 ASSERT_EQ_INT(-32603, cJSON_GetObjectItem(error, "code")->valueint, "error code");
271 ASSERT_EQ_STR("Tool not found", cJSON_GetObjectItem(error, "message")->valuestring, "error message");
272
273 cJSON_Delete(root);
274 free(json);
275}
276
277static void test_ping_response(void)
278{
279 printf("\n=== MCP ping response ===\n");
280 char *json = build_ping_response_test("99");
281 ASSERT(json != NULL, "response created");
282
283 cJSON *root = cJSON_Parse(json);
284 ASSERT_EQ_STR("2.0", cJSON_GetObjectItem(root, "jsonrpc")->valuestring, "jsonrpc version");
285 ASSERT(cJSON_GetObjectItem(root, "result") != NULL, "has result");
286
287 cJSON_Delete(root);
288 free(json);
289}
290
291static void test_announcement_11316(void)
292{
293 printf("\n=== Kind 11316 server announcement ===\n");
294 char *json = build_announcement_11316_test();
295 ASSERT(json != NULL, "announcement created");
296
297 cJSON *root = cJSON_Parse(json);
298 ASSERT_EQ_STR("2025-07-02", cJSON_GetObjectItem(root, "protocolVersion")->valuestring, "protocol version");
299
300 cJSON *caps = cJSON_GetObjectItem(root, "capabilities");
301 ASSERT(cJSON_GetObjectItem(caps, "tools") != NULL, "has tools capability");
302
303 cJSON *info = cJSON_GetObjectItem(root, "serverInfo");
304 ASSERT_EQ_STR("TollGate", cJSON_GetObjectItem(info, "name")->valuestring, "name");
305 ASSERT_EQ_STR("1.0.0", cJSON_GetObjectItem(info, "version")->valuestring, "version");
306
307 cJSON_Delete(root);
308 free(json);
309}
310
311static void test_announcement_11317(void)
312{
313 printf("\n=== Kind 11317 tools list ===\n");
314 char *json = build_announcement_11317_test();
315 ASSERT(json != NULL, "tools list created");
316
317 cJSON *root = cJSON_Parse(json);
318 cJSON *tools = cJSON_GetObjectItem(root, "tools");
319 ASSERT_EQ_INT(10, cJSON_GetArraySize(tools), "10 tools");
320
321 cJSON *t0 = cJSON_GetArrayItem(tools, 0);
322 ASSERT_EQ_STR("get_config", cJSON_GetObjectItem(t0, "name")->valuestring, "tool 0 name");
323 ASSERT(cJSON_GetObjectItem(t0, "inputSchema") != NULL, "tool has inputSchema");
324
325 cJSON_Delete(root);
326 free(json);
327}
328
329static void test_relay_list_10002(void)
330{
331 printf("\n=== Kind 10002 relay list ===\n");
332 char *json = build_relay_list_10002_test();
333 ASSERT(json != NULL, "relay list created");
334
335 cJSON *tags = cJSON_Parse(json);
336 ASSERT(cJSON_IsArray(tags), "is array");
337 ASSERT_EQ_INT(2, cJSON_GetArraySize(tags), "2 relay tags");
338
339 cJSON *r0 = cJSON_GetArrayItem(tags, 0);
340 ASSERT_EQ_STR("r", cJSON_GetArrayItem(r0, 0)->valuestring, "tag type r");
341 ASSERT_EQ_STR("wss://relay.damus.io", cJSON_GetArrayItem(r0, 1)->valuestring, "relay 0");
342
343 cJSON *r1 = cJSON_GetArrayItem(tags, 1);
344 ASSERT_EQ_STR("wss://nos.lol", cJSON_GetArrayItem(r1, 1)->valuestring, "relay 1");
345
346 cJSON_Delete(tags);
347 free(json);
348}
349
350static void test_mcp_parse_from_25910(void)
351{
352 printf("\n=== Parse MCP from kind 25910 content ===\n");
353
354 char method[64] = {0};
355 char params[1024] = {0};
356
357 bool ok = parse_mcp_from_25910(
358 "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"initialize\",\"params\":{}}",
359 method, sizeof(method), params, sizeof(params));
360 ASSERT(ok, "parsed initialize");
361 ASSERT_EQ_STR("initialize", method, "method=initialize");
362
363 ok = parse_mcp_from_25910(
364 "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"get_config\"}}",
365 method, sizeof(method), params, sizeof(params));
366 ASSERT(ok, "parsed tools/call");
367 ASSERT_EQ_STR("tools/call", method, "method=tools/call");
368 ASSERT(strstr(params, "get_config") != NULL, "params has get_config");
369
370 ok = parse_mcp_from_25910("{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}",
371 method, sizeof(method), params, sizeof(params));
372 ASSERT(ok, "parsed notification");
373 ASSERT_EQ_STR("notifications/initialized", method, "method=notifications/initialized");
374
375 ok = parse_mcp_from_25910("not json", method, sizeof(method), params, sizeof(params));
376 ASSERT(!ok, "garbage rejected");
377
378 ok = parse_mcp_from_25910("{\"jsonrpc\":\"2.0\"}", method, sizeof(method), params, sizeof(params));
379 ASSERT(!ok, "missing method rejected");
380}
381
382static void test_auth_check(void)
383{
384 printf("\n=== Auth check logic ===\n");
385
386 const char *owner = "d6bfe100d1600c0d8f769501676fc74c3809500bd131c8a549f88cf616c21f35";
387 const char *other = "0000000000000000000000000000000000000000000000000000000000000001";
388
389 ASSERT(strcmp(owner, owner) == 0, "owner matches self");
390 ASSERT(strcmp(owner, other) != 0, "owner differs from other");
391 ASSERT(strcmp(other, owner) != 0, "other differs from owner");
392 ASSERT(NULL == NULL, "two NULLs match (for safety check)");
393}
394
395static void test_25910_event_content_roundtrip(void)
396{
397 printf("\n=== Kind 25910 content roundtrip ===\n");
398
399 cJSON *request = cJSON_CreateObject();
400 cJSON_AddStringToObject(request, "jsonrpc", "2.0");
401 cJSON_AddNumberToObject(request, "id", 42);
402 cJSON_AddStringToObject(request, "method", "tools/call");
403 cJSON *params = cJSON_CreateObject();
404 cJSON_AddStringToObject(params, "name", "get_balance");
405 cJSON_AddItemToObject(request, "params", params);
406 char *content = cJSON_PrintUnformatted(request);
407 cJSON_Delete(request);
408
409 char method[64] = {0};
410 char params_out[1024] = {0};
411 bool ok = parse_mcp_from_25910(content, method, sizeof(method), params_out, sizeof(params_out));
412 ASSERT(ok, "roundtrip parse succeeded");
413 ASSERT_EQ_STR("tools/call", method, "method preserved");
414 ASSERT(strstr(params_out, "get_balance") != NULL, "tool name preserved");
415
416 free(content);
417}
418
419int main(void)
420{
421 printf("=== test_cvm_server ===\n");
422 test_initialize_response();
423 test_tools_list_response();
424 test_tool_call_response_success();
425 test_tool_call_response_error();
426 test_ping_response();
427 test_announcement_11316();
428 test_announcement_11317();
429 test_relay_list_10002();
430 test_mcp_parse_from_25910();
431 test_auth_check();
432 test_25910_event_content_roundtrip();
433 TEST_SUMMARY();
434}