// 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.

',published:false});assert.equal(draft.status,200);const contentId=(await draft.json()).id; assert.equal((await request('/api/generated-images/'+job.jobId,102)).status,404); assert.equal((await request('/api/generated-images/'+job.jobId,103)).status,200); assert.equal((await request('/api/admin/learning/content/'+contentId,103,'PUT',{published:true})).status,200); assert.equal((await request('/api/generated-images/'+job.jobId,102)).status,200); assert.equal((await request('/api/admin/learning/content/'+contentId,103,'PUT',{published:false})).status,200); assert.equal((await request('/api/generated-images/'+job.jobId,102)).status,404); // Concurrent unpublish holds the row while a body-only request reaches its lock. await db.query('UPDATE learning_content SET published=true WHERE id=$1',[contentId]); const unpublish=await pool.connect(); try { await unpublish.query('BEGIN');await unpublish.query('UPDATE learning_content SET published=false WHERE id=$1',[contentId]); let lockReached;const atLock=new Promise(r=>{lockReached=r;});lockNotice=lockReached; const update=request('/api/admin/learning/content/'+contentId,103,'PUT',{body:'

Concurrent body [3].

'}); await atLock;await unpublish.query('COMMIT');assert.equal((await update).status,200); assert.equal((await db.get('SELECT published FROM learning_content WHERE id=$1',[contentId])).published,false,'body edit must not restore stale publication'); assert.equal((await request('/api/generated-images/'+job.jobId,102)).status,404); assert.equal((await db.get('SELECT COUNT(*)::int AS n FROM generated_image_links WHERE content_id=$1',[contentId])).n,1); } finally { await unpublish.query('ROLLBACK');unpublish.release(); } const markdown='---\nmarp: true\n---\n# Original slide [3]\nDose 5 mg, page 19 [3].\n---\n# Image\n![Generated teaching visual](/api/generated-images/'+job.jobId+')\n'; const presentation=await request('/api/admin/learning/content',101,'POST',{title:'Presentation',content_type:'presentation',body:markdown,published:false}); assert.equal(presentation.status,200);const presentationId=(await presentation.json()).id; const reopenedPresentation=await request('/api/admin/learning/content/'+presentationId,103); assert.equal(reopenedPresentation.status,200);assert.equal((await reopenedPresentation.json()).content.body,markdown); const pptx=await request('/api/admin/learning/generate-pptx',103,'POST',{markdown,title:'Synthetic presentation'}); assert.equal(pptx.status,200,await pptx.clone().text()); const zip=await require('jszip').loadAsync(Buffer.from(await pptx.arrayBuffer())); const media=Object.keys(zip.files).filter(f=>/^ppt\/media\/.+\.png$/.test(f));assert.equal(media.length,1); assert.deepEqual(await zip.files[media[0]].async('nodebuffer'),png); assert.match(await zip.files['ppt/slides/slide1.xml'].async('string'),/Original slide \[3\]/); assert.equal((await request('/api/admin/learning/content',102,'POST',{title:'No permission'})).status,403); assert.equal((await request('/api/admin/image-settings/learning_hub',103,'PUT',{model:'synthetic',budget:32000})).status,403); assert.equal((await request('/api/admin/image-settings/learning_hub',101,'PUT',{model:'synthetic-own-learning',budget:1500})).status,200); assert.equal((await request('/api/admin/image-settings/learning_hub',101,'PUT',{model:'synthetic',budget:32001})).status,400); const listing=await (await request('/api/image-jobs/clinical_assistant',101)).text();assert.ok(!listing.includes('prompt_cipher'));assert.ok(!listing.includes('staged_bytes')); const body = ' Exact [3, 1].\n| Dose | Page |\n| 5 mg | 19 [3] |\n'; const payload = { lastAnswer: body, messages: [{ role: 'assistant', content: body, sources: [{ number: 3, page: 19 }], imageJobs: [{ jobId: image.jobId, status: 'forged', imageUrl: 'https://invalid.test' }] }], generatedImage: '/api/generated-images/' + image.jobId }; const saved = await request('/api/clinical-assistant/chats', 101, 'POST', payload); assert.equal(saved.status, 200); const savedId = (await saved.json()).id; const reopened = await (await request('/api/clinical-assistant/chats/' + savedId, 101)).json(); assert.equal(reopened.chat.payload.lastAnswer, body); assert.equal(reopened.chat.payload.messages[0].content, body); assert.deepEqual(reopened.chat.payload.messages[0].sources, [{ number: 3, page: 19 }]); assert.deepEqual(reopened.chat.payload.messages[0].imageJobs, [{ jobId: image.jobId }]); assert.equal(reopened.chat.payload.generatedImage, undefined, 'offbox image is session-only and never stored'); assert.match((await db.get('SELECT payload FROM clinical_assistant_chats WHERE id=$1', [savedId])).payload, /^enc1:/); assert.equal((await request('/api/clinical-assistant/chats/' + savedId, 102)).status, 404); assert.equal((await request('/api/clinical-assistant/chats', 102, 'POST', payload)).status, 403); assert.equal((await request('/api/clinical-assistant/chats', 102, 'POST', { messages: [], lastAnswer: '![image](' + payload.generatedImage + ')' })).status, 403); assert.equal((await request('/api/clinical-assistant/chats', 101, 'POST', { messages: [], generatedImageJobs: [{ jobId: 'bad' }] })).status, 400); } finally { await new Promise(r=>server.close(r)); } }); test('both workflows enforce exact 32000 UTF16 assembly and durable snapshots cannot change with admin settings', async () => { const jobs = service(); const before = paid; for (const workflow of ['clinical_assistant', 'learning_hub']) { await db.query("INSERT INTO app_settings(key,value) VALUES($1,'32000') ON CONFLICT(key) DO UPDATE SET value='32000'", [workflow + '.image_budget']); const base = await jobs.snapshot(workflow, { prompt: 'x', layout: 'square' }); const units = 32001 - base.rendered.length; const prompt = '😀'.repeat(Math.floor(units / 2)) + 'x'.repeat(units % 2); assert.equal((await jobs.snapshot(workflow, { prompt, layout: 'square' })).rendered.length, 32000); await assert.rejects(jobs.enqueue(101, workflow, { prompt: prompt + 'x', layout: 'square' }, 'over-32000'), e => e.statusCode === 413); const job = await jobs.enqueue(101, workflow, { prompt, layout: 'square' }, 'exact-32000'); const original = await db.get('SELECT * FROM generated_image_jobs WHERE id=$1', [job.jobId]); assert.equal(original.prompt_units, 32000); assert.match(original.prompt_cipher, /^enc1:/); for (const [field, value] of [['owner_id', 102], ['model', 'replacement'], ['budget', 1000], ['prompt_cipher', 'enc1:replacement'], ['prompt_revision', original.prompt_revision + 1], ['context_total', 9], ['context_included', 1]]) { await assert.rejects(db.query(`UPDATE generated_image_jobs SET ${field}=$2 WHERE id=$1`, [job.jobId, value]), /immutable/); } await db.query("UPDATE app_settings SET value='1000' WHERE key=$1", [workflow + '.image_budget']); let sent; await createImageService({ db, storage, generate: async (snapshot, text) => { sent = { snapshot, text }; paid++; return inspect(png); } }).tick(); assert.equal(sent.snapshot.id, job.jobId); assert.equal(sent.snapshot.budget, 32000); assert.equal(sent.snapshot.model, original.model); assert.equal(sent.text, require('../src/utils/crypto').decryptString(original.prompt_cipher)); } assert.equal(paid, before + 2); }); test('concurrent tool replay, owner/workflow identity and preflight failures never create duplicate paid jobs', async () => { const jobs = service(); const before = paid; const tool = require('../src/utils/imageTool'); const opts = { owner: 101, workflow: 'clinical_assistant', body: { idempotencyKey: 'concurrent-tool' }, imageContext:{request:'Concurrent original request',history:[]}, images: jobs }; const result = prompt => ({ content: 'Exact body [3].', toolCalls: [{ id: 'one', type: 'function', function: { name: 'generate_image', arguments: JSON.stringify({ prompt }) } }] }); const [a, b] = await Promise.all([tool.dispatch(result('First replay diagram'), opts), tool.dispatch(result('Second replay diagram'), opts)]); assert.equal(a.imageJobs[0].jobId, b.imageJobs[0].jobId); const other = await jobs.enqueue(102, 'clinical_assistant', { prompt: 'Other owner' }, 'tool:concurrent-tool'); const learning = await jobs.enqueue(101, 'learning_hub', { prompt: 'Other workflow' }, 'tool:concurrent-tool'); assert.notEqual(other.jobId, a.imageJobs[0].jobId); assert.notEqual(learning.jobId, a.imageJobs[0].jobId); await jobs.tick(); await jobs.tick(); await jobs.tick(); await jobs.tick(); assert.equal(paid, before + 3); const noMigration = createImageService({ db: { query: async () => { throw Error('synthetic missing migration'); } }, storage, generate }); await assert.rejects(noMigration.ready(), e => e.statusCode === 503); const noEncryption = createImageService({ db, storage, generate, encryption: { hasKey: () => false } }); await assert.rejects(noEncryption.enqueue(101, 'clinical_assistant', { prompt: 'No encryption' }, 'no-key'), e => e.statusCode === 503); const noGateway = createImageService({ db, storage, env: {}, encryption: require('../src/utils/crypto') }); await assert.rejects(noGateway.ready(), e => e.statusCode === 503); assert.equal(paid, before + 3); }); test('worker stop during preflight or claim never starts a new paid request; queued work resumes safely', async () => { for (const pauseAt of ['preflight', 'claim']) { const jobs = service(); const before = paid; const job = await jobs.enqueue(101, 'clinical_assistant', { prompt: 'Stop before payment' }, 'stop-' + pauseAt); let reached, release; const paused = new Promise(resolve => { reached = resolve; }); const gate = new Promise(resolve => { release = resolve; }); const worker = createImageService({ generate, storage: { ...storage, ready: async () => { await storage.ready(); if (pauseAt === 'preflight') { reached(); await gate; } } }, db: { ...db, pool: { async connect() { const client = await pool.connect(); return { release: () => client.release(), async query(...args) { const result = await client.query(...args); if (pauseAt === 'claim' && args[0] === 'COMMIT') { reached(); await gate; } return result; } }; } } } }); worker.start(); await paused; const stopped = worker.stop(); release(); await stopped; assert.equal(paid, before, pauseAt); assert.equal((await jobs.get(job.jobId, 101, 'clinical_assistant')).status, 'pending'); await service().tick(); assert.equal(paid, before + 1); assert.equal((await jobs.get(job.jobId, 101, 'clinical_assistant')).status, 'done'); } }); test('exact IMAGE HTTP input binds original request and contiguous whole recent turns; snapshots, UTF16 metadata and replay remain honest', async () => { const http = require('node:http'); const captured = []; const server = http.createServer(async (req,res) => { const chunks=[]; for await (const c of req) chunks.push(c); captured.push({path:req.url,body:JSON.parse(Buffer.concat(chunks))}); res.writeHead(200,{'Content-Type':'application/json'}); res.end(JSON.stringify({data:[{b64_json:png.toString('base64')}]})); }); server.listen(0,'127.0.0.1'); await new Promise(r=>server.once('listening',r)); const old = process.env.LITELLM_API_BASE; process.env.LITELLM_API_BASE='http://127.0.0.1:'+server.address().port+'/v1'; try { await db.query("UPDATE app_settings SET value='12000' WHERE key='clinical_assistant.image_budget'"); const jobs=createImageService({db,storage}); const context={request:' ORIGINAL request: draw the latest corrected dose 😀 [3].\n',history:[ {role:'user',content:'Old tiny turn must not jump a gap.'}, {role:'assistant',content:'too large boundary '+ '😀'.repeat(7000)}, {role:'user',content:'Recent correction '+ '😀'.repeat(1600)}, {role:'assistant',content:' Exact table [3, 1].\n| Dose | Page |\n| 5 mg | 19 [3] |\n'}]}; const original=JSON.stringify(context); const input={prompt:'MODEL DESCRIPTION ONLY',layout:'portrait'}; const tool=require('../src/utils/imageTool'); const opts={owner:101,workflow:'clinical_assistant',body:{idempotencyKey:'exact-image-input'},imageContext:context,images:jobs}; const ai={content:'Unchanged [3, 1].',toolCalls:[{id:'ctx',type:'function',function:{name:'generate_image',arguments:JSON.stringify(input)}}]}; const job=(await tool.dispatch(ai,opts)).imageJobs[0]; await jobs.tick(); assert.equal(captured.length,1); const sent=captured[0].body.prompt; assert.ok(sent.includes(context.request),'ORIGINAL request must reach IMAGE provider'); assert.ok(sent.includes(input.prompt)); assert.ok(sent.includes(context.history[2].content)); assert.ok(sent.includes(context.history[3].content)); assert.ok(sent.indexOf(context.history[2].content)e.statusCode===409); for (const workflow of ['clinical_assistant','learning_hub']) { await db.query("UPDATE app_settings SET value='32000' WHERE key=$1",[workflow+'.image_budget']); const base=await jobs.snapshot(workflow,{prompt:'x'}); const text='😀'.repeat(Math.floor((32001-base.rendered.length)/2))+'x'.repeat((32001-base.rendered.length)%2); const exact=await jobs.enqueue(101,workflow,{prompt:text},'http-exact-'+workflow); await jobs.tick(); assert.equal(captured.at(-1).body.prompt.length,32000); assert.match(captured.at(-1).body.prompt,/image only/i); assert.equal(exact.context.totalTurns,0); await assert.rejects(jobs.enqueue(101,workflow,{prompt:text+'x'},'http-over-'+workflow),e=>e.statusCode===413); } assert.equal(captured.length,3,'mandatory overflow never calls IMAGE provider'); } finally { if(old===undefined) delete process.env.LITELLM_API_BASE; else process.env.LITELLM_API_BASE=old; await new Promise(r=>server.close(r)); } }); test('expired PAID lease becomes explicit unknown even while storage and gateway readiness fail', async () => { const jobs=service(),before=paid; const job=await jobs.enqueue(101,'clinical_assistant',{prompt:'Crash plus outage'},'combined-outage'); const claim=await jobs.claim();assert.equal(claim.id,job.jobId); await db.query("UPDATE generated_image_jobs SET lease_until=NOW()-interval '1 second' WHERE id=$1",[job.jobId]); const down=createImageService({db,env:{},storage:{ready:async()=>{throw Error('synthetic outage');}}}); await down.tick().catch(()=>{}); assert.equal((await jobs.get(job.jobId,101,'clinical_assistant')).outcome,'unknown');assert.equal(paid,before); await jobs.tick();assert.equal(paid,before); }); test('partial schema without links/asset-read relation fails readiness and queued payment against real PG', async () => { const jobs=service(),before=paid; const job=await jobs.enqueue(101,'clinical_assistant',{prompt:'Do not pay with missing links'},'partial-schema'); await db.query('ALTER TABLE generated_image_links RENAME TO unavailable_links'); try { await assert.rejects(jobs.ready(),e=>e.statusCode===503); await assert.rejects(jobs.tick(),e=>e.statusCode===503); await assert.rejects(jobs.enqueue(101,'clinical_assistant',{prompt:'blocked too'},'partial-new'),e=>e.statusCode===503); assert.equal(paid,before); } finally { await db.query('ALTER TABLE unavailable_links RENAME TO generated_image_links');await db.query("UPDATE generated_image_jobs SET stage='interrupted' WHERE id=$1",[job.jobId]); } }); test('whole-turn context exactly fills the cap or is omitted in full; mandatory original request overflow never pays', async () => { const jobs=service(),before=paid; for(const workflow of ['clinical_assistant','learning_hub']) { await db.query("UPDATE app_settings SET value='32000' WHERE key=$1",[workflow+'.image_budget']); const input={prompt:'Image description',layout:'square'},context={request:' Original image request 😀\n',history:[{role:'user',content:'x'}]}; const base=await jobs.snapshot(workflow,input,context);const remaining=32001-base.rendered.length; context.history[0].content='😀'.repeat(Math.floor(remaining/2))+'x'.repeat(remaining%2); const original=JSON.stringify(context),exact=await jobs.snapshot(workflow,input,context); assert.equal(exact.rendered.length,32000);assert.equal(exact.included,1);assert.ok(exact.rendered.includes(context.history[0].content)); context.history[0].content+='x';const over=await jobs.snapshot(workflow,input,context); assert.equal(over.included,0);assert.equal(over.total,1);assert.ok(!over.rendered.includes(context.history[0].content)); assert.ok(over.rendered.includes(context.request));assert.ok(over.rendered.endsWith(require('../src/utils/generatedImages').IMAGE_OUTPUT_RULE)); context.history[0].content=context.history[0].content.slice(0,-1);assert.equal(JSON.stringify(context),original); await assert.rejects(jobs.enqueue(101,workflow,input,'mandatory-original-overflow',false,{request:'😀'.repeat(16000),history:[]}),e=>e.statusCode===413); } assert.equal(paid,before); });