58 lines
3.5 KiB
JavaScript
58 lines
3.5 KiB
JavaScript
import test from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import {CaptureBridge, captureRequested} from '../src/capture-bridge.ts';
|
||
|
|
|
||
|
|
const ready = () => new Response(JSON.stringify({mode: 'readme-capture', ready: true, token: 'a'.repeat(64)}));
|
||
|
|
|
||
|
|
test('ordinary previews and remote pages never initialize a capture bridge', async () => {
|
||
|
|
const bridge = new CaptureBridge();
|
||
|
|
let called = 0;
|
||
|
|
for (const href of ['http://127.0.0.1:1431/', 'https://example.com/?capture=1', 'file:///tmp/demo?capture=1', 'http://127.0.0.1:1431/?capture=0']) {
|
||
|
|
assert.equal(captureRequested(href), false);
|
||
|
|
assert.equal(await bridge.initialize(href, async () => { called++; return ready(); }), false);
|
||
|
|
}
|
||
|
|
assert.equal(called, 0);
|
||
|
|
assert.equal(bridge.active, false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('ready identity and nonce are required before any command dispatch', async () => {
|
||
|
|
for (const info of [{mode:'other',ready:true,token:'a'.repeat(64)}, {mode:'readme-capture',ready:false,token:'a'.repeat(64)}, {mode:'readme-capture',ready:true,token:'short'}]) {
|
||
|
|
const bridge = new CaptureBridge();
|
||
|
|
await assert.rejects(bridge.initialize('http://127.0.0.1:1431/?capture=1', async () => new Response(JSON.stringify(info))));
|
||
|
|
assert.equal(bridge.active, false);
|
||
|
|
await assert.rejects(bridge.invoke('stats', {}, async () => assert.fail('Must not dispatch')));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('read-only commands use authenticated same-origin requests and mutation is rejected', async () => {
|
||
|
|
const bridge = new CaptureBridge();
|
||
|
|
await bridge.initialize('http://127.0.0.1:1431/?capture=1', async () => ready());
|
||
|
|
let request;
|
||
|
|
const result = await bridge.invoke('search_measured', {query:'real query'}, async (url, options) => {
|
||
|
|
request = {url, options}; return new Response(JSON.stringify({results:[],metrics:{total_ms:1}}));
|
||
|
|
});
|
||
|
|
assert.equal(result.metrics.total_ms, 1);
|
||
|
|
assert.equal(request.url, 'http://127.0.0.1:1431/capture-api/search_measured');
|
||
|
|
assert.equal(request.options.headers['X-Searchi-Capture-Token'], 'a'.repeat(64));
|
||
|
|
assert.equal(request.options.redirect, 'error');
|
||
|
|
assert.equal(request.options.credentials, 'omit');
|
||
|
|
for (const command of ['open_result','index_folder','set_cpu_threads','../../other']) {
|
||
|
|
await assert.rejects(bridge.invoke(command, {}, async () => assert.fail('Mutation must not dispatch')), /read-only/);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a failed reinitialization cannot retain earlier capture authorization', async () => {
|
||
|
|
const bridge = new CaptureBridge();
|
||
|
|
await bridge.initialize('http://127.0.0.1:1431/?capture=1', async () => ready());
|
||
|
|
await assert.rejects(bridge.initialize('http://127.0.0.1:1431/?capture=1', async () => new Response('{}')));
|
||
|
|
assert.equal(bridge.active, false);
|
||
|
|
await assert.rejects(bridge.invoke('stats', {}, async () => assert.fail('No stale authorization')));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('media resolver rejects file paths, remote URLs and traversal; failed measured payload survives', async () => {
|
||
|
|
const bridge = new CaptureBridge();
|
||
|
|
await bridge.initialize('http://127.0.0.1:1431/?capture=1', async () => ready());
|
||
|
|
assert.equal(bridge.mediaURL('/capture-media/cat'), 'http://127.0.0.1:1431/capture-media/cat');
|
||
|
|
for (const path of ['C:/secret.jpg','https://example.com/cat','//example.com/cat','/capture-media/../secret','/capture-media/%2e%2e','/capture-media/cat?token=other']) assert.throws(() => bridge.mediaURL(path));
|
||
|
|
await assert.rejects(bridge.invoke('hybrid_search_measured', {}, async () => new Response(JSON.stringify({message:'busy',metrics:{total_ms:4}}), {status:503})), error => error.message === 'busy' && error.metrics.total_ms === 4);
|
||
|
|
});
|