diff --git a/scripts/agent/validate-events.mjs b/scripts/agent/validate-events.mjs index d104e29d..f1c3430d 100644 --- a/scripts/agent/validate-events.mjs +++ b/scripts/agent/validate-events.mjs @@ -1,279 +1,389 @@ -/** - * Event Schema Validation Tool - * - * Scans the codebase for event builder usage and verifies that the output - * conforms to the defined schemas in `js/nostrEventSchemas.js`. - */ -import fs from 'node:fs'; -import path from 'node:path'; -import crypto from 'node:crypto'; -import { WebSocket } from 'ws'; +import { + NOTE_TYPES, + validateEventStructure, + buildVideoPostEvent, + buildHttpAuthEvent, + buildReportEvent, + buildVideoMirrorEvent, + buildRepostEvent, + buildShareEvent, + buildRelayListEvent, + buildDmRelayListEvent, + buildProfileMetadataEvent, + buildMuteListEvent, + buildDeletionEvent, + buildLegacyDirectMessageEvent, + buildDmAttachmentEvent, + buildDmReadReceiptEvent, + buildDmTypingIndicatorEvent, + buildViewEvent, + buildZapRequestEvent, + buildReactionEvent, + buildCommentEvent, + buildWatchHistoryEvent, + buildSubscriptionListEvent, + buildBlockListEvent, + buildHashtagPreferenceEvent, + buildAdminListEvent, +} from "../../js/nostrEventSchemas.js"; -// Polyfills -if (typeof globalThis.crypto === 'undefined') { - globalThis.crypto = crypto; -} -if (typeof globalThis.WebSocket === 'undefined') { - globalThis.WebSocket = WebSocket; -} -if (typeof globalThis.localStorage === 'undefined') { - globalThis.localStorage = { - getItem: () => null, - setItem: () => {}, - removeItem: () => {}, - clear: () => {} - }; -} -if (typeof globalThis.window === 'undefined') { - globalThis.window = globalThis; -} +const PUBKEY = "0000000000000000000000000000000000000000000000000000000000000001"; +const CREATED_AT = 1700000000; -// Helper to load module -const loadModule = async (filePath) => { - return import(path.resolve(process.cwd(), filePath)); -}; - -async function main() { - const schemasModule = await loadModule('js/nostrEventSchemas.js'); - const { - validateEventStructure, - NOTE_TYPES, - buildVideoPostEvent, - buildHttpAuthEvent, - buildReportEvent, - buildVideoMirrorEvent, - buildRepostEvent, - buildShareEvent, - buildRelayListEvent, - buildDmRelayListEvent, - buildProfileMetadataEvent, - buildMuteListEvent, - buildDeletionEvent, - buildLegacyDirectMessageEvent, - buildDmAttachmentEvent, - buildDmReadReceiptEvent, - buildDmTypingIndicatorEvent, - buildViewEvent, - buildZapRequestEvent, - buildReactionEvent, - buildCommentEvent, - buildWatchHistoryEvent, - buildSubscriptionListEvent, - buildBlockListEvent, - buildHashtagPreferenceEvent, - buildAdminListEvent - } = schemasModule; - - console.log("Starting Event Schema Validation..."); - - let failureCount = 0; - - const validate = (type, builder, params, label) => { - try { - const event = builder(params); - const { valid, errors } = validateEventStructure(type, event); - if (!valid) { - console.error(`[FAIL] ${label} (${type})`); - errors.forEach(err => console.error(` - ${err}`)); - console.error(` Event:`, JSON.stringify(event, null, 2)); - failureCount++; - } else { - console.log(`[PASS] ${label} (${type})`); - } - } catch (e) { - console.error(`[ERROR] Exception in builder for ${label} (${type})`); - console.error(e); - failureCount++; - } - }; - - const pubkey = "0000000000000000000000000000000000000000000000000000000000000001"; - const created_at = Math.floor(Date.now() / 1000); - - // Test Cases - validate(NOTE_TYPES.VIDEO_POST, buildVideoPostEvent, { - pubkey, - created_at, - dTagValue: "test-video-id", - content: { +const TESTS = [ + { + name: "Video Post", + builder: buildVideoPostEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + dTagValue: "test-video-id", + content: { version: 3, title: "Test Video", - videoRootId: "root-id", - } - }, "Basic Video Post"); - - validate(NOTE_TYPES.HTTP_AUTH, buildHttpAuthEvent, { - pubkey, - created_at, - url: "https://example.com/login", - method: "POST" - }, "HTTP Auth"); - - validate(NOTE_TYPES.REPORT, buildReportEvent, { - pubkey, - created_at, + videoRootId: "test-video-id", + infoHash: "0123456789abcdef0123456789abcdef01234567", + }, + }, + type: NOTE_TYPES.VIDEO_POST, + }, + { + name: "Video Post (URL)", + builder: buildVideoPostEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + dTagValue: "test-video-url", + content: { + version: 3, + title: "Test Video URL", + videoRootId: "test-video-url", + url: "https://example.com/video.mp4", + }, + }, + type: NOTE_TYPES.VIDEO_POST, + }, + { + name: "HTTP Auth", + builder: buildHttpAuthEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + url: "https://example.com/auth", + method: "GET", + payload: "hash", + }, + type: NOTE_TYPES.HTTP_AUTH, + }, + { + name: "Report", + builder: buildReportEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, eventId: "e".repeat(64), - reportType: "spam" - }, "Report"); - - validate(NOTE_TYPES.VIDEO_MIRROR, buildVideoMirrorEvent, { - pubkey, - created_at, - content: "mirror content" - }, "Video Mirror"); - - validate(NOTE_TYPES.REPOST, buildRepostEvent, { - pubkey, - created_at, + reportType: "nudity", + }, + type: NOTE_TYPES.REPORT, + }, + { + name: "Video Mirror", + builder: buildVideoMirrorEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + tags: [["url", "https://example.com/video.mp4"], ["m", "video/mp4"]], + content: "Mirroring video", + }, + type: NOTE_TYPES.VIDEO_MIRROR, + }, + { + name: "Repost", + builder: buildRepostEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, eventId: "e".repeat(64), - targetKind: 1 // Text Note Repost (Kind 6) - }, "Repost"); - - validate(NOTE_TYPES.GENERIC_REPOST, buildRepostEvent, { - pubkey, - created_at, + eventRelay: "wss://relay.example.com", + }, + type: NOTE_TYPES.REPOST, + }, + { + name: "Generic Repost (Video)", + builder: buildRepostEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + targetKind: 30078, eventId: "e".repeat(64), - targetKind: 30078 // Video Post Repost (Kind 16) - }, "Generic Repost"); - - validate(NOTE_TYPES.SHARE, buildShareEvent, { - pubkey, - created_at, + }, + type: NOTE_TYPES.GENERIC_REPOST, + }, + { + name: "Share", + builder: buildShareEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + video: { id: "e".repeat(64), pubkey: PUBKEY }, + relays: ["wss://relay.example.com"], content: "Check this out", - video: { id: "e".repeat(64), pubkey: "p".repeat(64) } - }, "Share"); - - validate(NOTE_TYPES.RELAY_LIST, buildRelayListEvent, { - pubkey, - created_at, - relays: ["wss://relay.example.com"] - }, "Relay List"); - - validate(NOTE_TYPES.DM_RELAY_LIST, buildDmRelayListEvent, { - pubkey, - created_at, - relays: ["wss://dm.example.com"] - }, "DM Relay List"); - - validate(NOTE_TYPES.PROFILE_METADATA, buildProfileMetadataEvent, { - pubkey, - created_at, - metadata: { name: "Test User" } - }, "Profile Metadata"); - - validate(NOTE_TYPES.MUTE_LIST, buildMuteListEvent, { - pubkey, - created_at, - pTags: ["p".repeat(64)] - }, "Mute List"); - - validate(NOTE_TYPES.DELETION, buildDeletionEvent, { - pubkey, - created_at, - eventIds: ["e".repeat(64)] - }, "Deletion"); - - validate(NOTE_TYPES.LEGACY_DM, buildLegacyDirectMessageEvent, { - pubkey, - created_at, - recipientPubkey: "p".repeat(64), - ciphertext: "encrypted" - }, "Legacy DM"); - - validate(NOTE_TYPES.DM_ATTACHMENT, buildDmAttachmentEvent, { - pubkey, - created_at, - recipientPubkey: "p".repeat(64), - attachment: { url: "https://example.com/file.jpg", x: "hash" } - }, "DM Attachment"); - - validate(NOTE_TYPES.DM_READ_RECEIPT, buildDmReadReceiptEvent, { - pubkey, - created_at, - recipientPubkey: "p".repeat(64), - eventId: "e".repeat(64) - }, "DM Read Receipt"); - - validate(NOTE_TYPES.DM_TYPING, buildDmTypingIndicatorEvent, { - pubkey, - created_at, - recipientPubkey: "p".repeat(64) - }, "DM Typing Indicator"); - - validate(NOTE_TYPES.VIEW_EVENT, buildViewEvent, { - pubkey, - created_at, - pointerTag: ["e", "eventid", "relay"] - }, "View Event"); - - validate(NOTE_TYPES.ZAP_REQUEST, buildZapRequestEvent, { - pubkey, - created_at, - recipientPubkey: "p".repeat(64), + }, + type: NOTE_TYPES.SHARE, + }, + { + name: "Relay List", + builder: buildRelayListEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + relays: [{ url: "wss://relay.example.com", read: true, write: true }], + }, + type: NOTE_TYPES.RELAY_LIST, + }, + { + name: "DM Relay List", + builder: buildDmRelayListEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + relays: ["wss://relay.example.com"], + }, + type: NOTE_TYPES.DM_RELAY_LIST, + }, + { + name: "Profile Metadata", + builder: buildProfileMetadataEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + metadata: { name: "Test User", about: "Testing" }, + }, + type: NOTE_TYPES.PROFILE_METADATA, + }, + { + name: "Mute List", + builder: buildMuteListEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + pTags: [PUBKEY], + }, + type: NOTE_TYPES.MUTE_LIST, + }, + { + name: "Deletion", + builder: buildDeletionEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + eventIds: ["e".repeat(64)], + reason: "mistake", + }, + type: NOTE_TYPES.DELETION, + }, + { + name: "Legacy DM", + builder: buildLegacyDirectMessageEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + recipientPubkey: PUBKEY, + ciphertext: "encrypted", + }, + type: NOTE_TYPES.LEGACY_DM, + }, + { + name: "DM Attachment", + builder: buildDmAttachmentEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + recipientPubkey: PUBKEY, + attachment: { url: "https://example.com/file", x: "hash", type: "image/jpeg" }, + }, + type: NOTE_TYPES.DM_ATTACHMENT, + }, + { + name: "DM Read Receipt", + builder: buildDmReadReceiptEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + recipientPubkey: PUBKEY, + eventId: "e".repeat(64), + }, + type: NOTE_TYPES.DM_READ_RECEIPT, + }, + { + name: "DM Typing Indicator", + builder: buildDmTypingIndicatorEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + recipientPubkey: PUBKEY, + expiresAt: CREATED_AT + 60, + }, + type: NOTE_TYPES.DM_TYPING, + }, + { + name: "View Event", + builder: buildViewEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + pointerValue: "e".repeat(64), + pointerTag: ["e", "e".repeat(64)], + }, + type: NOTE_TYPES.VIEW_EVENT, + }, + { + name: "Zap Request", + builder: buildZapRequestEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + recipientPubkey: PUBKEY, amountSats: 100, - lnurl: "lnurl1...", - relays: ["wss://relay.damus.io"] - }, "Zap Request"); - - validate(NOTE_TYPES.VIDEO_REACTION, buildReactionEvent, { - pubkey, - created_at, + relays: ["wss://relay.example.com"], + }, + type: NOTE_TYPES.ZAP_REQUEST, + }, + { + name: "Video Reaction", + builder: buildReactionEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + pointerValue: "e".repeat(64), + targetPointer: { type: "e", value: "e".repeat(64) }, content: "+", - targetPointer: { type: "e", value: "e".repeat(64) } - }, "Reaction"); + }, + type: NOTE_TYPES.VIDEO_REACTION, + }, + { + name: "Video Comment", + builder: buildCommentEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + videoEventId: "e".repeat(64), + content: "Nice video", + }, + type: NOTE_TYPES.VIDEO_COMMENT, + }, + { + name: "Watch History", + builder: buildWatchHistoryEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + monthIdentifier: "2023-01", + content: { version: 2, month: "2023-01", items: [] }, + }, + type: NOTE_TYPES.WATCH_HISTORY, + }, + { + name: "Subscription List", + builder: buildSubscriptionListEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + content: "encrypted-json", + encryption: "nip44_v2", + }, + type: NOTE_TYPES.SUBSCRIPTION_LIST, + }, + { + name: "Block List", + builder: buildBlockListEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + content: "encrypted-json", + encryption: "nip44_v2", + }, + type: NOTE_TYPES.USER_BLOCK_LIST, + }, + { + name: "Hashtag Preferences", + builder: buildHashtagPreferenceEvent, + params: { + pubkey: PUBKEY, + created_at: CREATED_AT, + content: "encrypted-json", + }, + type: NOTE_TYPES.HASHTAG_PREFERENCES, + }, + { + name: "Admin Moderation List", + builder: buildAdminListEvent, + // buildAdminListEvent takes (listKey, params) + args: ["moderation", { + pubkey: PUBKEY, + created_at: CREATED_AT, + hexPubkeys: [PUBKEY], + }], + type: NOTE_TYPES.ADMIN_MODERATION_LIST, + }, + { + name: "Admin Blacklist", + builder: buildAdminListEvent, + args: ["blacklist", { + pubkey: PUBKEY, + created_at: CREATED_AT, + hexPubkeys: [PUBKEY], + }], + type: NOTE_TYPES.ADMIN_BLACKLIST, + }, + { + name: "Admin Whitelist", + builder: buildAdminListEvent, + args: ["whitelist", { + pubkey: PUBKEY, + created_at: CREATED_AT, + hexPubkeys: [PUBKEY], + }], + type: NOTE_TYPES.ADMIN_WHITELIST, + }, +]; - validate(NOTE_TYPES.VIDEO_COMMENT, buildCommentEvent, { - pubkey, - created_at, - content: "Great video!", - videoEventId: "e".repeat(64) - }, "Comment"); +async function run() { + console.log("Running Event Schema Validation..."); + let failureCount = 0; - validate(NOTE_TYPES.WATCH_HISTORY, buildWatchHistoryEvent, { - pubkey, - created_at, - content: {}, - monthIdentifier: "2023-10" // Required for valid event - }, "Watch History"); + for (const test of TESTS) { + try { + let event; + if (test.args) { + event = test.builder(...test.args); + } else { + event = test.builder(test.params); + } - validate(NOTE_TYPES.SUBSCRIPTION_LIST, buildSubscriptionListEvent, { - pubkey, - created_at, - content: "encrypted_subs" - }, "Subscription List"); - - validate(NOTE_TYPES.USER_BLOCK_LIST, buildBlockListEvent, { - pubkey, - created_at, - content: "encrypted_blocks" - }, "Block List"); - - validate(NOTE_TYPES.HASHTAG_PREFERENCES, buildHashtagPreferenceEvent, { - pubkey, - created_at, - content: "encrypted_prefs" - }, "Hashtag Preferences"); - - // Admin Lists - const adminBuilder = (params) => buildAdminListEvent("moderation", params); - validate(NOTE_TYPES.ADMIN_MODERATION_LIST, adminBuilder, { - pubkey, - created_at, - hexPubkeys: ["p".repeat(64)] - }, "Admin Moderation List"); + const { valid, errors } = validateEventStructure(test.type, event); + if (valid) { + console.log(`[PASS] ${test.name}`); + } else { + console.error(`[FAIL] ${test.name}`); + errors.forEach((err) => console.error(` - ${err}`)); + failureCount++; + } + } catch (e) { + console.error(`[ERROR] ${test.name} threw an exception:`, e); + failureCount++; + } + } if (failureCount > 0) { - console.log(`\nValidation complete with ${failureCount} failures.`); - process.exit(1); + console.error(`\nValidation failed with ${failureCount} errors.`); + process.exit(1); } else { - console.log("\nAll checks passed!"); - process.exit(0); + console.log("\nAll events validated successfully."); + process.exit(0); } } -main().catch(err => { - console.error("Fatal error:", err); - process.exit(1); -}); +run();