mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
- Removed a TODO comment in `scripts/agent/validate-events.mjs` as the functionality was already implemented. - Optimized the code to avoid redundant `JSON.parse` calls. - Verified the script passes all validation tests. - Created `artifacts/issue_telemetry_json.md` to track the larger task of handling JSON logs in telemetry aggregation.
431 lines
12 KiB
JavaScript
431 lines
12 KiB
JavaScript
import {
|
|
NOTE_TYPES,
|
|
getNostrEventSchema,
|
|
buildVideoPostEvent,
|
|
buildVideoMirrorEvent,
|
|
buildRepostEvent,
|
|
buildRelayListEvent,
|
|
buildDmRelayListEvent,
|
|
buildProfileMetadataEvent,
|
|
buildMuteListEvent,
|
|
buildDmAttachmentEvent,
|
|
buildDmReadReceiptEvent,
|
|
buildDmTypingIndicatorEvent,
|
|
buildViewEvent,
|
|
buildZapRequestEvent,
|
|
buildReactionEvent,
|
|
buildCommentEvent,
|
|
buildWatchHistoryEvent,
|
|
buildSubscriptionListEvent,
|
|
buildBlockListEvent,
|
|
buildHashtagPreferenceEvent,
|
|
buildAdminListEvent,
|
|
} from "../../js/nostrEventSchemas.js";
|
|
|
|
import { buildNip71VideoEvent } from "../../js/nostr/nip71.js";
|
|
|
|
// Mock data
|
|
const MOCK_PUBKEY = "0000000000000000000000000000000000000000000000000000000000000001";
|
|
const MOCK_EVENT_ID = "0000000000000000000000000000000000000000000000000000000000000002";
|
|
const MOCK_TIMESTAMP = 1700000000;
|
|
const MOCK_URL = "https://example.com/video.mp4";
|
|
const MOCK_THUMBNAIL = "https://example.com/thumb.jpg";
|
|
const MOCK_MAGNET = "magnet:?xt=urn:btih:example";
|
|
const MOCK_RELAY = "wss://relay.example.com";
|
|
const MOCK_D_TAG = "mock-d-tag";
|
|
|
|
// Helper to check if a tag exists
|
|
function hasTag(tags, tagName, tagValue = null) {
|
|
return tags.some(
|
|
(tag) =>
|
|
Array.isArray(tag) &&
|
|
tag[0] === tagName &&
|
|
(tagValue === null || tag[1] === tagValue)
|
|
);
|
|
}
|
|
|
|
// Validation logic
|
|
function validateEvent(type, event) {
|
|
const schema = getNostrEventSchema(type);
|
|
const errors = [];
|
|
|
|
if (!schema) {
|
|
return [`Schema not found for type: ${type}`];
|
|
}
|
|
|
|
// 1. Validate Kind
|
|
if (event.kind !== schema.kind) {
|
|
errors.push(`Kind mismatch: expected ${schema.kind}, got ${event.kind}`);
|
|
}
|
|
|
|
// 2. Validate Required Tags
|
|
// Topic tag
|
|
if (schema.topicTag) {
|
|
if (!hasTag(event.tags, schema.topicTag.name, schema.topicTag.value)) {
|
|
errors.push(
|
|
`Missing topic tag: ${schema.topicTag.name}=${schema.topicTag.value}`
|
|
);
|
|
}
|
|
}
|
|
|
|
// Identifier tag (d tag)
|
|
if (schema.identifierTag) {
|
|
// For lists with fixed identifier values (like subscription list), check the value
|
|
const expectedValue = schema.identifierTag.value;
|
|
if (!hasTag(event.tags, schema.identifierTag.name, expectedValue)) {
|
|
errors.push(
|
|
`Missing identifier tag: ${schema.identifierTag.name}${
|
|
expectedValue ? "=" + expectedValue : ""
|
|
}`
|
|
);
|
|
}
|
|
}
|
|
|
|
// Append tags
|
|
if (schema.appendTags) {
|
|
schema.appendTags.forEach((appendTag) => {
|
|
const tagName = appendTag[0];
|
|
const tagValue = appendTag[1];
|
|
if (!hasTag(event.tags, tagName, tagValue)) {
|
|
errors.push(`Missing append tag: ${tagName}=${tagValue}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Specific schema tags
|
|
if (schema.relayTagName) {
|
|
// Check if at least one relay tag exists if we added relays in mock data
|
|
// This is slightly context dependent, but we can check if the builder respects the tag name
|
|
// Logic: if we expect the builder to use 'r' or 'relay', we can't easily verify generally unless we check specific output
|
|
// But we can check if any tag with that name exists IF we passed relays.
|
|
}
|
|
|
|
// 3. Validate Content Format
|
|
if (schema.content) {
|
|
if (schema.content.format === "json") {
|
|
try {
|
|
const parsed = JSON.parse(event.content);
|
|
if (schema.content.fields) {
|
|
schema.content.fields.forEach(field => {
|
|
if (field.required && parsed[field.key] === undefined) {
|
|
errors.push(`Missing required content field: ${field.key}`);
|
|
}
|
|
if (parsed[field.key] !== undefined) {
|
|
if (field.type === 'number' && typeof parsed[field.key] !== 'number') {
|
|
errors.push(`Invalid type for content field ${field.key}: expected number, got ${typeof parsed[field.key]}`);
|
|
}
|
|
if (field.type === 'string' && typeof parsed[field.key] !== 'string') {
|
|
errors.push(`Invalid type for content field ${field.key}: expected string, got ${typeof parsed[field.key]}`);
|
|
}
|
|
if (field.type === 'boolean' && typeof parsed[field.key] !== 'boolean') {
|
|
errors.push(`Invalid type for content field ${field.key}: expected boolean, got ${typeof parsed[field.key]}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
errors.push("Content is not valid JSON");
|
|
}
|
|
} else if (schema.content.format === "empty") {
|
|
if (event.content !== "") {
|
|
errors.push("Content should be empty");
|
|
}
|
|
} else if (
|
|
schema.content.format === "text" ||
|
|
schema.content.format === "encrypted-tag-list" ||
|
|
schema.content.format === "nip44-json"
|
|
) {
|
|
if (typeof event.content !== "string") {
|
|
errors.push("Content must be a string");
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
// Test cases
|
|
const tests = [
|
|
{
|
|
name: "Video Post",
|
|
type: NOTE_TYPES.VIDEO_POST,
|
|
builder: buildVideoPostEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
dTagValue: MOCK_D_TAG,
|
|
content: {
|
|
version: 3,
|
|
title: "Test Video",
|
|
videoRootId: MOCK_EVENT_ID,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Video Mirror",
|
|
type: NOTE_TYPES.VIDEO_MIRROR,
|
|
builder: buildVideoMirrorEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
content: "Alt text",
|
|
tags: [["magnet", MOCK_MAGNET]],
|
|
},
|
|
},
|
|
{
|
|
name: "Repost",
|
|
type: NOTE_TYPES.REPOST,
|
|
builder: buildRepostEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
eventId: MOCK_EVENT_ID,
|
|
eventRelay: MOCK_RELAY,
|
|
targetEvent: { id: MOCK_EVENT_ID, pubkey: MOCK_PUBKEY, kind: 1, content: "test", tags: [], created_at: MOCK_TIMESTAMP },
|
|
},
|
|
},
|
|
{
|
|
name: "Relay List",
|
|
type: NOTE_TYPES.RELAY_LIST,
|
|
builder: buildRelayListEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
relays: [
|
|
{ url: "wss://r1.com", read: true, write: true },
|
|
{ url: "wss://r2.com", read: true, write: false },
|
|
],
|
|
},
|
|
},
|
|
{
|
|
name: "DM Relay List",
|
|
type: NOTE_TYPES.DM_RELAY_LIST,
|
|
builder: buildDmRelayListEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
relays: ["wss://dm.relay.com"],
|
|
},
|
|
},
|
|
{
|
|
name: "Profile Metadata",
|
|
type: NOTE_TYPES.PROFILE_METADATA,
|
|
builder: buildProfileMetadataEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
metadata: { name: "TestUser", about: "Testing" },
|
|
},
|
|
},
|
|
{
|
|
name: "Mute List",
|
|
type: NOTE_TYPES.MUTE_LIST,
|
|
builder: buildMuteListEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
pTags: [MOCK_PUBKEY],
|
|
},
|
|
},
|
|
{
|
|
name: "DM Attachment",
|
|
type: NOTE_TYPES.DM_ATTACHMENT,
|
|
builder: buildDmAttachmentEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
recipientPubkey: MOCK_PUBKEY,
|
|
attachment: { x: "hash", url: MOCK_URL, type: "image/jpeg" },
|
|
},
|
|
},
|
|
{
|
|
name: "DM Read Receipt",
|
|
type: NOTE_TYPES.DM_READ_RECEIPT,
|
|
builder: buildDmReadReceiptEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
recipientPubkey: MOCK_PUBKEY,
|
|
eventId: MOCK_EVENT_ID,
|
|
messageKind: 4,
|
|
},
|
|
},
|
|
{
|
|
name: "DM Typing Indicator",
|
|
type: NOTE_TYPES.DM_TYPING,
|
|
builder: buildDmTypingIndicatorEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
recipientPubkey: MOCK_PUBKEY,
|
|
eventId: MOCK_EVENT_ID,
|
|
expiresAt: MOCK_TIMESTAMP + 60,
|
|
},
|
|
},
|
|
{
|
|
name: "View Event",
|
|
type: NOTE_TYPES.VIEW_EVENT,
|
|
builder: buildViewEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
pointerTag: ["e", MOCK_EVENT_ID],
|
|
dedupeTag: "unique-view-id",
|
|
},
|
|
},
|
|
{
|
|
name: "Zap Request",
|
|
type: NOTE_TYPES.ZAP_REQUEST,
|
|
builder: buildZapRequestEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
recipientPubkey: MOCK_PUBKEY,
|
|
amountSats: 100,
|
|
relays: [MOCK_RELAY],
|
|
eventId: MOCK_EVENT_ID,
|
|
},
|
|
},
|
|
{
|
|
name: "Reaction",
|
|
type: NOTE_TYPES.VIDEO_REACTION,
|
|
builder: buildReactionEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
targetPointer: { type: "e", value: MOCK_EVENT_ID, relay: MOCK_RELAY },
|
|
content: "+",
|
|
},
|
|
},
|
|
{
|
|
name: "Video Comment",
|
|
type: NOTE_TYPES.VIDEO_COMMENT,
|
|
builder: buildCommentEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
videoEventId: MOCK_EVENT_ID,
|
|
videoEventRelay: MOCK_RELAY,
|
|
content: "Great video!",
|
|
},
|
|
},
|
|
{
|
|
name: "Watch History",
|
|
type: NOTE_TYPES.WATCH_HISTORY,
|
|
builder: buildWatchHistoryEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
monthIdentifier: "2023-11",
|
|
pointerTags: [["e", MOCK_EVENT_ID]],
|
|
content: JSON.stringify({ version: 2, month: "2023-11", items: [] }),
|
|
},
|
|
},
|
|
{
|
|
name: "Subscription List",
|
|
type: NOTE_TYPES.SUBSCRIPTION_LIST,
|
|
builder: buildSubscriptionListEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
content: "encrypted-content",
|
|
encryption: "nip44",
|
|
},
|
|
},
|
|
{
|
|
name: "Block List",
|
|
type: NOTE_TYPES.USER_BLOCK_LIST,
|
|
builder: buildBlockListEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
content: "encrypted-content",
|
|
encryption: "nip44",
|
|
},
|
|
},
|
|
{
|
|
name: "Hashtag Preferences",
|
|
type: NOTE_TYPES.HASHTAG_PREFERENCES,
|
|
builder: buildHashtagPreferenceEvent,
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
content: "encrypted-content",
|
|
},
|
|
},
|
|
{
|
|
name: "Admin Moderation List",
|
|
type: NOTE_TYPES.ADMIN_MODERATION_LIST,
|
|
builder: (params) => buildAdminListEvent("moderation", params),
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
hexPubkeys: [MOCK_PUBKEY],
|
|
},
|
|
},
|
|
{
|
|
name: "Admin Blacklist",
|
|
type: NOTE_TYPES.ADMIN_BLACKLIST,
|
|
builder: (params) => buildAdminListEvent("blacklist", params),
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
hexPubkeys: [MOCK_PUBKEY],
|
|
},
|
|
},
|
|
{
|
|
name: "Admin Whitelist",
|
|
type: NOTE_TYPES.ADMIN_WHITELIST,
|
|
builder: (params) => buildAdminListEvent("whitelist", params),
|
|
params: {
|
|
pubkey: MOCK_PUBKEY,
|
|
created_at: MOCK_TIMESTAMP,
|
|
hexPubkeys: [MOCK_PUBKEY],
|
|
},
|
|
},
|
|
{
|
|
name: "NIP-71 Video",
|
|
type: NOTE_TYPES.NIP71_VIDEO,
|
|
builder: buildNip71VideoEvent,
|
|
params: {
|
|
metadata: {
|
|
kind: 21,
|
|
title: "Test NIP-71 Video",
|
|
summary: "This is a test summary",
|
|
},
|
|
pubkey: MOCK_PUBKEY,
|
|
title: "Test NIP-71 Video",
|
|
},
|
|
},
|
|
];
|
|
|
|
// Execution
|
|
console.log("Starting Event Validation...");
|
|
let failCount = 0;
|
|
|
|
tests.forEach((test) => {
|
|
try {
|
|
const event = test.builder(test.params);
|
|
const errors = validateEvent(test.type, event);
|
|
|
|
if (errors.length > 0) {
|
|
console.error(`❌ ${test.name} Failed:`);
|
|
errors.forEach((err) => console.error(` - ${err}`));
|
|
failCount++;
|
|
} else {
|
|
console.log(`✅ ${test.name} Passed`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`❌ ${test.name} threw exception:`, e);
|
|
failCount++;
|
|
}
|
|
});
|
|
|
|
if (failCount > 0) {
|
|
console.log(`\nValidation failed with ${failCount} errors.`);
|
|
process.exit(1);
|
|
} else {
|
|
console.log("\nAll events validated successfully!");
|
|
process.exit(0);
|
|
}
|