Files
tools-legacy/tools/test/cropper.test.js
T
adebisi-fa 22d9ba40e3 fix(cropper): professional permission-denied page
Centered card with navbar, lock icon, sans-serif stack (error pages
previously rendered in the browser default serif), and action-aware
copy: permission denial offers only 'Back to Tools' instead of a
pointless 'Try again'; auth failures keep Try again + Back to Tools.
2026-10-01 01:35:37 +01:00

421 lines
14 KiB
JavaScript

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 = () => {
const calls = { getMessage: 0, deleteMessage: 0 };
const item = {
messageId: 'm-1',
popReceipt: 'pop-1',
dequeueCount: 1,
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 } = {}) {
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: 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);
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);
assert.match(await page.text(), /Smart Photo Editor|Image Cropper/);
} 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();
}
});