Merge pull request #2023 from PR0M3TH3AN/fix-webtorrent-webseed-regression-10274894627332192935

Fix WebTorrent webseed stream health check regression
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-01 09:14:43 -05:00
commit 5a67774011
32 files changed
+1165 -1058

No files matched your search

+13 -27
View File
@@ -1,37 +1,23 @@
import asyncio
from playwright.async_api import async_playwright
import sys
from playwright.sync_api import sync_playwright
async def run():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
def run():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# Listen for console logs with location
def handle_console(msg):
loc = msg.location
print(f"console:{msg.type}: {msg.text} @ {loc['url']}:{loc['lineNumber']}")
page.on("console", handle_console)
# Listen for exceptions
page.on("console", lambda msg: print(f"console:{msg.type}: {msg.text}"))
page.on("pageerror", lambda exc: print(f"pageerror: {exc}"))
# Listen for failed requests
page.on("requestfailed", lambda req: print(f"requestfailed: {req.url} {req.failure}"))
# Listen for 404s specifically
page.on("response", lambda res: print(f"response:{res.status}: {res.url}") if res.status >= 400 else None)
page.on("requestfailed", lambda req: print(f"requestfailed: {req.url} - {req.failure}"))
try:
print("Navigating to http://localhost:8000...")
await page.goto("http://localhost:8000")
# Wait a bit for initialization
await page.wait_for_timeout(5000)
page.goto("http://localhost:8000")
# Wait a bit for JS to execute and errors to appear
page.wait_for_timeout(5000)
except Exception as e:
print(f"Navigation error: {e}")
print(f"Error navigating: {e}")
finally:
await browser.close()
browser.close()
if __name__ == "__main__":
asyncio.run(run())
run()
+177 -234
View File
@@ -1,242 +1,185 @@
import './setup-test-env.js';
import { NostrClient } from '../../js/nostr/client.js';
import { decryptDM } from '../../js/dmDecryptor.js';
import {
buildVideoPostEvent,
buildLegacyDirectMessageEvent,
buildViewEvent,
NOTE_TYPES
} from '../../js/nostrEventSchemas.js';
import { generateSecretKey, getPublicKey } from 'nostr-tools';
import fs from 'fs';
import path from 'path';
import { buildVideoPostEvent, buildViewEvent, NOTE_TYPES } from '../../js/nostrEventSchemas.js';
import * as NostrTools from 'nostr-tools';
// Configuration
const TEST_RELAYS = [
'wss://relay.damus.io',
'wss://nos.lol'
const bytesToHex = (bytes) => {
return Buffer.from(bytes).toString('hex');
};
const RELAYS = [
"wss://relay.damus.io",
"wss://nos.lol"
];
// Artifacts
const ARTIFACTS_DIR = 'artifacts';
const LOG_FILE = path.join(ARTIFACTS_DIR, `interop-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.log`);
async function runTests() {
console.log("Starting Protocol & Interop Tests...");
// Logger
function log(message) {
const timestamp = new Date().toISOString();
const line = `[${timestamp}] ${message}`;
console.log(line);
fs.appendFileSync(LOG_FILE, line + '\n');
// --- Setup ---
console.log("[Setup] Generating ephemeral keys...");
const aliceSkBytes = NostrTools.generateSecretKey();
const aliceSk = bytesToHex(aliceSkBytes);
const alicePk = NostrTools.getPublicKey(aliceSkBytes);
const bobSkBytes = NostrTools.generateSecretKey();
const bobSk = bytesToHex(bobSkBytes);
const bobPk = NostrTools.getPublicKey(bobSkBytes);
console.log(`[Setup] Alice: ${alicePk}`);
console.log(`[Setup] Bob: ${bobPk}`);
const aliceClient = new NostrClient();
aliceClient.relays = [...RELAYS];
aliceClient.readRelays = [...RELAYS];
aliceClient.writeRelays = [...RELAYS];
const bobClient = new NostrClient();
bobClient.relays = [...RELAYS];
bobClient.readRelays = [...RELAYS];
bobClient.writeRelays = [...RELAYS];
console.log("[Setup] Connecting clients...");
// Initialize to connect
await aliceClient.init(); // This might try to load stored session, but shouldn't matter
await bobClient.init();
// Register signers
await aliceClient.registerPrivateKeySigner({ privateKey: aliceSk, pubkey: alicePk });
aliceClient.pubkey = alicePk; // Explicitly set client pubkey to ensure correct signer resolution
await bobClient.registerPrivateKeySigner({ privateKey: bobSk, pubkey: bobPk });
bobClient.pubkey = bobPk; // Explicitly set client pubkey to ensure correct signer resolution
let testsPassed = 0;
let testsTotal = 0;
// --- Test A: Video Post ---
testsTotal++;
console.log("\n[Test A] Publishing VIDEO_POST...");
try {
const videoPayload = {
version: 3,
title: "Interop Test Video " + Date.now(),
description: "This is a test video event.",
videoRootId: "interop-test-" + Date.now(),
mode: "dev",
isPrivate: false,
isNsfw: false,
isForKids: false,
enableComments: true
};
// We use signAndPublishEvent directly with a manually built event to test the schema helper
const event = buildVideoPostEvent({
pubkey: alicePk,
created_at: Math.floor(Date.now() / 1000),
dTagValue: videoPayload.videoRootId,
content: videoPayload
});
const { signedEvent } = await aliceClient.signAndPublishEvent(event);
console.log(`[Test A] Published event ${signedEvent.id}`);
// Verify
console.log("[Test A] Verifying roundtrip...");
// Give relays a moment
await new Promise(r => setTimeout(r, 2000));
// getEventById returns the Video object (converted), but we can ask for raw too
const result = await bobClient.getEventById(signedEvent.id, { includeRaw: true });
if (!result || !result.rawEvent) throw new Error("Failed to fetch event back");
const fetched = result.rawEvent;
if (fetched.id !== signedEvent.id) throw new Error("Fetched ID mismatch");
// Parse content
const content = typeof fetched.content === 'string' ? JSON.parse(fetched.content) : fetched.content;
if (content.title !== videoPayload.title) throw new Error("Content mismatch: title");
console.log("[Test A] PASSED");
testsPassed++;
} catch (e) {
console.error("[Test A] FAILED:", e);
}
// --- Test B: View Event ---
testsTotal++;
console.log("\n[Test B] Publishing VIEW_EVENT...");
try {
const viewEvent = buildViewEvent({
pubkey: alicePk,
created_at: Math.floor(Date.now() / 1000),
pointerValue: "test-video-pointer", // Arbitrary pointer for test
content: "view-test"
});
const { signedEvent: signedView } = await aliceClient.signAndPublishEvent(viewEvent);
console.log(`[Test B] Published view event ${signedView.id}`);
await new Promise(r => setTimeout(r, 2000));
// Bob fetches raw event
const fetchedView = await bobClient.fetchRawEventById(signedView.id);
if (!fetchedView) throw new Error("Failed to fetch view event");
if (fetchedView.id !== signedView.id) throw new Error("View event ID mismatch");
console.log("[Test B] PASSED");
testsPassed++;
} catch (e) {
console.error("[Test B] FAILED:", e);
}
// --- Test C: Direct Message ---
testsTotal++;
console.log("\n[Test C] Sending Direct Message...");
try {
const message = "Hello Bob, this is Alice " + Date.now();
const npubBob = NostrTools.nip19.npubEncode(bobPk);
const sendResult = await aliceClient.sendDirectMessage(npubBob, message);
if (!sendResult.ok) throw new Error("Failed to send DM: " + (sendResult.error || "unknown"));
console.log("[Test C] DM sent successfully");
await new Promise(r => setTimeout(r, 3000));
console.log("[Test C] Bob checking messages...");
// Force bob to list messages (which triggers decryption using his registered signer)
const messages = await bobClient.listDirectMessages(bobPk, { limit: 5 });
const received = messages.find(m => m.plaintext === message);
if (!received) {
console.log("Received messages:", messages.map(m => m.plaintext));
throw new Error("Bob did not find the specific message");
}
if (received.sender.pubkey !== alicePk) {
console.log(`[Test C] Sender mismatch: Expected ${alicePk}, got ${received.sender.pubkey}`);
throw new Error("Sender mismatch");
}
console.log("[Test C] PASSED");
testsPassed++;
} catch (e) {
console.error("[Test C] FAILED:", e);
}
// --- Summary ---
console.log("\n---------------------------------------------------");
console.log(`Tests Completed: ${testsPassed}/${testsTotal}`);
aliceClient.logout();
bobClient.logout();
if (testsPassed === testsTotal) {
console.log("ALL TESTS PASSED");
process.exit(0);
} else {
console.error("SOME TESTS FAILED");
process.exit(1);
}
}
// Generate Keys
const bytesToHex = (bytes) => Buffer.from(bytes).toString('hex');
const EPHEMERAL_SK_BYTES = generateSecretKey();
const EPHEMERAL_SK = bytesToHex(EPHEMERAL_SK_BYTES);
const EPHEMERAL_PK = getPublicKey(EPHEMERAL_SK_BYTES);
async function runInteropTest() {
// Ensure artifacts dir
if (!fs.existsSync(ARTIFACTS_DIR)) {
fs.mkdirSync(ARTIFACTS_DIR);
}
fs.writeFileSync(LOG_FILE, ''); // Clear log
let nodeClient;
let exitCode = 0;
const artifacts = {};
try {
log('--- Interop Test Started ---');
log(`Ephemeral Pubkey: ${EPHEMERAL_PK}`);
log(`Relays: ${TEST_RELAYS.join(', ')}`);
// 1. Initialize Node Client
log('Initializing NostrClient...');
nodeClient = new NostrClient();
nodeClient.relays = [...TEST_RELAYS];
nodeClient.writeRelays = [...TEST_RELAYS];
nodeClient.readRelays = [...TEST_RELAYS];
// Register ephemeral signer
await nodeClient.registerPrivateKeySigner({ privateKey: EPHEMERAL_SK });
log('Signer registered.');
// Initialize pool (lazy, but explicit init ensures connection attempts)
log('Warming up connections...');
await nodeClient.ensurePool(); // Ensure pool is created
// 2. Test A: Video Post
log('Test A: Publishing VIDEO_POST...');
const videoEventTemplate = buildVideoPostEvent({
pubkey: EPHEMERAL_PK,
created_at: Math.floor(Date.now() / 1000),
dTagValue: `interop-test-${Date.now()}`,
content: {
version: 3,
title: 'Interop Test Video',
url: 'https://example.com/video.mp4',
description: 'A video to verify protocol interoperability.',
mode: 'live',
videoRootId: `interop-root-${Date.now()}`
}
});
const { signedEvent: publishedVideo } = await nodeClient.signAndPublishEvent(videoEventTemplate, {
context: 'interop-video'
});
log(`Published Video ID: ${publishedVideo.id}`);
artifacts.videoEventId = publishedVideo.id;
// Fetch back verification with Retry
log('Verifying Video Post (fetch by ID)...');
let fetchedVideo = null;
const maxRetries = 5;
const initialDelay = 2000;
for (let i = 0; i < maxRetries; i++) {
const delay = initialDelay * (i + 1);
log(`Attempt ${i + 1}/${maxRetries}: Waiting ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
try {
fetchedVideo = await nodeClient.fetchRawEventById(publishedVideo.id);
if (fetchedVideo) {
log('Video event found!');
break;
}
} catch (e) {
log(`Fetch attempt failed: ${e.message}`);
}
}
if (!fetchedVideo) {
throw new Error(`Failed to fetch video event ${publishedVideo.id} after ${maxRetries} retries.`);
}
if (fetchedVideo.content !== publishedVideo.content) {
throw new Error('Fetched video content mismatch.');
}
log('Video Post verified successfully.');
// 3. Test B: View Event
log('Test B: Publishing VIEW_EVENT...');
const viewEventTemplate = buildViewEvent({
pubkey: EPHEMERAL_PK,
created_at: Math.floor(Date.now() / 1000),
pointerValue: publishedVideo.id, // Pointing to the video we just created
pointerTag: ['e', publishedVideo.id],
includeSessionTag: true // simulate session
});
const { signedEvent: publishedView } = await nodeClient.signAndPublishEvent(viewEventTemplate, {
context: 'interop-view'
});
log(`Published View Event ID: ${publishedView.id}`);
artifacts.viewEventId = publishedView.id;
// No fetch back for view event to save time, assuming if Video worked, this works too.
log('View Event published.');
// 4. Test C: Direct Message (Legacy NIP-04)
log('Test C: Publishing Encrypted DM...');
const dmMessage = "Interop Test Secret Message";
const signer = await nodeClient.ensureActiveSignerForPubkey(EPHEMERAL_PK);
// Encrypt (Self-DM)
const ciphertext = await signer.nip04Encrypt(EPHEMERAL_PK, dmMessage);
const dmTemplate = buildLegacyDirectMessageEvent({
pubkey: EPHEMERAL_PK,
created_at: Math.floor(Date.now() / 1000),
recipientPubkey: EPHEMERAL_PK,
ciphertext: ciphertext
});
const { signedEvent: publishedDM } = await nodeClient.signAndPublishEvent(dmTemplate, {
context: 'interop-dm'
});
log(`Published DM ID: ${publishedDM.id}`);
artifacts.dmEventId = publishedDM.id;
// Fetch and Decrypt with Retry
log('Verifying DM Decryption...');
let fetchedDM = null;
for (let i = 0; i < maxRetries; i++) {
const delay = initialDelay * (i + 1);
log(`Attempt ${i + 1}/${maxRetries}: Waiting ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
try {
fetchedDM = await nodeClient.fetchRawEventById(publishedDM.id);
if (fetchedDM) {
log('DM event found!');
break;
}
} catch (e) {
log(`Fetch attempt failed: ${e.message}`);
}
}
if (!fetchedDM) {
throw new Error(`Failed to fetch DM event ${publishedDM.id} after ${maxRetries} retries.`);
}
const decryptContext = await nodeClient.buildDmDecryptContext(EPHEMERAL_PK);
const decryptionResult = await decryptDM(fetchedDM, decryptContext);
if (!decryptionResult.ok) {
log(`Decryption failed: ${JSON.stringify(decryptionResult.errors)}`);
throw new Error('DM Decryption failed.');
}
if (decryptionResult.plaintext !== dmMessage) {
throw new Error(`Decryption mismatch. Expected "${dmMessage}", got "${decryptionResult.plaintext}"`);
}
log(`DM Decrypted successfully: "${decryptionResult.plaintext}"`);
log('--- Interop Test PASSED ---');
// Save Summary
const summary = {
timestamp: new Date().toISOString(),
status: "success",
relays: TEST_RELAYS,
ephemeralPubkey: EPHEMERAL_PK,
artifacts
};
const summaryFile = path.join(ARTIFACTS_DIR, `interop-summary-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.json`);
fs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2));
log(`Summary saved to ${summaryFile}`);
} catch (err) {
log(`--- Interop Test FAILED: ${err.message} ---`);
if (err.stack) log(err.stack);
exitCode = 1;
// Save Failure Summary
const summary = {
timestamp: new Date().toISOString(),
status: "failed",
error: err.message,
relays: TEST_RELAYS,
ephemeralPubkey: EPHEMERAL_PK,
artifacts
};
const summaryFile = path.join(ARTIFACTS_DIR, `interop-summary-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.json`);
fs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2));
} finally {
log('Cleaning up...');
if (nodeClient && nodeClient.pool) {
// Close pool connections if possible (NostrTools SimplePool might not expose close easily in all versions,
// but we can try client logic if implemented or just exit)
// The client logic might not fully close sockets immediately, process.exit will handle it.
}
process.exit(exitCode);
}
}
runInteropTest();
runTests().catch(e => {
console.error("Unhandled execution error:", e);
process.exit(1);
});
+271
View File
@@ -0,0 +1,271 @@
import './setup-test-env.js';
import { startRelay } from './load-test-relay.mjs';
import { NostrClient } from '../../js/nostr/client.js';
import { decryptDM } from '../../js/dmDecryptor.js';
import {
buildVideoPostEvent,
buildLegacyDirectMessageEvent
} from '../../js/nostrEventSchemas.js';
import { generateSecretKey, getPublicKey } from 'nostr-tools';
import { chromium } from 'playwright';
import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
const RELAY_PORT = 8899;
const RELAY_URL = `ws://localhost:${RELAY_PORT}`;
const HTTP_PORT = 8000;
const HTTP_URL = `http://localhost:${HTTP_PORT}`;
// Artifacts
const ARTIFACTS_DIR = 'artifacts';
const LOG_FILE = path.join(ARTIFACTS_DIR, `smoke-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.log`);
const SCREENSHOT_FILE = path.join(ARTIFACTS_DIR, 'smoke-test-ui.png');
// Logger
function log(message) {
const timestamp = new Date().toISOString();
const line = `[${timestamp}] ${message}`;
console.log(line);
fs.appendFileSync(LOG_FILE, line + '\n');
}
// Generate Keys
const bytesToHex = (bytes) => Buffer.from(bytes).toString('hex');
const EPHEMERAL_SK_BYTES = generateSecretKey();
const EPHEMERAL_SK = bytesToHex(EPHEMERAL_SK_BYTES);
const EPHEMERAL_PK = getPublicKey(EPHEMERAL_SK_BYTES);
async function startHttpServer() {
log('Starting HTTP Server...');
const serve = spawn('npx', ['serve', '.', '-p', String(HTTP_PORT)], {
stdio: 'ignore',
shell: true
});
const maxRetries = 20;
for (let i = 0; i < maxRetries; i++) {
try {
await fetch(HTTP_URL);
log('HTTP Server is ready.');
return serve;
} catch (e) {
await new Promise(r => setTimeout(r, 250));
}
}
serve.kill();
throw new Error('HTTP Server failed to start within timeout.');
}
async function runSmokeTest() {
// Ensure artifacts dir
if (!fs.existsSync(ARTIFACTS_DIR)) {
fs.mkdirSync(ARTIFACTS_DIR);
}
fs.writeFileSync(LOG_FILE, ''); // Clear log
let relayServer;
let httpServer;
let browser;
let nodeClient;
let exitCode = 0;
try {
log('--- Smoke Test Started ---');
log(`Ephemeral Pubkey: ${EPHEMERAL_PK}`);
// 1. Start Infrastructure
relayServer = startRelay(RELAY_PORT);
httpServer = await startHttpServer();
// 2. Initialize Node Client
log('Initializing Node Client...');
nodeClient = new NostrClient();
nodeClient.relays = [RELAY_URL];
nodeClient.writeRelays = [RELAY_URL];
nodeClient.readRelays = [RELAY_URL];
await nodeClient.registerPrivateKeySigner({ privateKey: EPHEMERAL_SK });
await nodeClient.init();
log('Node Client connected.');
// 3. Publish Video Event (Node)
log('Node: Publishing Video Post...');
const videoEventTemplate = buildVideoPostEvent({
pubkey: EPHEMERAL_PK,
created_at: Math.floor(Date.now() / 1000),
dTagValue: `smoke-test-${Date.now()}`,
content: {
version: 3,
title: 'Smoke Test Video',
url: 'https://example.com/video.mp4',
description: 'A video to verify feed rendering.',
mode: 'live',
videoRootId: `smoke-root-${Date.now()}`
}
});
const { signedEvent: publishedVideo } = await nodeClient.signAndPublishEvent(videoEventTemplate, {
context: 'smoke-video'
});
log(`Node: Published Video ID: ${publishedVideo.id}`);
// 4. Publish DM (Node -> Self)
log('Node: Publishing Encrypted DM...');
const dmMessage = "Smoke Test Secret Message";
const signer = await nodeClient.ensureActiveSignerForPubkey(EPHEMERAL_PK);
const ciphertext = await signer.nip04Encrypt(EPHEMERAL_PK, dmMessage);
const dmTemplate = buildLegacyDirectMessageEvent({
pubkey: EPHEMERAL_PK,
created_at: Math.floor(Date.now() / 1000),
recipientPubkey: EPHEMERAL_PK,
ciphertext: ciphertext
});
const { signedEvent: publishedDM } = await nodeClient.signAndPublishEvent(dmTemplate, {
context: 'smoke-dm'
});
log(`Node: Published DM ID: ${publishedDM.id}`);
// 5. Browser Test (Playwright)
log('Browser: Launching...');
browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
// Inject LocalStorage for Auto-Login and Relay Config
const savedProfiles = {
entries: [{ pubkey: EPHEMERAL_PK, authType: "nip07" }],
activePubkey: EPHEMERAL_PK,
version: 1
};
const relayList = [{ url: RELAY_URL, mode: "both" }];
// Note: Key format derived from investigation: bitvid:profile:<pubkey>:relayList:v1
const relayStorageKey = `bitvid:profile:${EPHEMERAL_PK}:relayList:v1`;
await context.addInitScript(({ profiles, relays, relayKey }) => {
// Disable Whitelist Mode to allow fresh pubkey login
window.localStorage.setItem('bitvid_admin_whitelist_mode', 'false');
window.localStorage.setItem('bitvid:savedProfiles:v1', JSON.stringify(profiles));
window.localStorage.setItem(relayKey, JSON.stringify(relays));
// Mock NIP-07 to avoid errors if app tries to use it (though we won't sign in browser)
window.nostr = {
getPublicKey: async () => profiles.activePubkey,
signEvent: async () => { throw new Error("Mock NIP-07 cannot sign"); }
};
}, { profiles: savedProfiles, relays: relayList, relayKey: relayStorageKey });
const page = await context.newPage();
log(`Browser: Navigating to ${HTTP_URL}...`);
// Capture Console
page.on('console', msg => log(`[Browser Console] ${msg.type()}: ${msg.text()}`));
page.on('pageerror', err => log(`[Browser Error] ${err.message}`));
await page.goto(HTTP_URL);
// Verify Login (Profile Button presence)
log('Browser: Verifying Login...');
// #profileButton might be hidden initially, wait for it
try {
await page.waitForSelector('#profileButton', { state: 'visible', timeout: 15000 });
log('Browser: Login verified (Profile button visible).');
} catch (e) {
log('Browser: Login verification failed (Profile button not found). Dumping localStorage...');
const storage = await page.evaluate(() => JSON.stringify(localStorage));
log(`[Browser Storage] ${storage}`);
await page.screenshot({ path: path.join(ARTIFACTS_DIR, 'smoke-fail-login.png') });
}
// Verify Feed (Video Card presence)
log('Browser: Verifying Video in Feed...');
// Look for the video title
try {
// Wait longer for feed to load (relays might be slow)
await page.waitForSelector(`text=Smoke Test Video`, { timeout: 60000 });
log('Browser: Video found in feed!');
} catch (e) {
log('Browser: Video NOT found in feed. Taking screenshot...');
await page.screenshot({ path: path.join(ARTIFACTS_DIR, 'smoke-fail-feed.png') });
// Debug: Check relays
const relayState = await page.evaluate(() => {
// Try to access global nostrClient if exposed, or check localStorage again
return {
localStorage: JSON.stringify(localStorage),
// If we can't access client directly, we rely on logs
};
});
log(`[Browser Debug] Storage: ${relayState.localStorage}`);
throw new Error('Browser: Video NOT found in feed.');
}
// Screenshot
await page.screenshot({ path: SCREENSHOT_FILE });
log(`Browser: Screenshot saved to ${SCREENSHOT_FILE}`);
// 6. Node Verification (DM Decrypt)
log('Node: Verifying DM Decryption...');
// Fetch back
await new Promise(r => setTimeout(r, 500));
const fetchedDM = await nodeClient.fetchRawEventById(publishedDM.id);
if (!fetchedDM) {
throw new Error('Node: Failed to fetch DM back.');
}
const decryptContext = await nodeClient.buildDmDecryptContext(EPHEMERAL_PK);
const decryptionResult = await decryptDM(fetchedDM, decryptContext);
if (!decryptionResult.ok) {
log(`Node: Decryption failed: ${JSON.stringify(decryptionResult.errors)}`);
throw new Error('Node: Decryption failed.');
}
if (decryptionResult.plaintext !== dmMessage) {
throw new Error(`Node: Decryption mismatch. Got "${decryptionResult.plaintext}"`);
}
log(`Node: DM Decrypted successfully: "${decryptionResult.plaintext}"`);
log('--- Smoke Test PASSED ---');
// Generate JSON Summary
const summary = {
timestamp: new Date().toISOString(),
status: "success",
details: {
videoEventId: publishedVideo.id,
dmEventId: publishedDM.id,
ephemeralPubkey: EPHEMERAL_PK
}
};
const summaryFile = path.join(ARTIFACTS_DIR, `smoke-summary-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.json`);
fs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2));
log(`Summary saved to ${summaryFile}`);
} catch (err) {
log(`--- Smoke Test FAILED: ${err.message} ---`);
if (err.stack) log(err.stack);
exitCode = 1;
} finally {
// Cleanup
log('Cleaning up...');
if (nodeClient && nodeClient.pool) {
// Close pool connections
if (typeof nodeClient.pool.close === 'function') {
nodeClient.pool.close(nodeClient.relays);
}
}
if (browser) await browser.close();
if (httpServer) httpServer.kill();
if (relayServer) await relayServer.close();
process.exit(exitCode);
}
}
runSmokeTest();
-20
View File
@@ -129,10 +129,6 @@ async function reviewPR(prNumber, baseRef) {
hasFailures = true;
commentBody += '### ⚠️ Lint Warnings/Errors\n\n';
commentBody += '```\n' + lintRes.stdout + '\n' + lintRes.stderr + '\n```\n\n';
if (lintRes.stdout.includes('stylelint') || lintRes.stderr.includes('stylelint')) {
commentBody += '> **💡 Tip:** Try running `npm run lint:css -- --fix` to automatically resolve some style issues.\n\n';
}
}
// 4. Unit Tests
@@ -142,19 +138,6 @@ async function reviewPR(prNumber, baseRef) {
hasFailures = true;
commentBody += '### ❌ Test Failures\n\n';
commentBody += '```\n' + testRes.stdout + '\n' + testRes.stderr + '\n```\n\n';
// Extract failing test files
const failingTests = [];
const failureRegex = /✖\s+(tests\/[\w\-\.\/]+)\s+failed with exit code 1/g;
const testOutput = testRes.stdout + '\n' + testRes.stderr;
let match;
while ((match = failureRegex.exec(testOutput)) !== null) {
failingTests.push(match[1]);
}
if (failingTests.length > 0) {
commentBody += `> **Suggested areas to inspect:** ${failingTests.map(f => `\`${f}\``).join(', ')}\n`;
}
commentBody += '> **Suggestion:** Inspect the stack trace above. Run `npm run test:unit` locally to reproduce.\n\n';
} else {
commentBody += '### ✅ Tests Passed\n\nAll unit tests passed.\n\n';
@@ -176,10 +159,7 @@ async function reviewPR(prNumber, baseRef) {
if (sensitiveChanges.length > 0) {
commentBody += '### 🛡️ Guardrails\n\n';
commentBody += 'This PR modifies sensitive files. **Security and Protocol Review Required.**\n';
// Explicit flags for CI/bots to pick up
commentBody += '`requires-security-review` `requires-protocol-review`\n\n';
commentBody += '<details><summary>Sensitive Files Touched</summary>\n\n';
sensitiveChanges.forEach(f => commentBody += `- ${f}\n`);
commentBody += '\n</details>\n\n';
+1 -1
View File
@@ -42,7 +42,7 @@ const VALUE_ALLOWLIST = new Map([
]);
const MARKUP_EXTENSIONS = new Set([".html", ".md", ".mdx", ".markdown", ".njk", ".nunjucks"]);
const MARKUP_IGNORED_DIRECTORIES = new Set(["node_modules", ".git", "issues", "scripts"]);
const MARKUP_IGNORED_DIRECTORIES = new Set(["node_modules", ".git"]);
const CLASS_ATTRIBUTE_PATTERN = /class\s*=\s*(["'])(.*?)\1/gis;
const BRACKET_UTILITY_PATTERN = /\[[^\]]+\]/g;
const CHECK_MODES = new Set(["all", "tokens", "brackets"]);
+1 -2
View File
@@ -32,8 +32,7 @@ const IGNORED_GLOBS = [
'tests/visual/embed-layout.spec.ts',
'ai/**',
'**/*.ai',
'issues/**',
'scripts/agent/**'
'releases/**'
];
const gitArgs = [
-2
View File
@@ -26,8 +26,6 @@ const IGNORED_DIRS = new Set([
"logs",
"playwright-report",
"test-results",
"issues",
"scripts",
]);
const DIST_SCAN_PREFIXES = new Set([