Files
c-relay-pg/tests/test_ws_client.c

182 lines
6.9 KiB
C

/*
* test_ws_client.c — Standalone test for nostr_core_lib's WebSocket client.
*
* Connects to a relay, sends a REQ with a filter, and counts how many
* EVENT messages and EOSE it receives. This isolates the WebSocket client
* from the caching backfill logic to diagnose why the C client only gets
* 1 event from laantungir.net/relay while node.js gets 500 + EOSE.
*
* Build: see test_ws_client_makefile or use the caching/ Makefile pattern.
* cd tests && gcc -o test_ws_client test_ws_client.c \
* -I../nostr_core_lib -I../nostr_core_lib/nostr_core \
* -I../nostr_core_lib/cjson -I../nostr_core_lib/nostr_websocket \
* ../nostr_core_lib/libnostr_core_x64.a \
* -lssl -lcrypto -lwebsockets -lsecp256k1 -lz -ldl -lpthread -lm
*
* Usage: ./test_ws_client <relay_url> <pubkey_hex> [limit] [kinds]
* limit defaults to 500, kinds defaults to "0,1,3,6,10000,10002,30023"
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "nostr_core.h"
#include "cjson/cJSON.h"
typedef struct {
int event_count;
int got_eose;
int got_timeout;
int got_error;
char last_status[256];
time_t start;
} test_ctx_t;
static void test_callback(const char* relay_url, const char* status,
const char* event_id, int events_received,
int total_relays, int completed_relays,
void* user_data) {
(void)relay_url; (void)event_id; (void)total_relays; (void)completed_relays;
test_ctx_t* ctx = (test_ctx_t*)user_data;
if (!ctx || !status) return;
double elapsed = difftime(time(NULL), ctx->start);
/* Track all status types */
if (strcmp(status, "event_found") == 0) {
ctx->event_count = events_received;
if (ctx->event_count % 50 == 0) {
printf(" [%5.0fs] %d events received\n", elapsed, ctx->event_count);
}
} else if (strcmp(status, "eose") == 0) {
ctx->got_eose = 1;
ctx->event_count = events_received;
printf(" [%5.0fs] EOSE received! events=%d\n", elapsed, ctx->event_count);
} else if (strcmp(status, "timeout") == 0) {
ctx->got_timeout = 1;
ctx->event_count = events_received;
printf(" [%5.0fs] TIMEOUT (relay timed out) events=%d\n", elapsed, ctx->event_count);
} else if (strcmp(status, "error") == 0) {
ctx->got_error = 1;
printf(" [%5.0fs] ERROR from relay\n", elapsed);
} else if (strcmp(status, "all_complete") == 0) {
printf(" [%5.0fs] all_complete (synthetic) events=%d\n", elapsed, events_received);
} else if (strcmp(status, "connecting") == 0) {
printf(" [%5.0fs] connecting...\n", elapsed);
} else if (strcmp(status, "subscribed") == 0) {
printf(" [%5.0fs] subscribed, REQ sent\n", elapsed);
} else {
printf(" [%5.0fs] status='%s' events=%d\n", elapsed, status, events_received);
}
snprintf(ctx->last_status, sizeof(ctx->last_status), "%s", status);
}
int main(int argc, char* argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <relay_url> <pubkey_hex> [limit] [kinds_csv]\n", argv[0]);
fprintf(stderr, "Example: %s wss://laantungir.net/relay 1ec454734dcbf6fe54901ce25c0c7c6bca5edd89443416761fadc321d38df139 500\n", argv[0]);
return 1;
}
const char* relay_url = argv[1];
const char* pubkey = argv[2];
int limit = (argc >= 4) ? atoi(argv[3]) : 500;
const char* kinds_csv = (argc >= 5) ? argv[4] : "0,1,3,6,10000,10002,30023";
printf("=== nostr_core_lib WebSocket Client Test ===\n");
printf("Relay: %s\n", relay_url);
printf("Pubkey: %s\n", pubkey);
printf("Limit: %d\n", limit);
printf("Kinds: %s\n", kinds_csv);
printf("Timeout: 30s\n\n");
/* Build the filter JSON */
cJSON* filter = cJSON_CreateObject();
cJSON* authors = cJSON_CreateArray();
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey));
cJSON_AddItemToObject(filter, "authors", authors);
/* Parse kinds CSV */
cJSON* kinds = cJSON_CreateArray();
char kinds_buf[256];
strncpy(kinds_buf, kinds_csv, sizeof(kinds_buf) - 1);
kinds_buf[sizeof(kinds_buf) - 1] = '\0';
char* tok = strtok(kinds_buf, ",");
while (tok) {
cJSON_AddItemToArray(kinds, cJSON_CreateNumber((double)atoi(tok)));
tok = strtok(NULL, ",");
}
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber((double)limit));
/* Use until=now to mimic backfill first query */
long now = (long)time(NULL);
cJSON_AddItemToObject(filter, "until", cJSON_CreateNumber((double)now));
printf("Until: %ld (now)\n\n", now);
const char* one_relay = relay_url;
int result_count = 0;
test_ctx_t ctx = {0};
ctx.start = time(NULL);
printf("Calling synchronous_query_relays_with_progress...\n\n");
cJSON** events = synchronous_query_relays_with_progress(
&one_relay, 1, filter, RELAY_QUERY_ALL_RESULTS,
&result_count, 30, /* 30s timeout */
test_callback, &ctx,
0, NULL /* no NIP-42 */);
double total_elapsed = difftime(time(NULL), ctx.start);
printf("\n=== RESULTS ===\n");
printf("Total elapsed: %.0fs\n", total_elapsed);
printf("Events received: %d\n", ctx.event_count);
printf("Result count: %d\n", result_count);
printf("Got EOSE: %s\n", ctx.got_eose ? "YES" : "NO");
printf("Got timeout: %s\n", ctx.got_timeout ? "YES" : "NO");
printf("Got error: %s\n", ctx.got_error ? "YES" : "NO");
printf("Last status: %s\n", ctx.last_status);
if (events) {
printf("Returned event array: %d events\n", result_count);
for (int i = 0; i < result_count && i < 3; i++) {
if (events[i]) {
cJSON* id = cJSON_GetObjectItem(events[i], "id");
cJSON* kind = cJSON_GetObjectItem(events[i], "kind");
cJSON* created = cJSON_GetObjectItem(events[i], "created_at");
printf(" event[%d]: id=%s kind=%d created_at=%lld\n",
i,
(id && cJSON_IsString(id)) ? id->valuestring : "?",
(kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1,
(created && cJSON_IsNumber(created)) ? (long long)created->valuedouble : 0);
cJSON_Delete(events[i]);
}
}
/* Free remaining */
for (int i = 3; i < result_count; i++) {
if (events[i]) cJSON_Delete(events[i]);
}
free(events);
}
cJSON_Delete(filter);
printf("\n");
if (ctx.got_eose) {
printf("✓ SUCCESS: EOSE received, WebSocket client works correctly\n");
return 0;
} else if (ctx.got_timeout) {
printf("✗ FAILURE: Timed out without EOSE — WebSocket client bug confirmed\n");
return 1;
} else {
printf("✗ FAILURE: No EOSE and no timeout — unexpected state\n");
return 2;
}
}