mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(exploreData): trigger refresh immediately on visibility change
- `handleVisibility` only scheduled intervals but didn't trigger an immediate refresh when returning to the tab. - This caused `tests/unit/services/exploreDataService.test.mjs` to fail (assertion `0 !== 1`). - Added explicit calls to `refreshWatchHistoryTagCounts` and `refreshTagIdf` when `document.hidden` becomes false. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
aa920b5a7a
commit
106f7640c4
128 files changed
+5817
-7552
No files matched your search
@@ -38,8 +38,8 @@ def run():
|
||||
page.on("requestfailed", on_request_failed)
|
||||
|
||||
try:
|
||||
log("INFO", "Navigating to http://localhost:3000...")
|
||||
page.goto("http://localhost:3000")
|
||||
log("INFO", "Navigating to http://localhost:8000...")
|
||||
page.goto("http://localhost:8000")
|
||||
|
||||
# Wait for load state to ensure initial resources are loaded
|
||||
try:
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import {
|
||||
randomInt,
|
||||
randomString,
|
||||
runFuzzer,
|
||||
} from "./fuzz-utils.mjs";
|
||||
import { validateEventStructure, NOTE_TYPES } from "../../js/nostrEventSchemas.js";
|
||||
|
||||
function generateAggressiveJSON(depth = 0) {
|
||||
if (depth > 50) return "deep"; // Stop before stack overflow unless we want to test that limit
|
||||
|
||||
const choice = randomInt(0, 10);
|
||||
|
||||
if (choice < 2) {
|
||||
// Nested Object
|
||||
const obj = {};
|
||||
const key = Math.random() < 0.1 ? "__proto__" : (Math.random() < 0.1 ? "constructor" : randomString(5));
|
||||
obj[key] = generateAggressiveJSON(depth + 1);
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (choice < 4) {
|
||||
// Nested Array
|
||||
return [generateAggressiveJSON(depth + 1)];
|
||||
}
|
||||
|
||||
if (choice < 5) {
|
||||
// Huge String
|
||||
return randomString(10000);
|
||||
}
|
||||
|
||||
if (choice < 6) {
|
||||
// Unicode Chaos
|
||||
return "\uD800\uDC00" + randomString(100, true) + "\uFFFF";
|
||||
}
|
||||
|
||||
if (choice < 7) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (choice < 8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 12345;
|
||||
}
|
||||
|
||||
function genAggressiveEvent() {
|
||||
const types = Object.values(NOTE_TYPES);
|
||||
const type = types[randomInt(0, types.length - 1)];
|
||||
|
||||
// Malformed structure
|
||||
if (Math.random() < 0.1) return [type, null];
|
||||
if (Math.random() < 0.1) return [type, []];
|
||||
if (Math.random() < 0.1) return [type, "string"];
|
||||
|
||||
const event = {
|
||||
kind: randomInt(0, 50000),
|
||||
tags: [],
|
||||
content: ""
|
||||
};
|
||||
|
||||
// Malformed tags
|
||||
if (Math.random() < 0.5) {
|
||||
const numTags = randomInt(0, 100);
|
||||
for(let i=0; i<numTags; i++) {
|
||||
if (Math.random() < 0.1) event.tags.push(null);
|
||||
else if (Math.random() < 0.1) event.tags.push("string");
|
||||
else event.tags.push([randomString(5), randomString(20)]);
|
||||
}
|
||||
}
|
||||
|
||||
// Aggressive Content
|
||||
try {
|
||||
const aggressive = generateAggressiveJSON();
|
||||
event.content = JSON.stringify(aggressive);
|
||||
} catch (e) {
|
||||
event.content = "{}";
|
||||
}
|
||||
|
||||
// Sometimes unparsable JSON
|
||||
if (Math.random() < 0.2) {
|
||||
event.content = "{ unparsable: true, " + randomString(50);
|
||||
}
|
||||
|
||||
return [type, event];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await runFuzzer(
|
||||
"validateEventStructure-Aggressive",
|
||||
validateEventStructure,
|
||||
genAggressiveEvent,
|
||||
5000 // 5000 iterations
|
||||
);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
+304
-407
@@ -1,422 +1,319 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import * as schemas from '../../js/nostrEventSchemas.js';
|
||||
import { buildNip71VideoEvent } from '../../js/nostr/nip71.js';
|
||||
import {
|
||||
getNostrEventSchema,
|
||||
validateEventStructure,
|
||||
buildVideoPostEvent,
|
||||
buildHttpAuthEvent,
|
||||
buildReportEvent,
|
||||
buildGiftWrapEvent,
|
||||
buildSealEvent,
|
||||
buildChatMessageEvent,
|
||||
buildVideoMirrorEvent,
|
||||
buildRepostEvent,
|
||||
buildShareEvent,
|
||||
buildRelayListEvent,
|
||||
buildDmRelayListEvent,
|
||||
buildProfileMetadataEvent,
|
||||
buildMuteListEvent,
|
||||
buildDeletionEvent,
|
||||
buildLegacyDirectMessageEvent,
|
||||
buildDmAttachmentEvent,
|
||||
buildDmReadReceiptEvent,
|
||||
buildDmTypingIndicatorEvent,
|
||||
buildViewEvent,
|
||||
buildZapRequestEvent,
|
||||
buildReactionEvent,
|
||||
buildCommentEvent,
|
||||
buildWatchHistoryEvent,
|
||||
buildSubscriptionListEvent,
|
||||
buildBlockListEvent,
|
||||
buildHashtagPreferenceEvent,
|
||||
buildAdminListEvent,
|
||||
NOTE_TYPES,
|
||||
} from "../../js/nostrEventSchemas.js";
|
||||
import { buildNip71VideoEvent } from "../../js/nostr/nip71.js";
|
||||
import fs from "fs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const REPORT_FILE = "artifacts/validate-events.json";
|
||||
|
||||
// Mock window for compatibility
|
||||
if (typeof window === 'undefined') {
|
||||
global.window = {};
|
||||
}
|
||||
// Valid 32-byte hex pubkey for testing
|
||||
const TEST_PUBKEY = "0000000000000000000000000000000000000000000000000000000000000001";
|
||||
const TEST_EVENT_ID = "0000000000000000000000000000000000000000000000000000000000000002";
|
||||
|
||||
const results = [];
|
||||
function runValidation() {
|
||||
const results = [];
|
||||
const failures = [];
|
||||
|
||||
const builders = {
|
||||
...schemas,
|
||||
buildNip71VideoEvent
|
||||
};
|
||||
const runTest = (name, builder, input, expectedType) => {
|
||||
try {
|
||||
const event = builder(input);
|
||||
const validation = validateEventStructure(expectedType, event);
|
||||
|
||||
function runTest(builderName, testName, args, expectedValid = true) {
|
||||
try {
|
||||
const builder = builders[builderName];
|
||||
if (typeof builder !== 'function') {
|
||||
throw new Error(`Builder ${builderName} not found`);
|
||||
const result = {
|
||||
builder: name,
|
||||
input: input,
|
||||
event: event,
|
||||
validation: {
|
||||
status: validation.valid ? "PASS" : "FAIL",
|
||||
failures: validation.errors.map(msg => ({ message: msg }))
|
||||
}
|
||||
};
|
||||
|
||||
results.push(result);
|
||||
|
||||
if (!validation.valid) {
|
||||
failures.push(result);
|
||||
console.error(`❌ ${name} FAILED:`);
|
||||
validation.errors.forEach(e => console.error(` - ${e}`));
|
||||
} else {
|
||||
console.log(`✅ ${name} PASSED`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`❌ ${name} CRASHED:`, e);
|
||||
failures.push({
|
||||
builder: name,
|
||||
input: input,
|
||||
error: e.message,
|
||||
validation: { status: "CRASH", failures: [{ message: e.message }] }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handle arguments: if args is an array, apply it. If it's an object, assume single argument (params).
|
||||
// Exception: buildAdminListEvent takes (listKey, params).
|
||||
let event;
|
||||
if (Array.isArray(args)) {
|
||||
event = builder(...args);
|
||||
} else {
|
||||
event = builder(args);
|
||||
console.log("Starting event schema validation...");
|
||||
|
||||
// 1. Video Post
|
||||
runTest("buildVideoPostEvent", buildVideoPostEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
dTagValue: "video-id",
|
||||
content: {
|
||||
version: 3,
|
||||
title: "My Video",
|
||||
videoRootId: "video-id",
|
||||
url: "https://example.com/video.mp4"
|
||||
}
|
||||
}, NOTE_TYPES.VIDEO_POST);
|
||||
|
||||
if (!event) {
|
||||
throw new Error(`Builder ${builderName} returned null/undefined`);
|
||||
// 2. HTTP Auth
|
||||
runTest("buildHttpAuthEvent", buildHttpAuthEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
url: "https://example.com/login",
|
||||
method: "GET"
|
||||
}, NOTE_TYPES.HTTP_AUTH);
|
||||
|
||||
// 3. Report
|
||||
runTest("buildReportEvent", buildReportEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
eventId: TEST_EVENT_ID,
|
||||
reportType: "nudity"
|
||||
}, NOTE_TYPES.REPORT);
|
||||
|
||||
// 4. Gift Wrap
|
||||
runTest("buildGiftWrapEvent", buildGiftWrapEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
ciphertext: "encrypted-content"
|
||||
}, NOTE_TYPES.GIFT_WRAP);
|
||||
|
||||
// 5. Seal
|
||||
runTest("buildSealEvent", buildSealEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
ciphertext: "encrypted-rumor"
|
||||
}, NOTE_TYPES.SEAL);
|
||||
|
||||
// 6. Chat Message
|
||||
runTest("buildChatMessageEvent", buildChatMessageEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
content: "Hello world"
|
||||
}, NOTE_TYPES.CHAT_MESSAGE);
|
||||
|
||||
// 7. Video Mirror
|
||||
runTest("buildVideoMirrorEvent", buildVideoMirrorEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
content: "https://example.com/video.mp4"
|
||||
}, NOTE_TYPES.VIDEO_MIRROR);
|
||||
|
||||
// 8. Repost
|
||||
runTest("buildRepostEvent", buildRepostEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
eventId: TEST_EVENT_ID,
|
||||
eventRelay: "wss://relay.example.com"
|
||||
}, NOTE_TYPES.REPOST);
|
||||
|
||||
// 9. Share
|
||||
runTest("buildShareEvent", buildShareEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
content: "Check this out!",
|
||||
video: { id: "video-id", pubkey: TEST_PUBKEY }
|
||||
}, NOTE_TYPES.SHARE);
|
||||
|
||||
// 10. Relay List
|
||||
runTest("buildRelayListEvent", buildRelayListEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
relays: ["wss://relay1.com", { url: "wss://relay2.com", mode: "read" }]
|
||||
}, NOTE_TYPES.RELAY_LIST);
|
||||
|
||||
// 11. DM Relay List
|
||||
runTest("buildDmRelayListEvent", buildDmRelayListEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
relays: ["wss://dm.relay.com"]
|
||||
}, NOTE_TYPES.DM_RELAY_LIST);
|
||||
|
||||
// 12. Profile Metadata
|
||||
runTest("buildProfileMetadataEvent", buildProfileMetadataEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
metadata: { name: "Alice", about: "Bob" }
|
||||
}, NOTE_TYPES.PROFILE_METADATA);
|
||||
|
||||
// 13. Mute List
|
||||
runTest("buildMuteListEvent", buildMuteListEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
pTags: [TEST_PUBKEY]
|
||||
}, NOTE_TYPES.MUTE_LIST);
|
||||
|
||||
// 14. Deletion
|
||||
runTest("buildDeletionEvent", buildDeletionEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
eventIds: [TEST_EVENT_ID],
|
||||
reason: "spam"
|
||||
}, NOTE_TYPES.DELETION);
|
||||
|
||||
// 15. Legacy DM
|
||||
runTest("buildLegacyDirectMessageEvent", buildLegacyDirectMessageEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
ciphertext: "secret message"
|
||||
}, NOTE_TYPES.LEGACY_DM);
|
||||
|
||||
// 16. DM Attachment
|
||||
runTest("buildDmAttachmentEvent", buildDmAttachmentEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
attachment: { x: "hash", url: "https://example.com/file.jpg" }
|
||||
}, NOTE_TYPES.DM_ATTACHMENT);
|
||||
|
||||
// 17. DM Read Receipt
|
||||
runTest("buildDmReadReceiptEvent", buildDmReadReceiptEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
eventId: TEST_EVENT_ID
|
||||
}, NOTE_TYPES.DM_READ_RECEIPT);
|
||||
|
||||
// 18. DM Typing Indicator
|
||||
runTest("buildDmTypingIndicatorEvent", buildDmTypingIndicatorEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
eventId: TEST_EVENT_ID
|
||||
}, NOTE_TYPES.DM_TYPING);
|
||||
|
||||
// 19. View Event
|
||||
runTest("buildViewEvent", buildViewEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
pointerValue: "video-id",
|
||||
dedupeTag: "random-dedupe"
|
||||
}, NOTE_TYPES.VIEW_EVENT);
|
||||
|
||||
// 20. Zap Request
|
||||
runTest("buildZapRequestEvent", buildZapRequestEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
recipientPubkey: TEST_PUBKEY,
|
||||
amountSats: 100,
|
||||
lnurl: "lnurl1..."
|
||||
}, NOTE_TYPES.ZAP_REQUEST);
|
||||
|
||||
// 21. Reaction
|
||||
runTest("buildReactionEvent", buildReactionEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
targetPointer: { type: "e", value: TEST_EVENT_ID },
|
||||
content: "+"
|
||||
}, NOTE_TYPES.VIDEO_REACTION);
|
||||
|
||||
// 22. Comment
|
||||
runTest("buildCommentEvent", buildCommentEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
videoEventId: TEST_EVENT_ID,
|
||||
content: "Nice video!"
|
||||
}, NOTE_TYPES.VIDEO_COMMENT);
|
||||
|
||||
// 23. Watch History
|
||||
// Testing implicit monthIdentifier via default
|
||||
runTest("buildWatchHistoryEvent (Default Month)", buildWatchHistoryEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
content: { "video-id": 1234567890 }
|
||||
}, NOTE_TYPES.WATCH_HISTORY);
|
||||
|
||||
// 24. Subscription List
|
||||
runTest("buildSubscriptionListEvent", buildSubscriptionListEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
content: [["p", TEST_PUBKEY]]
|
||||
}, NOTE_TYPES.SUBSCRIPTION_LIST);
|
||||
|
||||
// 25. Block List
|
||||
runTest("buildBlockListEvent", buildBlockListEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
content: [["p", TEST_PUBKEY]]
|
||||
}, NOTE_TYPES.USER_BLOCK_LIST);
|
||||
|
||||
// 26. Hashtag Preference
|
||||
runTest("buildHashtagPreferenceEvent", buildHashtagPreferenceEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
content: { version: 1, interests: ["nostr"], disinterests: [] }
|
||||
}, NOTE_TYPES.HASHTAG_PREFERENCES);
|
||||
|
||||
// 27. Admin List (Moderation)
|
||||
runTest("buildAdminListEvent (Moderation)", (input) => buildAdminListEvent("moderation", input), {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
hexPubkeys: [TEST_PUBKEY]
|
||||
}, NOTE_TYPES.ADMIN_MODERATION_LIST);
|
||||
|
||||
// 28. NIP-71 Video
|
||||
runTest("buildNip71VideoEvent", buildNip71VideoEvent, {
|
||||
pubkey: TEST_PUBKEY,
|
||||
created_at: 1234567890,
|
||||
title: "NIP-71 Test",
|
||||
content: "Summary",
|
||||
metadata: {
|
||||
duration: 120,
|
||||
alt: "Alt text"
|
||||
}
|
||||
}, NOTE_TYPES.NIP71_VIDEO);
|
||||
|
||||
let type;
|
||||
const builderToType = {
|
||||
buildVideoPostEvent: schemas.NOTE_TYPES.VIDEO_POST,
|
||||
buildVideoMirrorEvent: schemas.NOTE_TYPES.VIDEO_MIRROR,
|
||||
buildRepostEvent: schemas.NOTE_TYPES.REPOST,
|
||||
buildShareEvent: schemas.NOTE_TYPES.SHARE,
|
||||
buildRelayListEvent: schemas.NOTE_TYPES.RELAY_LIST,
|
||||
buildDmRelayListEvent: schemas.NOTE_TYPES.DM_RELAY_LIST,
|
||||
buildProfileMetadataEvent: schemas.NOTE_TYPES.PROFILE_METADATA,
|
||||
buildMuteListEvent: schemas.NOTE_TYPES.MUTE_LIST,
|
||||
buildDeletionEvent: schemas.NOTE_TYPES.DELETION,
|
||||
buildLegacyDirectMessageEvent: schemas.NOTE_TYPES.LEGACY_DM,
|
||||
buildDmAttachmentEvent: schemas.NOTE_TYPES.DM_ATTACHMENT,
|
||||
buildDmReadReceiptEvent: schemas.NOTE_TYPES.DM_READ_RECEIPT,
|
||||
buildDmTypingIndicatorEvent: schemas.NOTE_TYPES.DM_TYPING,
|
||||
buildViewEvent: schemas.NOTE_TYPES.VIEW_EVENT,
|
||||
buildZapRequestEvent: schemas.NOTE_TYPES.ZAP_REQUEST,
|
||||
buildReactionEvent: schemas.NOTE_TYPES.VIDEO_REACTION,
|
||||
buildCommentEvent: schemas.NOTE_TYPES.VIDEO_COMMENT,
|
||||
buildWatchHistoryEvent: schemas.NOTE_TYPES.WATCH_HISTORY,
|
||||
buildSubscriptionListEvent: schemas.NOTE_TYPES.SUBSCRIPTION_LIST,
|
||||
buildBlockListEvent: schemas.NOTE_TYPES.USER_BLOCK_LIST,
|
||||
buildHashtagPreferenceEvent: schemas.NOTE_TYPES.HASHTAG_PREFERENCES,
|
||||
buildAdminListEvent: schemas.NOTE_TYPES.ADMIN_MODERATION_LIST, // Default
|
||||
buildGiftWrapEvent: schemas.NOTE_TYPES.GIFT_WRAP,
|
||||
buildSealEvent: schemas.NOTE_TYPES.SEAL,
|
||||
buildChatMessageEvent: schemas.NOTE_TYPES.CHAT_MESSAGE,
|
||||
buildHttpAuthEvent: schemas.NOTE_TYPES.HTTP_AUTH,
|
||||
buildReportEvent: schemas.NOTE_TYPES.REPORT,
|
||||
buildNip71VideoEvent: schemas.NOTE_TYPES.NIP71_VIDEO // Default
|
||||
};
|
||||
// Write Report
|
||||
fs.writeFileSync(REPORT_FILE, JSON.stringify(results, null, 2));
|
||||
console.log(`\nReport written to ${REPORT_FILE}`);
|
||||
|
||||
type = builderToType[builderName];
|
||||
|
||||
// Special cases for type resolution
|
||||
if (builderName === 'buildRepostEvent' && event.kind === 16) {
|
||||
type = schemas.NOTE_TYPES.GENERIC_REPOST;
|
||||
}
|
||||
if (builderName === 'buildAdminListEvent') {
|
||||
const listKey = Array.isArray(args) ? args[0] : args; // assuming first arg is listKey
|
||||
if (listKey === 'moderation' || listKey === 'editors') type = schemas.NOTE_TYPES.ADMIN_MODERATION_LIST;
|
||||
else if (listKey === 'whitelist') type = schemas.NOTE_TYPES.ADMIN_WHITELIST;
|
||||
else if (listKey === 'blacklist') type = schemas.NOTE_TYPES.ADMIN_BLACKLIST;
|
||||
}
|
||||
if (builderName === 'buildNip71VideoEvent') {
|
||||
if (event.kind === 22) type = schemas.NOTE_TYPES.NIP71_SHORT_VIDEO;
|
||||
else type = schemas.NOTE_TYPES.NIP71_VIDEO;
|
||||
}
|
||||
|
||||
const validation = schemas.validateEventStructure(type, event);
|
||||
|
||||
results.push({
|
||||
builder: builderName,
|
||||
testName,
|
||||
inputs: args,
|
||||
event,
|
||||
valid: validation.valid === expectedValid,
|
||||
validationResult: validation,
|
||||
expectedValid
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
results.push({
|
||||
builder: builderName,
|
||||
testName,
|
||||
inputs: args,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
valid: false
|
||||
});
|
||||
if (failures.length > 0) {
|
||||
console.error(`\n⚠️ Found ${failures.length} validation failures.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\n✨ All validations passed!");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Test Cases ---
|
||||
|
||||
const PUBKEY = '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const CREATED_AT = 1700000000;
|
||||
|
||||
// Video Post
|
||||
runTest('buildVideoPostEvent', 'Basic Video Post', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
dTagValue: 'video-1',
|
||||
content: {
|
||||
version: 3,
|
||||
title: 'My Video',
|
||||
videoRootId: 'video-1',
|
||||
url: 'https://example.com/video.mp4'
|
||||
}
|
||||
});
|
||||
|
||||
runTest('buildVideoPostEvent', 'Video Post with Magnet', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
dTagValue: 'video-2',
|
||||
content: {
|
||||
version: 3,
|
||||
title: 'My Magnet Video',
|
||||
videoRootId: 'video-2',
|
||||
magnet: 'magnet:?xt=urn:btih:1234567890abcdef1234567890abcdef12345678&dn=video'
|
||||
}
|
||||
});
|
||||
|
||||
// Video Mirror
|
||||
runTest('buildVideoMirrorEvent', 'Video Mirror', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
tags: [['url', 'https://example.com/mirror.mp4'], ['m', 'video/mp4']],
|
||||
content: 'Alt text'
|
||||
});
|
||||
|
||||
// Repost
|
||||
runTest('buildRepostEvent', 'Repost Event', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
eventId: '1111111111111111111111111111111111111111111111111111111111111111',
|
||||
eventRelay: 'wss://relay.example.com'
|
||||
});
|
||||
|
||||
runTest('buildRepostEvent', 'Generic Repost (Kind 16)', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
targetKind: 30078,
|
||||
serializedEvent: JSON.stringify({ kind: 30078, tags: [], content: '{}' })
|
||||
});
|
||||
|
||||
// Share
|
||||
runTest('buildShareEvent', 'Share Event', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
content: 'Check this out!',
|
||||
video: { id: '2222222222222222222222222222222222222222222222222222222222222222', pubkey: PUBKEY },
|
||||
relays: ['wss://relay.example.com']
|
||||
});
|
||||
|
||||
// Relay List
|
||||
runTest('buildRelayListEvent', 'Relay List', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
relays: [
|
||||
{ url: 'wss://relay1.com', read: true, write: true },
|
||||
{ url: 'wss://relay2.com', read: true, write: false }
|
||||
]
|
||||
});
|
||||
|
||||
// DM Relay List
|
||||
runTest('buildDmRelayListEvent', 'DM Relay List', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
relays: ['wss://relay.dm.com']
|
||||
});
|
||||
|
||||
// Profile Metadata
|
||||
runTest('buildProfileMetadataEvent', 'Profile Metadata', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
metadata: { name: 'Alice', about: 'Hello' }
|
||||
});
|
||||
|
||||
// Mute List
|
||||
runTest('buildMuteListEvent', 'Mute List', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
pTags: [PUBKEY],
|
||||
content: '{ "blockedPubkeys": [] }',
|
||||
encrypted: true,
|
||||
encryptionTag: 'nip44_v2'
|
||||
});
|
||||
|
||||
// Deletion
|
||||
runTest('buildDeletionEvent', 'Deletion', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
eventIds: ['3333333333333333333333333333333333333333333333333333333333333333'],
|
||||
reason: 'Mistake'
|
||||
});
|
||||
|
||||
// Legacy DM
|
||||
runTest('buildLegacyDirectMessageEvent', 'Legacy DM', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
ciphertext: 'encrypted_content'
|
||||
});
|
||||
|
||||
// DM Attachment
|
||||
runTest('buildDmAttachmentEvent', 'DM Attachment', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
attachment: { url: 'https://example.com/file.jpg', x: 'hash', type: 'image/jpeg' }
|
||||
});
|
||||
|
||||
// DM Read Receipt
|
||||
runTest('buildDmReadReceiptEvent', 'DM Read Receipt', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
eventId: '4444444444444444444444444444444444444444444444444444444444444444'
|
||||
});
|
||||
|
||||
// DM Typing
|
||||
runTest('buildDmTypingIndicatorEvent', 'DM Typing', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
expiresAt: CREATED_AT + 60
|
||||
});
|
||||
|
||||
// View Event
|
||||
runTest('buildViewEvent', 'View Event', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
pointerValue: 'video-1',
|
||||
dedupeTag: 'session-1',
|
||||
includeSessionTag: true
|
||||
});
|
||||
|
||||
// Zap Request
|
||||
runTest('buildZapRequestEvent', 'Zap Request', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
amountSats: 100,
|
||||
relays: ['wss://relay.zap.com']
|
||||
});
|
||||
|
||||
// Reaction
|
||||
runTest('buildReactionEvent', 'Reaction', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
targetPointer: { type: 'e', value: '5555555555555555555555555555555555555555555555555555555555555555' },
|
||||
content: '+'
|
||||
});
|
||||
|
||||
// Comment
|
||||
runTest('buildCommentEvent', 'Comment', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
content: 'Nice video!',
|
||||
videoEventId: '6666666666666666666666666666666666666666666666666666666666666666',
|
||||
rootKind: '30078'
|
||||
});
|
||||
|
||||
// Watch History
|
||||
runTest('buildWatchHistoryEvent', 'Watch History', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
monthIdentifier: '2023-11',
|
||||
content: { version: 2, month: '2023-11', items: [] }
|
||||
});
|
||||
|
||||
// Subscription List
|
||||
runTest('buildSubscriptionListEvent', 'Subscription List', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
content: 'encrypted_subscriptions',
|
||||
encryption: 'nip44_v2'
|
||||
});
|
||||
|
||||
// Block List
|
||||
runTest('buildBlockListEvent', 'Block List', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
content: 'encrypted_blocks',
|
||||
encryption: 'nip44_v2'
|
||||
});
|
||||
|
||||
// Hashtag Preferences
|
||||
runTest('buildHashtagPreferenceEvent', 'Hashtag Preferences', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
content: 'encrypted_preferences'
|
||||
});
|
||||
|
||||
// Admin Lists
|
||||
runTest('buildAdminListEvent', 'Admin Moderation List', ['moderation', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
hexPubkeys: [PUBKEY]
|
||||
}]);
|
||||
|
||||
runTest('buildAdminListEvent', 'Admin Whitelist', ['whitelist', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
hexPubkeys: [PUBKEY]
|
||||
}]);
|
||||
|
||||
// Gift Wrap
|
||||
runTest('buildGiftWrapEvent', 'Gift Wrap', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
ciphertext: 'sealed_message'
|
||||
});
|
||||
|
||||
// Seal
|
||||
runTest('buildSealEvent', 'Seal', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
ciphertext: 'rumor_message'
|
||||
});
|
||||
|
||||
// Chat Message
|
||||
runTest('buildChatMessageEvent', 'Chat Message', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
recipientPubkey: PUBKEY,
|
||||
content: 'Hello'
|
||||
});
|
||||
|
||||
// HTTP Auth
|
||||
runTest('buildHttpAuthEvent', 'HTTP Auth', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
url: 'https://example.com/auth',
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
// Report
|
||||
runTest('buildReportEvent', 'Report', {
|
||||
pubkey: PUBKEY,
|
||||
created_at: CREATED_AT,
|
||||
eventId: '7777777777777777777777777777777777777777777777777777777777777777',
|
||||
reportType: 'spam'
|
||||
});
|
||||
|
||||
// NIP-71 Video
|
||||
runTest('buildNip71VideoEvent', 'NIP-71 Video', {
|
||||
metadata: {
|
||||
title: 'My NIP-71 Video',
|
||||
summary: 'Summary',
|
||||
kind: 21,
|
||||
publishedAt: 1700000000
|
||||
},
|
||||
pubkey: PUBKEY,
|
||||
title: 'My NIP-71 Video',
|
||||
created_at: CREATED_AT
|
||||
});
|
||||
|
||||
// --- CLI Handling ---
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let outFile = 'artifacts/validate-events-latest.json';
|
||||
const dryRun = args.includes('--dry-run');
|
||||
|
||||
args.forEach(arg => {
|
||||
if (arg.startsWith('--out=')) {
|
||||
outFile = arg.split('=')[1];
|
||||
}
|
||||
});
|
||||
|
||||
// Summary
|
||||
const total = results.length;
|
||||
const passed = results.filter(r => r.valid).length;
|
||||
const failed = total - passed;
|
||||
|
||||
console.log(`\nValidation Summary: ${passed}/${total} passed`);
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`⚠️ ${failed} failures found:`);
|
||||
results.filter(r => !r.valid).forEach(r => {
|
||||
console.log(`\n[FAIL] ${r.builder}: ${r.testName}`);
|
||||
if (r.error) {
|
||||
console.log(` Error: ${r.error}`);
|
||||
} else if (r.validationResult && r.validationResult.errors) {
|
||||
r.validationResult.errors.forEach(e => console.log(` Schema Error: ${e}`));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('✓ All builders validated successfully.');
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
const dir = path.dirname(outFile);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(outFile, JSON.stringify(results, null, 2));
|
||||
console.log(`\nReport written to ${outFile}`);
|
||||
}
|
||||
runValidation();
|
||||
Reference in new issue
Block a user