import test from 'node:test'; import assert from 'node:assert/strict'; import { createApp } from '../server.js'; import { makeJwt, makePbCookie, startServer, stopServer, urlOf, cookieValue, } from './helpers.js'; // Fake portal: web-auth + openfga verify with a configurable crop_image verdict. async function startFakePortal({ cropAllowed = true } = {}) { const state = { verifyCalls: 0 }; const server = await startServer((req, res) => { let raw = ''; req.on('data', (c) => (raw += c)); req.on('end', () => { const json = (status, body) => { res.writeHead(status, { 'content-type': 'application/json' }); res.end(JSON.stringify(body)); }; if (req.method === 'POST' && req.url === '/api/web/auth') { return json(200, { ok: true, sessionId: 's1', loginUrl: 'https://portal.example/login' }); } if (req.url.startsWith('/api/web/auth/s1/cookie')) { return json(200, { ok: true, sessionId: 's1', status: 'Activated', cookie: makePbCookie(makeJwt({ expiresInSec: 3600 })), phone: '+2348068287222', firstName: 'Ada', lastName: 'Obi', }); } if (req.method === 'POST' && req.url === '/api/openfga/verify') { state.verifyCalls += 1; const body = JSON.parse(raw); // capture_biometric checks (session endpoint) always pass; crop_image // follows the configured verdict. const allowed = body.relation === 'crop_image' ? cropAllowed : true; return json(200, { ok: true, allowed }); } json(404, { ok: false }); }); }); return { server, state, stop: () => stopServer(server) }; } // Fake a2loft: dims load, save echo; records calls. async function startFakeA2loft() { const calls = []; const server = await startServer((req, res) => { let raw = ''; req.on('data', (c) => (raw += c)); req.on('end', () => { calls.push({ method: req.method, url: req.url, body: raw ? JSON.parse(raw) : null }); const json = (body) => { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify(body)); }; if (req.url.startsWith('/identity/dims/picture/')) { return json({ ok: true, data: { upload_id: 'up-1', file_name: 'id-domains/ucth/raw-pictures/x/img.jpg', caption: '08068287222', domain: 'ucth', raw_file_id: 'raw-1', }, }); } if (req.url.includes('/profile/picture')) { return json({ ok: true, message: 'Picture uploaded.' }); } json({ ok: true }); }); }); return { calls, server, stop: () => stopServer(server) }; } // Fake minio + azure queue — plain objects exposing just what the router uses. const fakeMinio = () => ({ presignedGetObject: async (_bucket, name) => `https://storage.example/${name}`, }); const fakeQueue = (messageText) => { const calls = { getMessage: 0, deleteMessage: 0 }; const item = { messageId: 'm-1', popReceipt: 'pop-1', dequeueCount: 1, messageText: messageText ?? JSON.stringify({ upload_id: 'up-1', file_name: 'id-domains/ucth/raw-pictures/x/img.jpg', caption: '08068287222', domain: 'ucth', raw_file_id: 'raw-1', }), }; return { calls, // azure-storage callback signatures getMessage: (_queue, _opts, cb) => { calls.getMessage += 1; cb(null, item); }, deleteMessage: (_queue, _id, _pop, cb) => { calls.deleteMessage += 1; cb(null, { isSuccessful: true }); }, }; }; // Fake removebg + nodered upstreams. async function startFakeRemovebg() { const calls = []; const server = await startServer((req, res) => { let raw = ''; req.on('data', (c) => (raw += c)); req.on('end', () => { calls.push({ url: req.url, key: req.headers['x-api-key'] }); res.writeHead(200, { 'content-type': 'image/png' }); res.end('PNGDATA'); }); }); return { calls, server, stop: () => stopServer(server) }; } async function startFakeNodered() { const calls = []; const server = await startServer((req, res) => { let raw = ''; req.on('data', (c) => (raw += c)); req.on('end', () => { calls.push({ method: req.method, url: req.url, auth: req.headers.authorization }); const json = (body) => { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify(body)); }; if (req.url === '/idid/picture/review/up-1') { return json({ ok: true, data: { agentPhoneHash: '12345' } }); } if (req.url === '/unical/agents') { return json([{ phoneHashes: [12345], name: 'Agent A', phone: '+2340000000000' }]); } if (req.url === '/zeebe/picture/review') { return json({ ok: true }); } json({ ok: false }); }); }); return { calls, server, stop: () => stopServer(server) }; } async function boot({ cropAllowed = true, cropperClients, queue } = {}) { const portal = await startFakePortal({ cropAllowed }); const a2loft = await startFakeA2loft(); const removebg = await startFakeRemovebg(); const nodered = await startFakeNodered(); // presigned URLs from the fake minio must not hit the real network const testFetch = async (url, init) => { if (String(url).startsWith('https://storage.example/')) { return { ok: true, status: 200, headers: { get: () => 'image/png' }, arrayBuffer: async () => Buffer.from('PNGDATA'), json: async () => ({}), text: async () => 'PNGDATA', }; } return fetch(url, init); }; const app = createApp({ portalUrl: urlOf(portal.server), a2loftBase: urlOf(a2loft.server), removebgUrl: urlOf(removebg.server), noderedBase: urlOf(nodered.server), fetch: testFetch, cropperClients: cropperClients !== undefined ? cropperClients : { minio: fakeMinio(), queue: queue || fakeQueue(), noderedAuth: { username: 'nr', password: 'nr-secret' }, carvekitKey: 'carve-key', }, }); const httpServer = await new Promise((resolve) => { const s = app.listen(0, '127.0.0.1', () => resolve(s)); }); const base = urlOf(httpServer); const stop = async () => { await new Promise((r) => httpServer.close(r)); await portal.stop(); await a2loft.stop(); await removebg.stop(); await nodered.stop(); }; return { base, a2loft, removebg, nodered, portal, stop }; } const manual = { redirect: 'manual' }; async function login(base) { const res = await fetch(`${base}/auth/callback?sessionId=s1`, manual); assert.equal(res.status, 302); return `session=${cookieValue(res, 'session')}; user_profile=${cookieValue(res, 'user_profile')}`; } test('unconfigured deployment: cropper answers 503, other tools unaffected', async () => { const { base, stop } = await boot({ cropperClients: null }); try { const cookies = await login(base); const res = await fetch(`${base}/cropper/api/baseUrl`, { headers: { cookie: cookies } }); assert.equal(res.status, 503); // the page says so too, rather than rendering actions that all fail const page = await fetch(`${base}/cropper`, { headers: { cookie: cookies } }); assert.equal(page.status, 503); assert.match(await page.text(), /not configured/i); const landing = await fetch(`${base}/`); assert.equal(landing.status, 200); } finally { await stop(); } }); test('crop gate: permitted operator loads the page; denied operator does not', async () => { const ok = await boot({ cropAllowed: true }); try { const cookies = await login(ok.base); const page = await fetch(`${ok.base}/cropper`, { headers: { cookie: cookies } }); assert.equal(page.status, 200); const html = await page.text(); assert.match(html, /Smart Photo Editor|Image Cropper/); assert.match(html, /window\.__SESSION__ = \{/, 'session injected server-side'); assert.match(html, /cds-header-nav-item href="\/cropper" aria-current="page"/); // notifications must use a component the bundle actually defines assert.match(html, /cds-inline-notification/); assert.ok(!/cds-toast-item/.test(html), 'cds-toast-item is not a registered element'); assert.match(html, /cds-side-nav-link href="\/cropper" aria-current="page"/); } finally { await ok.stop(); } const denied = await boot({ cropAllowed: false }); try { const cookies = await login(denied.base); const page = await fetch(`${denied.base}/cropper`, { headers: { cookie: cookies } }); assert.equal(page.status, 403); assert.match(await page.text(), /permission to crop images/i); } finally { await denied.stop(); } }); test('picture routes are gated and proxied; reads use the cached gate', async () => { const { base, portal, stop } = await boot(); try { const cookies = await login(base); const before = portal.state.verifyCalls; const next = await fetch(`${base}/cropper/picture/next`, { headers: { cookie: cookies } }); assert.equal(next.status, 200); const body = await next.json(); assert.equal(body.ok, true); assert.equal(body.data.message.upload_id, 'up-1'); // load-by-id goes to a2loft dims; image fetch returns a data URL const loaded = await fetch(`${base}/cropper/picture/load/up-1`, { headers: { cookie: cookies } }); assert.equal((await loaded.json()).ok, true); const image = await fetch(`${base}/cropper/picture/id-domains_ucth_raw-pictures_x_img.jpg`, { headers: { cookie: cookies }, }); const imageBody = await image.json(); assert.equal(imageBody.ok, true); assert.match(imageBody.data, /^data:image\/png;base64,/); // several reads, but only one crop verify within the TTL assert.equal(portal.state.verifyCalls - before, 1); } finally { await stop(); } }); test('removebg is gated and proxies the cleanup service', async () => { const denied = await boot({ cropAllowed: false }); try { const cookies = await login(denied.base); const res = await fetch(`${denied.base}/cropper/api/removebg`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify({ image_file_b64: 'QUJD' }), }); assert.equal(res.status, 403); assert.equal(denied.removebg.calls.length, 0, 'cleanup service not contacted'); } finally { await denied.stop(); } const ok = await boot({ cropAllowed: true }); try { const cookies = await login(ok.base); const res = await fetch(`${ok.base}/cropper/api/removebg`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify({ image_file_b64: 'QUJD' }), }); assert.equal(res.status, 200); assert.equal(await res.text(), 'PNGDATA'); assert.equal(ok.removebg.calls[0].key, 'carve-key'); } finally { await ok.stop(); } }); test('save verifies the gate live and proxies to a2loft with session identity', async () => { const { base, a2loft, portal, stop } = await boot(); try { const cookies = await login(base); // prime the cache with a denied verdict for the reads, then flip the // portal to allowed — the live save check must see the new verdict. const payload = { pictureDataUrl: 'data:image/jpeg;base64,QUJD', phone: '08068287222', requiresCleaning: true, message: { pop_receipt: 'pop-1', message_id: 'm-1', message: { upload_id: 'up-1', domain: 'ucth', agentDeviceHash: 'agent-1', caption: '08068287222', file_url: null, }, }, }; const res = await fetch(`${base}/cropper/profile/picture?otp=778899`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify(payload), }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.ok, true); const save = a2loft.calls.find((c) => c.url.includes('/profile/picture')); assert.ok(save, 'a2loft save contacted'); const u = new URL(save.url, 'http://x'); assert.equal(decodeURIComponent(u.pathname), '/identity/ucth/profile/picture'); assert.equal(u.searchParams.get('phone'), '+2348068287222', 'session phone is the validating identity'); assert.equal(u.searchParams.get('otp'), '778899'); assert.equal(save.body.phone, '08068287222', 'profile phone from the picture'); assert.equal(save.body.uploadId, 'up-1'); assert.ok(portal.state.verifyCalls > 0); } finally { await stop(); } }); test('save refuses without an OTP and without the crop permission', async () => { const { base, a2loft, stop } = await boot({ cropAllowed: false }); try { const cookies = await login(base); const payload = { pictureDataUrl: 'data:image/jpeg;base64,QUJD', phone: '08068287222', message: { pop_receipt: 'custom', message_id: 'm-1', message: { upload_id: 'up-1', domain: 'ucth', caption: '08068287222', file_url: null }, }, }; const noOtp = await fetch(`${base}/cropper/profile/picture`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify(payload), }); // permission denial dominates; 400 (missing OTP) or 403 both satisfy the spec assert.ok([400, 403].includes(noOtp.status), `got ${noOtp.status}`); const denied = await fetch(`${base}/cropper/profile/picture?otp=778899`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify(payload), }); assert.equal(denied.status, 403); assert.equal(a2loft.calls.filter((c) => c.url.includes('/profile/picture')).length, 0, 'a2loft never contacted'); } finally { await stop(); } }); test('review and dequeue proxy with the queued message', async () => { const { base, nodered, stop } = await boot(); try { const cookies = await login(base); const agent = await fetch(`${base}/cropper/picture/review/up-1`, { headers: { cookie: cookies } }); const agentBody = await agent.json(); assert.equal(agentBody.ok, true, JSON.stringify(agentBody)); assert.equal(agentBody.data.name, 'Agent A'); const review = await fetch(`${base}/cropper/picture/review`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify({ deleteFromQueue: true, message: { message_id: 'm-1', pop_receipt: 'pop-1', message: { domain: 'ucth' } }, mode: 'raw', raw: { phone: '08068287222', reviewRejectMessage: 'blurry' }, }), }); const reviewBody = await review.json(); assert.equal(reviewBody.ok, true, JSON.stringify(reviewBody)); assert.ok(nodered.calls.find((c) => c.url === '/zeebe/picture/review')); } finally { await stop(); } }); test('baseUrl exposes the identity-prefixed a2loft base', async () => { const { base, stop } = await boot(); try { const cookies = await login(base); const res = await fetch(`${base}/cropper/api/baseUrl`, { headers: { cookie: cookies } }); const body = await res.json(); assert.equal(body.ok, true); assert.match(body.data, /\/identity$/); assert.ok(body.data.startsWith('http://127.0.0.1:')); } finally { await stop(); } }); test('malformed request bodies are rejected, never hang the request', async () => { const { base, a2loft, stop } = await boot(); try { const cookies = await login(base); for (const path of ['/cropper/picture/review', '/cropper/dequeue', '/cropper/profile/picture?otp=1']) { const res = await fetch(`${base}${path}`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify({}), }); assert.equal(res.status, 400, `${path} → ${res.status}`); const body = await res.json(); assert.equal(body.ok, false); } assert.equal(a2loft.calls.filter((c) => c.url.includes('/profile/picture')).length, 0); } finally { await stop(); } }); test('a malformed queue item does not hang the request', async () => { const { base, stop } = await boot({ queue: fakeQueue('not-json') }); try { const cookies = await login(base); const res = await fetch(`${base}/cropper/picture/next`, { headers: { cookie: cookies } }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.ok, false); assert.match(body.message, /malformed/i); } finally { await stop(); } }); test('save rejects an unknown domain without contacting a2loft', async () => { const { base, a2loft, stop } = await boot(); try { const cookies = await login(base); const res = await fetch(`${base}/cropper/profile/picture?otp=778899`, { method: 'POST', headers: { cookie: cookies, 'content-type': 'application/json' }, body: JSON.stringify({ pictureDataUrl: 'data:image/jpeg;base64,QUJD', phone: '08068287222', message: { pop_receipt: 'custom', message_id: 'm-1', message: { upload_id: 'up-1', domain: '../../admin', agentDeviceHash: 'a' }, }, }), }); assert.equal(res.status, 400); assert.equal(a2loft.calls.length, 0, 'a2loft never contacted'); } finally { await stop(); } }); test('picture fetches reject object keys outside the id-domains prefix', async () => { const { base, stop } = await boot(); try { const cookies = await login(base); // encoded traversal survives as a single path segment; a plain ../ is // normalised away by the HTTP client before the route ever sees it const traversal = await fetch(`${base}/cropper/picture/..%2F..%2Fsecret`, { headers: { cookie: cookies } }); assert.equal(traversal.status, 400, `got ${traversal.status}`); const escapeWithinPrefix = await fetch(`${base}/cropper/picture/id-domains_.._.._secret`, { headers: { cookie: cookies }, }); assert.equal(escapeWithinPrefix.status, 400, `got ${escapeWithinPrefix.status}`); const ok = await fetch(`${base}/cropper/picture/id-domains_ucth_raw-pictures_x_img.jpg`, { headers: { cookie: cookies }, }); assert.equal(ok.status, 200); } finally { await stop(); } });