Files
bitvid/scripts/agent/interop-test.mjs
T
google-labs-jules[bot]andPR0M3TH3AN e3d9ea7e3a feat: add interoperability test script for Nostr protocol verification
- Implements `scripts/agent/interop-test.mjs` to verify Video Post, View Event, and DM flows.
- Uses `NostrClient` with ephemeral keys and a local relay.
- Ensures correct event construction via `nostrEventSchemas.js`.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-07 07:10:56 +00:00

156 lines
5.3 KiB
JavaScript

import WebSocket from 'ws';
import { webcrypto } from 'node:crypto';
import { startRelay } from './simple-relay.mjs';
import { NostrClient } from '../../js/nostr/client.js';
import { buildVideoPostEvent, buildViewEvent } from '../../js/nostrEventSchemas.js';
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
// Polyfills
if (typeof globalThis.WebSocket === 'undefined') {
globalThis.WebSocket = WebSocket;
}
if (typeof globalThis.crypto === 'undefined') {
globalThis.crypto = webcrypto;
}
if (typeof globalThis.localStorage === 'undefined') {
const storage = new Map();
globalThis.localStorage = {
getItem: (k) => storage.get(k) || null,
setItem: (k, v) => storage.set(String(k), String(v)),
removeItem: (k) => storage.delete(k),
clear: () => storage.clear(),
key: (i) => Array.from(storage.keys())[i] || null,
get length() { return storage.size; }
};
}
if (typeof globalThis.window === 'undefined') {
globalThis.window = globalThis;
globalThis.window.localStorage = globalThis.localStorage;
}
if (typeof globalThis.navigator === 'undefined') {
globalThis.navigator = { userAgent: 'node' };
}
async function runTests() {
console.log('Starting Interop Tests...');
// Start Relay
const relayPort = 8899; // Use different port to avoid conflict
const relayUrl = `ws://localhost:${relayPort}`;
const relayServer = startRelay(relayPort);
console.log(`Test relay started at ${relayUrl}`);
try {
// Setup Clients
const aliceClient = new NostrClient();
aliceClient.relays = [relayUrl];
aliceClient.writeRelays = [relayUrl];
aliceClient.readRelays = [relayUrl];
await aliceClient.ensurePool();
await aliceClient.connectToRelays();
const bobClient = new NostrClient();
bobClient.relays = [relayUrl];
bobClient.writeRelays = [relayUrl];
bobClient.readRelays = [relayUrl];
await bobClient.ensurePool();
await bobClient.connectToRelays();
// Generate Keys
const aliceSk = generateSecretKey();
const aliceSkHex = Buffer.from(aliceSk).toString('hex');
const alicePk = getPublicKey(aliceSk);
const bobSk = generateSecretKey();
const bobSkHex = Buffer.from(bobSk).toString('hex');
const bobPk = getPublicKey(bobSk);
const bobNpub = nip19.npubEncode(bobPk);
console.log('Alice Pubkey:', alicePk);
console.log('Bob Pubkey:', bobPk);
// Register Signers
await aliceClient.registerPrivateKeySigner({ privateKey: aliceSkHex, pubkey: alicePk });
await bobClient.registerPrivateKeySigner({ privateKey: bobSkHex, pubkey: bobPk });
// --- Test 1: Video Post ---
console.log('\n[Test 1] Publishing Video Post...');
const videoEvent = buildVideoPostEvent({
pubkey: alicePk,
created_at: Math.floor(Date.now() / 1000),
dTagValue: `test-video-${Date.now()}`,
content: {
version: 3,
title: 'Interop Test Video',
videoRootId: `root-${Date.now()}`,
description: 'A test video for interoperability',
url: 'https://example.com/video.mp4',
mode: 'dev'
}
});
const { signedEvent: publishedVideo } = await aliceClient.signAndPublishEvent(videoEvent);
console.log('Video published:', publishedVideo.id);
// Give relay a moment
await new Promise(r => setTimeout(r, 200));
// Verify
const fetchedVideo = await aliceClient.getEventById(publishedVideo.id);
if (!fetchedVideo) {
console.log('Fetching raw event to debug...');
const raw = await aliceClient.fetchRawEventById(publishedVideo.id);
console.log('Raw event fetch result:', raw ? 'FOUND' : 'NOT FOUND');
throw new Error('Failed to fetch published video');
}
if (fetchedVideo.title !== 'Interop Test Video') {
console.log('Fetched Video:', JSON.stringify(fetchedVideo, null, 2));
throw new Error('Video content mismatch');
}
console.log('PASS: Video Post verified.');
// --- Test 2: View Event ---
console.log('\n[Test 2] Publishing View Event...');
const viewEvent = buildViewEvent({
pubkey: alicePk,
created_at: Math.floor(Date.now() / 1000),
pointerValue: publishedVideo.id // referencing the video
});
const { signedEvent: publishedView } = await aliceClient.signAndPublishEvent(viewEvent);
console.log('View event published:', publishedView.id);
console.log('PASS: View Event published.');
// --- Test 3: Direct Message ---
console.log('\n[Test 3] Sending DM...');
const message = 'Hello Bob, this is a test.';
const sendResult = await aliceClient.sendDirectMessage(bobNpub, message, null, { useNip17: false });
if (!sendResult.ok) throw new Error(`DM Send failed: ${sendResult.error}`);
console.log('DM sent.');
// Wait a bit for relay
await new Promise(r => setTimeout(r, 500));
// Bob fetches
const messages = await bobClient.listDirectMessages();
const receivedMsg = messages.find(m => m.plaintext === message);
if (!receivedMsg) {
console.log('Messages received:', messages.map(m => m.plaintext));
throw new Error('Bob did not receive the DM or decryption failed');
}
console.log('PASS: DM received and decrypted.');
} catch (error) {
console.error('\nTEST FAILED:', error);
process.exitCode = 1;
} finally {
relayServer.close();
console.log('\nRelay closed. Exiting.');
process.exit(process.exitCode || 0);
}
}
runTests();