Files
nostr_core_lib/tests/nip34_test.c
T

641 lines
20 KiB
C

/*
* NIP-34 Git Stuff Test Suite
* Tests repository announcement, state, patch, PR, issue, and status event creation
* Following TESTS POLICY: Shows expected vs actual values, prints entire JSON events
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../nostr_core/nip034.h"
#include "../nostr_core/nip001.h"
#include "../nostr_core/nostr_common.h"
#include "../nostr_core/utils.h"
#include "../cjson/cJSON.h"
// Test private key (well-known test key, DO NOT use in production)
static const unsigned char test_private_key[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
static int test_count = 0;
static int passed_tests = 0;
void print_test_header(const char* test_name) {
test_count++;
printf("\n=== TEST %d: %s ===\n", test_count, test_name);
}
void print_test_result(int passed, const char* test_name) {
if (passed) {
passed_tests++;
printf("✅ PASS: %s\n", test_name);
} else {
printf("❌ FAIL: %s\n", test_name);
}
}
void print_json_event(cJSON* event, const char* label) {
if (!event) {
printf("%s: NULL\n", label);
return;
}
char* json = cJSON_Print(event);
printf("%s:\n%s\n", label, json ? json : "NULL");
if (json) free(json);
}
// Test 1: Create a repository announcement event
int test_repo_announcement(void) {
print_test_header("Repository Announcement (kind 30617)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* clone_urls[] = {
"https://laantungir.net/grasp/npub1test/c-relay.git",
"https://gitgrasp.com/npub1test/c-relay.git",
NULL
};
const char* relay_urls[] = {
"wss://relay.laantungir.net",
"wss://relay.damus.io",
NULL
};
const char* maintainers[] = {
"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4",
NULL
};
cJSON* event = nostr_nip34_create_repo_announcement(
signer,
"c-relay",
"c-relay",
"A blazingly fast, production-ready Nostr relay",
"https://git.laantungir.net/laantungir/c-relay",
clone_urls, 2,
relay_urls, 2,
"abc123def456abc123def456abc123def456abc123def456abc123def456abc123",
maintainers, 1
);
if (!event) {
printf("❌ Failed to create repo announcement event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Repository Announcement Event");
// Validate the event
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0 = NOSTR_SUCCESS)\n", rc);
// Check kind
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 30617)\n", kind_val);
// Check tags
cJSON* tags = cJSON_GetObjectItem(event, "tags");
int tag_count = tags ? cJSON_GetArraySize(tags) : 0;
printf("Tag count: %d (expected >= 6)\n", tag_count);
// Verify clone tag is a single multi-value tag: ["clone", url1, url2]
int clone_tag_count = 0;
int clone_values_in_first = 0;
// Verify relays tag is a single multi-value tag: ["relays", url1, url2]
int relays_tag_count = 0;
int relays_values_in_first = 0;
if (tags) {
int n = cJSON_GetArraySize(tags);
for (int i = 0; i < n; i++) {
cJSON* tag = cJSON_GetArrayItem(tags, i);
if (tag && cJSON_IsArray(tag)) {
cJSON* name = cJSON_GetArrayItem(tag, 0);
const char* name_str = cJSON_GetStringValue(name);
if (name_str) {
if (strcmp(name_str, "clone") == 0) {
clone_tag_count++;
if (clone_tag_count == 1)
clone_values_in_first = cJSON_GetArraySize(tag) - 1;
} else if (strcmp(name_str, "relays") == 0) {
relays_tag_count++;
if (relays_tag_count == 1)
relays_values_in_first = cJSON_GetArraySize(tag) - 1;
}
}
}
}
}
printf("Clone tags: %d (expected 1), values in first: %d (expected 2)\n",
clone_tag_count, clone_values_in_first);
printf("Relays tags: %d (expected 1), values in first: %d (expected 2)\n",
relays_tag_count, relays_values_in_first);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 30617 && tag_count >= 6 &&
clone_tag_count == 1 && clone_values_in_first == 2 &&
relays_tag_count == 1 && relays_values_in_first == 2);
print_test_result(passed, "Repository Announcement");
return passed;
}
// Test 2: Create a repository state event
int test_repo_state(void) {
print_test_header("Repository State (kind 30618)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* ref_names[] = {
"refs/heads/master",
"refs/heads/develop",
"refs/tags/v1.0.0",
"HEAD",
NULL
};
const char* ref_values[] = {
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
"f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1",
"0011223344550011223344550011223344550011223344550011223344550011",
"ref: refs/heads/master",
NULL
};
cJSON* event = nostr_nip34_create_repo_state(
signer,
"c-relay",
ref_names, ref_values, 4
);
if (!event) {
printf("❌ Failed to create repo state event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Repository State Event");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 30618)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 30618);
print_test_result(passed, "Repository State");
return passed;
}
// Test 3: Create a patch event
int test_patch(void) {
print_test_header("Patch (kind 1617)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* patch_content =
"From 1234567890abcdef1234567890abcdef12345678 Mon Sep 17 00:00:00 2024\n"
"From: Test User <test@example.com>\n"
"Date: Mon, 1 Jan 2024 12:00:00 +0000\n"
"Subject: [PATCH] Add new feature\n"
"\n"
"This is a test patch.\n"
"---\n"
" src/main.c | 2 ++\n"
" 1 file changed, 2 insertions(+)\n"
"\n"
"diff --git a/src/main.c b/src/main.c\n"
"index abc123..def456 100644\n"
"--- a/src/main.c\n"
"+++ b/src/main.c\n"
"@@ -1,3 +1,5 @@\n"
" #include <stdio.h>\n"
"+\n"
"+// New feature\n"
" int main(void) {\n"
" return 0;\n"
" }\n";
cJSON* event = nostr_nip34_create_patch(
signer,
"30617:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:c-relay",
"abc123def456abc123def456abc123def456abc123def456abc123def456abc123",
patch_content,
"1234567890abcdef1234567890abcdef12345678",
"abcdef1234567890abcdef1234567890abcdef12",
"-----BEGIN PGP SIGNATURE-----\n\niQEzBAABCAAdFiEE...\n-----END PGP SIGNATURE-----",
"Test User",
"test@example.com",
"1704110400",
0,
1, // is_root
1 // is_root_revision
);
if (!event) {
printf("❌ Failed to create patch event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Patch Event");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 1617)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 1617);
print_test_result(passed, "Patch");
return passed;
}
// Test 4: Create a pull request event
int test_pull_request(void) {
print_test_header("Pull Request (kind 1618)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* clone_urls[] = {
"https://github.com/user/c-relay.git",
NULL
};
cJSON* event = nostr_nip34_create_pull_request(
signer,
"30617:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:c-relay",
"abc123def456abc123def456abc123def456abc123def456abc123def456abc123",
"Fix critical bug in relay connection handling",
"This PR fixes a race condition in the relay connection code.\n\nChanges:\n- Added mutex locking\n- Fixed timeout handling",
"fedcba9876543210fedcba9876543210fedcba98",
clone_urls, 1,
"fix-race-condition",
NULL, // no root patch event
"abcdef1234567890abcdef1234567890abcdef12" // merge base
);
if (!event) {
printf("❌ Failed to create pull request event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Pull Request Event");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 1618)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 1618);
print_test_result(passed, "Pull Request");
return passed;
}
// Test 5: Create an issue event
int test_issue(void) {
print_test_header("Issue (kind 1621)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* labels[] = {
"bug",
"high-priority",
NULL
};
cJSON* event = nostr_nip34_create_issue(
signer,
"30617:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:c-relay",
"Connection timeout on startup",
"When starting the relay with a large database, the connection times out after 30 seconds.\n\nSteps to reproduce:\n1. Start relay with 1M events\n2. Try to connect\n3. Observe timeout",
labels, 2
);
if (!event) {
printf("❌ Failed to create issue event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Issue Event");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 1621)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 1621);
print_test_result(passed, "Issue");
return passed;
}
// Test 6: Create a status event
int test_status(void) {
print_test_header("Status (kind 1631 - Merged)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
cJSON* event = nostr_nip34_create_status(
signer,
NOSTR_NIP34_KIND_STATUS_MERGED,
"abc123def456abc123def456abc123def456abc123def456abc123def456abc123",
"30617:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:c-relay",
"abc123def456abc123def456abc123def456abc123def456abc123def456abc123",
"Patch has been merged to master"
);
if (!event) {
printf("❌ Failed to create status event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Status Event (Merged)");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 1631)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 1631);
print_test_result(passed, "Status");
return passed;
}
// Test 7: Create a grasp list event
int test_grasp_list(void) {
print_test_header("Grasp List (kind 10317)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* grasp_urls[] = {
"wss://grasp.laantungir.net",
"wss://gitgrasp.com",
NULL
};
cJSON* event = nostr_nip34_create_grasp_list(
signer,
grasp_urls, 2
);
if (!event) {
printf("❌ Failed to create grasp list event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "Grasp List Event");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 10317)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 10317);
print_test_result(passed, "Grasp List");
return passed;
}
// Test 8: Parse nostr:// URLs
int test_parse_nostr_url(void) {
print_test_header("Parse nostr:// URLs");
int all_passed = 1;
// Test 1: nostr://<npub>/<identifier>
{
char npub[256], ident[256], relay[512];
int rc = nostr_nip34_parse_nostr_url(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/c-relay",
npub, ident, relay
);
printf("Test 1 - npub/identifier:\n");
printf(" Result: %d (expected 0)\n", rc);
printf(" npub: %s\n", npub);
printf(" ident: %s\n", ident);
printf(" relay: %s\n", relay);
if (rc != 0 || strlen(npub) == 0 || strlen(ident) == 0) {
printf(" ❌ FAIL\n");
all_passed = 0;
} else {
printf(" ✅ PASS\n");
}
}
// Test 2: nostr://<npub>/<relay>/<identifier>
{
char npub[256], ident[256], relay[512];
int rc = nostr_nip34_parse_nostr_url(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/relay.ngit.dev/ngit",
npub, ident, relay
);
printf("Test 2 - npub/relay/identifier:\n");
printf(" Result: %d (expected 0)\n", rc);
printf(" npub: %s\n", npub);
printf(" relay: %s\n", relay);
printf(" ident: %s\n", ident);
if (rc != 0 || strlen(npub) == 0 || strlen(relay) == 0 || strlen(ident) == 0) {
printf(" ❌ FAIL\n");
all_passed = 0;
} else {
printf(" ✅ PASS\n");
}
}
// Test 3: nostr://<nip05>/<identifier>
{
char npub[256], ident[256], relay[512];
int rc = nostr_nip34_parse_nostr_url(
"nostr://danconwaydev.com/ws%3A%2F%2Flocalhost%3A7334/my-local-only-repo",
npub, ident, relay
);
printf("Test 3 - nip05/relay/identifier (percent-encoded):\n");
printf(" Result: %d (expected 0)\n", rc);
printf(" npub: %s\n", npub);
printf(" relay: %s\n", relay);
printf(" ident: %s\n", ident);
if (rc != 0 || strlen(npub) == 0 || strlen(relay) == 0 || strcmp(ident, "my-local-only-repo") != 0) {
printf(" ❌ FAIL\n");
all_passed = 0;
} else {
printf(" ✅ PASS\n");
}
}
// Test 4: Invalid URL (no nostr:// prefix)
{
char npub[256], ident[256], relay[512];
int rc = nostr_nip34_parse_nostr_url(
"https://example.com/repo.git",
npub, ident, relay
);
printf("Test 4 - invalid URL (https):\n");
printf(" Result: %d (expected -1)\n", rc);
if (rc != -1) {
printf(" ❌ FAIL\n");
all_passed = 0;
} else {
printf(" ✅ PASS\n");
}
}
print_test_result(all_passed, "Parse nostr:// URLs");
return all_passed;
}
// Test 9: Create a PR update event
int test_pr_update(void) {
print_test_header("PR Update (kind 1619)");
nostr_signer_t* signer = nostr_signer_local(test_private_key);
if (!signer) {
printf("❌ Failed to create signer\n");
return 0;
}
const char* clone_urls[] = {
"https://github.com/user/c-relay.git",
NULL
};
cJSON* event = nostr_nip34_create_pr_update(
signer,
"30617:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:c-relay",
"abc123def456abc123def456abc123def456abc123def456abc123def456abc123",
"pr_event_id_1234567890abcdef",
"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4",
"new_tip_commit_hash_1234567890abcdef",
clone_urls, 1,
"updated_merge_base_hash_1234567890"
);
if (!event) {
printf("❌ Failed to create PR update event\n");
nostr_signer_free(signer);
return 0;
}
print_json_event(event, "PR Update Event");
int rc = nostr_validate_event(event);
printf("Validation result: %d (expected 0)\n", rc);
cJSON* kind = cJSON_GetObjectItem(event, "kind");
int kind_val = kind ? (int)cJSON_GetNumberValue(kind) : -1;
printf("Kind: %d (expected 1619)\n", kind_val);
cJSON_Delete(event);
nostr_signer_free(signer);
int passed = (rc == NOSTR_SUCCESS && kind_val == 1619);
print_test_result(passed, "PR Update");
return passed;
}
int main(void) {
printf("========================================\n");
printf(" NIP-34 Git Stuff Test Suite\n");
printf("========================================\n");
// Initialize crypto
if (nostr_crypto_init() != 0) {
printf("❌ Failed to initialize crypto\n");
return 1;
}
// Run all tests
int results[9];
results[0] = test_repo_announcement();
results[1] = test_repo_state();
results[2] = test_patch();
results[3] = test_pull_request();
results[4] = test_issue();
results[5] = test_status();
results[6] = test_grasp_list();
results[7] = test_parse_nostr_url();
results[8] = test_pr_update();
// Summary
printf("\n========================================\n");
printf(" TEST SUMMARY\n");
printf("========================================\n");
printf("Total tests: %d\n", test_count);
printf("Passed: %d\n", passed_tests);
printf("Failed: %d\n", test_count - passed_tests);
nostr_crypto_cleanup();
if (passed_tests == test_count) {
printf("🎉 ALL TESTS PASSED! NIP-34 implementation is working correctly.\n");
return 0;
} else {
printf("❌ SOME TESTS FAILED!\n");
return 1;
}
}