const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const root = path.join(__dirname, '..'); const read = file => fs.readFileSync(path.join(root, file), 'utf8'); test('anyone signed in can generate here — no moderator gate', () => { const route = read('src/routes/myResources.js'); // This existed alongside Learning Hub, which was moderator-owned, so that not // being a moderator did not mean not being able to generate anything at all. // Learning Hub is retired and this is the only generator left; the absence of // a role gate still matters, and is still worth pinning. assert.doesNotMatch(route, /moderatorMiddleware/); assert.match(route, /router\.use\('\/my-resources', authMiddleware\)/, 'signed in is the only requirement, and the gate names its own prefix'); }); test('nothing here can return another person’s work', () => { const route = read('src/routes/myResources.js'); // Every statement that touches the table filters on the owner. A missing // WHERE clause here is the whole risk, so it is asserted rather than assumed. const statements = route.match(/'(SELECT|UPDATE|DELETE|INSERT)[^']*(?:' \+\s*\n\s*'[^']*)*'/g) || []; const touching = statements.filter(s => /user_resources/.test(s)); assert.ok(touching.length >= 5, 'expected the table statements to be found'); for (const s of touching) { if (/^'INSERT/.test(s)) continue; // supplies user_id as a value instead // A share link is resolved by its token — the token is the credential — // and what it reveals is the title and who shared it, never the content. if (/token_hash = \?/.test(s)) continue; assert.match(s, /user_id = \?/, 'every read and write is scoped to the owner: ' + s.slice(0, 60)); } assert.match(route, /INSERT INTO user_resources \(user_id,/, 'and an insert records one'); }); test('markdown is the artifact; every format is rendered from it', () => { const exporter = read('src/utils/documentExport.js'); // Refining means editing text, never patching a binary — which is what makes // "change slide 4" possible at all. assert.match(exporter, /async function render\(markdown, kind, format, options\)/); assert.match(exporter, /var office = kind === 'presentation' \? 'pptx' : 'docx';/); assert.match(exporter, /--reference-doc=' \+ REFERENCE_DECK/, 'decks keep the house template'); // PDF goes through Gotenberg because pandoc ships no PDF engine in this // image, and converting the office file preserves the deck's layout. assert.match(exporter, /forms\/libreoffice\/convert/); assert.match(exporter, /AbortSignal\.timeout\(90000\)/, 'and cannot hang a request'); // Temporary directories are always cleaned, including on failure. assert.match(exporter, /\} finally \{[\s\S]{0,200}rm\(workdir, \{ recursive: true, force: true \}\)/); }); test('a failed PDF says so, because the other two formats still work', () => { const route = read('src/routes/myResources.js'); assert.match(route, /PDF conversion is unavailable right now\. PowerPoint and Word still work\./); // Gotenberg is a different stack; PDF is the one export allowed to fail. assert.match(read('src/utils/documentExport.js'), /GOTENBERG_URL \|\| 'http:\/\/gotenberg:3000'/); assert.match(read('docker-compose.yml'), /danvics_convert/, 'and ped-ai is on its network'); }); test('the generated markdown is told the rules pandoc enforces', () => { const route = read('src/routes/myResources.js'); // The same rules the Learning prompt carries, found by rendering decks and // looking at them: a table alone on its slide, blank lines around it, no // "Slide 3:" prefixes, no deep nesting. assert.match(route, /A slide containing a table contains ONLY that table/); assert.match(route, /a table needs a blank line/); assert.match(route, /the heading is the slide\\'s subject, not "Slide 3:"/); // And grounded resources cite only at the end. assert.match(route, /Do NOT cite in the body/); assert.match(route, /In a presentation that is the final slide, titled References/); }); test('a library has a ceiling, and generation says when it is reached', () => { const route = read('src/routes/myResources.js'); assert.match(route, /var MAX_PER_USER = 100;/); assert.match(route, /You have reached ' \+ MAX_PER_USER \+ ' saved resources/); // The count is per owner, so one person filling their library cannot stop // anyone else generating. assert.match(route, /SELECT COUNT\(\*\)::int AS n FROM user_resources WHERE user_id = \?/); }); test('the screen is reachable by anyone signed in, and states that it is private', () => { const index = read('public/index.html'); const component = read('public/components/my-resources.html'); // A menu item of its own, next to the Learning Hub: related, not the same // thing, and sitting together is how someone discovers the difference. assert.match(index, /