// Run only with scripts/test-generated-images.sh: disposable internal-network PG + private S3.
const test = require('node:test');
const assert = require('node:assert/strict');
const { Pool } = require('pg');
const { createImageService, requestKey } = require('../src/utils/generatedImages');
const { createStorage, inspect } = require('../src/utils/generatedImageStorage');
const links = require('../src/utils/generatedImageLinks');
const { savedChatPayload } = require('../src/utils/clinicalConversation');
const revisions = require('../src/utils/promptRevisions');
const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aL9sAAAAASUVORK5CYII=', 'base64');
if (!process.env.GENERATED_IMAGES_TEST_DB || new URL(process.env.GENERATED_IMAGES_TEST_DB).hostname !== 'test-pg' || new URL(process.env.GENERATED_IMAGES_TEST_DB).pathname !== '/image_lane') throw Error('Use the disposable test script, never an application database');
const pool = new Pool({ connectionString: process.env.GENERATED_IMAGES_TEST_DB });
const db = { pool, query: (sql, params) => pool.query(sql, params), async all(sql, params) { return (await pool.query(sql, params)).rows; }, async get(sql, params) { return (await pool.query(sql, params)).rows[0]; } };
let paid = 0;
const generate = async () => { paid++; return inspect(png); };
const storage = createStorage();
const service = () => createImageService({ db, storage, generate });
let image;
test.before(async () => {
await pool.query('CREATE TABLE users(id INTEGER PRIMARY KEY); INSERT INTO users VALUES(101),(102),(103); CREATE TABLE app_settings(key TEXT PRIMARY KEY,value TEXT,updated_at TIMESTAMPTZ DEFAULT NOW()); CREATE TABLE learning_content(id SERIAL PRIMARY KEY,body TEXT,published BOOLEAN DEFAULT false);');
for (const migration of ['1777700000000_add-prompt-revisions','1777800000000_generated-images','1777900000000_image-context']) {
let sql; require('../migrations/' + migration).up({ sql: value => { sql = value; } }); await pool.query(sql);
}
await pool.query("INSERT INTO app_settings(key,value) VALUES('learning_hub.image_model','synthetic-learning-image'),('clinical_assistant.image_model','synthetic-clinical-image')");
});
test.after(async () => { storage.close(); await pool.end(); });
test('private S3 asset, encrypted durable snapshot, idempotency and owner/workflow isolation', async () => {
const jobs = service(); const before = paid;
const input = { prompt: 'Synthetic flowchart comparison', layout: 'portrait' };
image = await jobs.enqueue(101, 'clinical_assistant', input, 'first');
const dup = await jobs.enqueue(101, 'clinical_assistant', input, 'first'); assert.equal(dup.jobId, image.jobId);
await assert.rejects(jobs.enqueue(101, 'clinical_assistant', { prompt: 'different' }, 'first'), e => e.statusCode === 409);
const row = await db.get('SELECT * FROM generated_image_jobs WHERE id=$1', [image.jobId]);
assert.match(row.prompt_cipher, /^enc1:/); assert.ok(!row.prompt_cipher.includes(input.prompt)); assert.equal(row.budget, 32000); assert.equal(row.model, 'synthetic-clinical-image');
await jobs.tick(); assert.equal(paid, before + 1);
const done = await jobs.get(image.jobId, 101, 'clinical_assistant'); assert.equal(done.status, 'done'); assert.equal(done.imageUrl, '/api/generated-images/' + image.jobId);
assert.deepEqual((await jobs.asset(image.jobId, { id: 101 })).bytes, png);
await assert.rejects(jobs.get(image.jobId, 102, 'clinical_assistant'), e => e.statusCode === 404);
await assert.rejects(jobs.get(image.jobId, 101, 'learning_hub'), e => e.statusCode === 404);
await assert.rejects(jobs.asset(image.jobId, { id: 102, role: 'admin' }), e => e.statusCode === 404);
assert.equal((await db.get('SELECT staged_bytes FROM generated_image_jobs WHERE id=$1', [image.jobId])).staged_bytes, null);
const anonymous = await fetch('http://test-s3:9000/generated-images/assets/' + image.jobId); assert.equal(anonymous.status, 403);
});
test('saved chat keeps exact body/citations, validates owned asset and durable job references', async () => {
const body = '## Exact\nDose [3, 1].\n| A | Source |\n| --- | --- |\n| 5 mg | [1] |';
const sources = [{ number: 3, page: 19, title: 'Synthetic three' }, { number: 1, page: 4, title: 'Synthetic one' }];
const payload = savedChatPayload({ messages: [{ role: 'assistant', content: body, sources, imageJobs: [image] }], lastAnswer: body, sources, generatedImage: '/api/generated-images/' + image.jobId });
await links.validateChat(db, payload, 101);
assert.equal(payload.messages[0].content, body); assert.deepEqual(payload.sources, sources);
assert.deepEqual(JSON.parse(JSON.stringify(payload)), payload);
await assert.rejects(links.validateChat(db, payload, 102), e => e.statusCode === 403);
});
test('Learning has independent model/prompt revision and authenticated current-publication grants; clinical UUID cannot be published', async () => {
const jobs = service();
const revision = await revisions.mutate(db, 'learning_hub.image_behavior', { action: 'save', value: 'Synthetic learning instructions.', expectedRevision: 0, actor: 101 });
const job = await jobs.enqueue(101, 'learning_hub', { prompt: 'Learning diagram' }, 'learning');
const row = await db.get('SELECT * FROM generated_image_jobs WHERE id=$1', [job.jobId]);
assert.equal(row.prompt_revision, revision.revision); assert.equal(row.model, 'synthetic-learning-image');
assert.match(require('../src/utils/crypto').decryptString(row.prompt_cipher), /Synthetic learning instructions/);
const changed = await revisions.mutate(db, 'learning_hub.image_behavior', { action: 'save', value: 'Second synthetic Learning instructions.', expectedRevision: revision.revision, actor: 101 });
const restored = await revisions.mutate(db, 'learning_hub.image_behavior', { action: 'restore', revisionId: revision.revision, expectedRevision: changed.revision, actor: 101 });
assert.equal(restored.value, revision.value);
assert.equal((await revisions.read(db, 'learning_hub.image_behavior', restored.revision)).restoredFrom, revision.revision);
assert.equal((await db.get('SELECT prompt_revision FROM generated_image_jobs WHERE id=$1', [job.jobId])).prompt_revision, revision.revision);
await jobs.tick();
const id = (await db.query("INSERT INTO learning_content(body,published) VALUES('',false) RETURNING id")).rows[0].id;
const client = await pool.connect();
try {
await client.query('BEGIN');
await assert.rejects(links.validateLearning(client, '', 101, id), e => e.statusCode === 403);
const ids = await links.validateLearning(client, '
', 101, id);
await links.setLinks(client, id, ids); await client.query('COMMIT');
} finally { client.release(); }
await assert.rejects(jobs.asset(job.jobId, { id: 102, role: 'user' }), e => e.statusCode === 404);
assert.deepEqual((await jobs.asset(job.jobId, { id: 102, role: 'moderator' })).bytes, png);
await db.query('UPDATE learning_content SET published=true WHERE id=$1', [id]);
assert.deepEqual((await jobs.asset(job.jobId, { id: 102, role: 'user' })).bytes, png);
await db.query('UPDATE learning_content SET published=false WHERE id=$1', [id]);
await assert.rejects(jobs.asset(job.jobId, { id: 102, role: 'user' }), e => e.statusCode === 404);
await db.query('DELETE FROM generated_image_links WHERE content_id=$1', [id]);
await assert.rejects(jobs.asset(job.jobId, { id: 102, role: 'moderator' }), e => e.statusCode === 404);
await assert.rejects(links.validateLearning(db, '/api/generated-images/' + job.jobId, 102, id), e => e.statusCode === 403);
await assert.rejects(db.query('INSERT INTO generated_image_links VALUES($1,$2)', [image.jobId, id]), /Only Learning assets/);
await assert.rejects(db.query("UPDATE generated_image_jobs SET workflow='learning_hub' WHERE id=$1", [image.jobId]), /immutable/);
});
test('restart resumes queued/storage stages, but never retries ambiguous paid stages; lease fencing and SKIP LOCKED are native', async () => {
const jobs = service(); const before = paid;
const queued = await jobs.enqueue(101, 'clinical_assistant', { prompt: 'Queued across restart' }, 'restart');
await service().tick(); assert.equal((await jobs.get(queued.jobId,101,'clinical_assistant')).status, 'done');
const unknown = await jobs.enqueue(101, 'clinical_assistant', { prompt: 'Crash during provider request' }, 'unknown');
const old = await jobs.claim(); assert.equal(old.id, unknown.jobId);
await db.query("UPDATE generated_image_jobs SET lease_until=NOW()-interval '1 second' WHERE id=$1", [unknown.jobId]);
await service().tick(); assert.equal((await jobs.get(unknown.jobId,101,'clinical_assistant')).outcome, 'unknown');
assert.equal(paid, before + 1);
const fenced = await db.query("UPDATE generated_image_jobs SET stage='storing' WHERE id=$1 AND lease_token=$2 AND stage='generating' RETURNING id", [old.id,old.lease_token]); assert.equal(fenced.rows.length, 0);
const storing = await jobs.enqueue(101,'clinical_assistant',{ prompt: 'Storage crash' },'storage');
await createImageService({ db, generate, storage: { ...storage, put: async () => { throw Error('synthetic unavailable'); } } }).tick();
const staged = await db.get('SELECT stage,staged_bytes FROM generated_image_jobs WHERE id=$1', [storing.jobId]);
assert.equal(staged.stage, 'storing'); assert.notDeepEqual(staged.staged_bytes, png);
assert.deepEqual(require('../src/utils/crypto').decryptBuffer(staged.staged_bytes), png);
await db.query("UPDATE generated_image_jobs SET lease_until=NOW()-interval '1 second' WHERE id=$1",[storing.jobId]);
await service().tick(); assert.equal((await jobs.get(storing.jobId,101,'clinical_assistant')).status, 'done'); assert.equal(paid,before+2);
const first = await jobs.enqueue(101,'clinical_assistant',{ prompt:'lock one' },'lock-one');
const second = await jobs.enqueue(102,'clinical_assistant',{ prompt:'lock two' },'lock-two');
const client = await pool.connect();
try {
await client.query('BEGIN'); await client.query('SELECT id FROM generated_image_jobs WHERE id=$1 FOR UPDATE',[first.jobId]);
const claim = await service().claim(); assert.equal(claim.id, second.jobId);
await client.query('COMMIT');
} finally { client.release(); }
await db.query("UPDATE generated_image_jobs SET stage='interrupted' WHERE id=ANY($1::uuid[])", [[first.jobId,second.jobId]]);
});
test('budget exact UTF16 assembly, malformed input and missing storage prevent paid calls; provider timeout is explicit unknown', async () => {
const jobs = service(); const before = paid;
await db.query("INSERT INTO app_settings(key,value) VALUES('clinical_assistant.image_behavior','X'),('clinical_assistant.image_budget','1000')");
const base = await jobs.snapshot('clinical_assistant',{ prompt:'x',layout:'square' });
const prompt = '😀'.repeat(Math.floor((1001-base.rendered.length)/2)) + ('x'.repeat((1001-base.rendered.length)%2));
const exact = await jobs.snapshot('clinical_assistant',{prompt,layout:'square'}); assert.equal(exact.rendered.length, 1000);
await assert.rejects(jobs.enqueue(101,'clinical_assistant',{prompt:prompt+'x',layout:'square'},'large'), e => e.statusCode === 413);
for (const input of [{prompt:''},{prompt:'x',model:'forbidden'},{prompt:'x',layout:'url'},{prompt:'x'.repeat(32001)}]) await assert.rejects(jobs.enqueue(101,'clinical_assistant',input,'bad'));
const unavailable = createImageService({ db, generate, storage: { ready: async () => { throw Error('synthetic storage outage'); } } });
await assert.rejects(unavailable.enqueue(101,'clinical_assistant',{prompt:'no call'},'offline'),e => e.statusCode === 503);
assert.equal(paid,before);
const unknown = await jobs.enqueue(101,'clinical_assistant',{prompt:'ambiguous timeout'},'timeout');
await createImageService({ db, storage, generate: async () => { paid++; throw Error('synthetic timeout after possible billing'); } }).tick();
await jobs.tick(); assert.equal(paid,before+1); assert.equal((await jobs.get(unknown.jobId,101,'clinical_assistant')).outcome,'unknown');
});
test('actual authenticated asset/settings and Learning content write routes enforce grants and publication atomically', async () => {
const express = require('express'); const fs = require('fs'); const vm = require('vm'); const jwt = require('jsonwebtoken');
const jobs = service();
await pool.query("ALTER TABLE learning_content ADD title TEXT, ADD slug TEXT, ADD category_id INTEGER, ADD subject TEXT, ADD content_type TEXT, ADD author_id INTEGER, ADD updated_at TIMESTAMPTZ DEFAULT NOW()");
const convert = sql => { let n=0; return sql.replace(/\?/g,()=>'$'+(++n)); };
let lockNotice;
const routeDb = { ...db, pool: { async connect() {
const client=await pool.connect(); return { release:()=>client.release(), query(sql,params) {
if(sql.includes('FOR UPDATE') && lockNotice) { lockNotice(); lockNotice=null; }
return client.query(sql,params);
} };
} }, getSetting: async key => (await db.get('SELECT value FROM app_settings WHERE key=$1',[key]))?.value,
async get(sql,params) { return db.get(convert(sql),params); },
async all(sql,params) { return db.all(convert(sql),params); },
async run(sql,params) { const r=await db.query(convert(sql),params);return {lastInsertRowid:r.rows[0]?.id,changes:r.rowCount}; }
};
function load(file,mocks) {
const module={exports:{}};
vm.runInNewContext(fs.readFileSync(file,'utf8'),{module,Buffer,console:{warn(){},error(){}},process:{env:{JWT_SECRET:'synthetic-signing-only',CLINICAL_ASSISTANT_MCP_WARMUP:'false'}},setTimeout(){},require:n=>{assert.ok(n in mocks,n);return mocks[n];}});
return module.exports;
}
const roles={101:'admin',102:'user',103:'moderator'};
const auth = load('src/middleware/auth.js',{'jsonwebtoken':jwt,'../db/database':{get:async(sql,params)=>sql.includes('user_sessions')?{id:1,last_activity:new Date()}:roles[params[0]]?{id:params[0],role:roles[params[0]]}:null},'../utils/sessions':{hashToken:()=> 'synthetic-hash'},'../utils/platform':{isMobileClient:()=>false}});
const imageRoutes=load('src/routes/generatedImages.js',{'express':express,'../middleware/auth':auth,'../utils/generatedImages':{...require('../src/utils/generatedImages'),service:()=>jobs},'../db/database':routeDb});
const learningRoutes=load('src/routes/learningAdmin.js',{'express':express,'../db/database':routeDb,'../middleware/auth':auth,'../utils/embeddings':{isEmbeddingsAvailable:()=>false},'../utils/generatedImageLinks':links});
await db.query('CREATE TABLE clinical_assistant_chats(id SERIAL PRIMARY KEY,user_id INTEGER,title TEXT,payload TEXT,created_at TIMESTAMPTZ DEFAULT NOW(),updated_at TIMESTAMPTZ DEFAULT NOW())');
const learningAI=load('src/routes/learningAI.js',{
express,multer:require('multer'),axios:{},path:require('path'),'../utils/ai':{},'../utils/imageTool':require('../src/utils/imageTool'),
'../middleware/auth':auth,'../db/database':routeDb,'../utils/crypto':require('../src/utils/crypto'),'../utils/urlSafety':require('../src/utils/urlSafety'),
'../utils/policy':{requireFeature:()=>()=>{}},'../utils/generatedImageLinks':links,'../utils/generatedImages':{service:()=>jobs},'pptxgenjs':require('pptxgenjs')
});
await db.query('CREATE TABLE learning_categories(id SERIAL PRIMARY KEY,name TEXT);CREATE TABLE learning_questions(id SERIAL PRIMARY KEY,content_id INTEGER,sort_order INTEGER)');
const clinicalRoutes = load('src/routes/clinicalAssistant.js', {
express, axios: {}, crypto: require('crypto'), '../db/database': routeDb, '../middleware/auth': auth,
'../utils/ai': {}, '../utils/generatedImages': { ...require('../src/utils/generatedImages'), service: () => jobs },
'../utils/imageTool': require('../src/utils/imageTool'), '../utils/generatedImageLinks': links,
'../utils/logger': { audit() {}, error() {} }, '../utils/crypto': require('../src/utils/crypto'), '../utils/redis': {},
'../utils/clinicalPromptPool': { createClinicalPromptPool: () => ({}) }, '../utils/clinicalMcpClient': {}, '../utils/clinicalRetrieval': {},
'../utils/clinicalPrompts': require('../src/utils/clinicalPrompts'), '../utils/clinicalConversation': require('../src/utils/clinicalConversation'), '../utils/clinicalAnswer': require('../src/utils/clinicalAnswer')
});
// Execute REAL server registrations in order, including the actual blanket adminConfig guard.
const configRoutes=load('src/routes/adminConfig.js',{
express,'../db/database':routeDb,'../middleware/auth':auth,'../utils/prompts':{getAllPrompts:()=>[]},
'../utils/promptCatalog':{},'../utils/promptRevisions':{},'../utils/clinicalConversation':require('../src/utils/clinicalConversation'),
'../utils/logger':{},'../utils/errors':{},'../utils/ttsProvider':{},'../utils/litellm':{},'../utils/sttProvider':{},'../utils/embeddings':{}
});
const app=express();app.use(express.json());
const composition=fs.readFileSync('server.js','utf8');
vm.runInNewContext(composition.slice(composition.indexOf('// Routes\n'),composition.indexOf('// User-level preference:')),{
app,APP_VERSION:'synthetic',process:{env:{}},require(name){
if(name==='./src/routes/generatedImages') return imageRoutes;
if(name==='./src/routes/learningAdmin') return learningRoutes;
if(name==='./src/routes/learningAI') return learningAI;
if(name==='./src/routes/clinicalAssistant') return clinicalRoutes;
if(name==='./src/routes/adminConfig') return configRoutes;
if(name==='./src/middleware/auth') return auth;
if(name==='./src/db/database') return routeDb;
if(name==='./src/utils/models') return {activeProvider:'synthetic',getAvailableModelsWithOverrides:async()=>[],getEffectiveDefaultModel:async()=>''};
if(name==='./src/utils/ai') return {};
if(name==='./src/utils/generatedImages') return {service:()=>({start(){}})};
assert.match(name,/^\.\/src\/routes\//);return express.Router(); // no unrelated application modules/services
}
});
const server=app.listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));
const base='http://127.0.0.1:'+server.address().port;
const request=(path,owner,method='GET',body)=>fetch(base+path,{method,headers:{'Content-Type':'application/json',...(owner?{Authorization:'Bearer '+jwt.sign({userId:owner},'synthetic-signing-only')}: {})},body:body?JSON.stringify(body):undefined});
try {
assert.equal((await request('/api/health')).status,200);
assert.equal((await request('/api/models')).status,200);
assert.equal((await request('/api/admin/learning/image/jobs',null,'POST',{prompt:'denied'})).status,401);
assert.equal((await request('/api/admin/learning/image/jobs',102,'POST',{prompt:'denied'})).status,403);
assert.equal((await request('/api/admin/config',103)).status,403);
const moderatorJob=await request('/api/admin/learning/image/jobs',103,'POST',{prompt:'Moderator image',idempotencyKey:'moderator-image'});
assert.equal(moderatorJob.status,200,await moderatorJob.clone().text());
const moderatorId=(await moderatorJob.json()).jobId;
assert.equal((await request('/api/admin/learning/image/jobs/'+moderatorId,103)).status,200);await jobs.tick();
assert.equal((await request('/api/generated-images/'+image.jobId)).status,401);
assert.equal((await request('/api/generated-images/'+image.jobId,102)).status,404);
const bytes=await request('/api/generated-images/'+image.jobId+'?download=1',101);
assert.equal(bytes.status,200);assert.equal(bytes.headers.get('x-image-owner'),'101');assert.equal(bytes.headers.get('cache-control'),'private, no-store');assert.equal(bytes.headers.get('x-content-type-options'),'nosniff');assert.deepEqual(Buffer.from(await bytes.arrayBuffer()),png);
const denied=await request('/api/admin/learning/content',101,'POST',{title:'Forbidden',body:'
',published:true});assert.equal(denied.status,403);
assert.equal((await db.get("SELECT COUNT(*)::int AS n FROM learning_content WHERE title='Forbidden'")).n,0);
const job=await jobs.enqueue(101,'learning_hub',{prompt:'Attach through actual CMS'},'route-attach');await jobs.tick();
const draft=await request('/api/admin/learning/content',101,'POST',{title:'Teaching',body:'
Exact body.
Concurrent body [3].