56 lines
3 KiB
JavaScript
56 lines
3 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const { pathToFileURL } = require('node:url');
|
|
const { normalizeMcpSearchResponse } = require('../src/utils/clinicalRetrieval');
|
|
|
|
async function sourcesModule() {
|
|
return import(pathToFileURL(path.join(__dirname, '..', 'public/js/assistant/sources.js')).href);
|
|
}
|
|
|
|
test('assistant source excerpts hide image markdown but keep OCR text', async () => {
|
|
const { renderSourcesList } = await sourcesModule();
|
|
const sources = normalizeMcpSearchResponse({ results: [{ title: 'Nelson', excerpt: ' AGE STREAMS OF DEVELOPMENT' }] });
|
|
const html = renderSourcesList(sources);
|
|
|
|
assert.match(html, /AGE STREAMS OF DEVELOPMENT/);
|
|
assert.doesNotMatch(html, /tmp\/pdf-images|!\[\]/);
|
|
});
|
|
|
|
test('actual source rendering preserves full bounded tables, whitespace and escaping', async () => {
|
|
const { JSDOM } = require('jsdom');
|
|
const { renderSourcesList } = await sourcesModule();
|
|
const rows = Array.from({ length: 70 }, (_, i) => '| Drug ' + i + ' | 2 mg/kg |');
|
|
const sources = normalizeMcpSearchResponse({ results: [{
|
|
id: 42, title: 'Fixture', page_number: 7,
|
|
excerpt: 'Table 1. Dose\n\n| Drug | Dose |\n|---|---|\n' + rows.join('\n') + '\n\nNote: Synthetic values only.'
|
|
}] });
|
|
assert.ok(sources[0].excerpt.length > 900);
|
|
assert.ok(sources[0].excerpt.length <= 1800);
|
|
const doc = new JSDOM(renderSourcesList(sources)).window.document;
|
|
const excerpt = doc.querySelector('.assistant-source-excerpt p');
|
|
assert.equal(excerpt.textContent, sources[0].excerpt);
|
|
assert.equal(excerpt.style.whiteSpace, 'pre-wrap');
|
|
assert.match(excerpt.textContent, /\| Drug \| Dose \|\n\|---\|---\|\n/);
|
|
assert.match(excerpt.textContent, /Note: Synthetic values only\.$/);
|
|
assert.match(doc.querySelector('.assistant-source-meta').textContent, /page 7/);
|
|
|
|
const unsafe = '<img src=x onerror=alert(1)>\n| A & B | <script>x</script> |';
|
|
const escaped = new JSDOM(renderSourcesList([{ title: '<b>Title</b>', excerpt: unsafe }])).window.document;
|
|
assert.equal(escaped.querySelector('.assistant-source-excerpt').textContent, unsafe);
|
|
assert.equal(escaped.querySelector('img, script, b'), null);
|
|
});
|
|
|
|
test('legacy source numbers remain text and cannot inject attributes or markup', async () => {
|
|
const { JSDOM } = require('jsdom');
|
|
const { renderSourcesList } = await sourcesModule();
|
|
for (const number of ['1" onclick="alert(1)', '1"><img src=x onerror=alert(1)>', "1'><script>alert(1)</script>", '1', 2]) {
|
|
const dom = new JSDOM(renderSourcesList([{ number, title: 'Legacy source' }]));
|
|
const card = dom.window.document.querySelector('.assistant-source');
|
|
assert.equal(card.id, 'assistant-source-' + number);
|
|
assert.equal(card.querySelector('strong').textContent, '[' + number + '] Legacy source');
|
|
assert.deepEqual(card.getAttributeNames().sort(), ['class', 'id']);
|
|
assert.equal(dom.window.document.querySelector('img, script, [onclick], [onerror]'), null);
|
|
dom.window.close();
|
|
}
|
|
});
|