Sporalize-api / tests /admin-review-author.test.ts
Shoraky's picture
Keep feedback attribution tied to the editor
f5af86b
Raw History Blame Contribute Delete
4.42 kB
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { test } from "node:test";
test("admin action edits attribute changed feedback to the editor", { timeout: 30_000 }, async () => {
const adminId = "11111111-1111-4111-8111-111111111111";
const ownerId = "22222222-2222-4222-8222-222222222222";
const sessionId = "33333333-3333-4333-8333-333333333333";
const actionId = "44444444-4444-4444-8444-444444444444";
const user = { id: adminId, email: "admin@example.com", email_confirmed_at: new Date().toISOString(), user_metadata: {} };
const savedReviews: Array<Record<string, unknown>> = [];
let oldNotes = ["Original feedback"];
const upstream = createServer(async (req, res) => {
const url = new URL(req.url!, "http://stub");
res.setHeader("Content-Type", "application/json");
if (url.pathname === "/auth/v1/user") return res.end(JSON.stringify(user));
if (url.pathname === "/rest/v1/users") return res.end(JSON.stringify({ user_id: adminId, account_role: "admin" }));
if (url.pathname === "/rest/v1/sessions") return res.end(JSON.stringify({ status: "completed" }));
if (url.pathname === "/rest/v1/annotations" && req.method === "GET") {
return res.end(JSON.stringify({ user_id: ownerId, score: 5, annotations_notes: oldNotes.map((note) => ({ note })) }));
}
if (url.pathname === "/rest/v1/rpc/save_action_review") {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(Buffer.from(chunk));
const body = JSON.parse(Buffer.concat(chunks).toString()) as Record<string, unknown>;
savedReviews.push(body);
oldNotes = body.p_notes as string[];
return res.end("null");
}
if (url.pathname === `/api/sessions/${sessionId}/actions/${actionId}` && req.method === "PATCH") return res.end("{}");
res.statusCode = 404;
return res.end(JSON.stringify({ error: "Unexpected mock request", path: url.pathname }));
});
upstream.listen(0, "127.0.0.1");
await once(upstream, "listening");
const portProbe = createServer();
portProbe.listen(0, "127.0.0.1");
await once(portProbe, "listening");
const port = (portProbe.address() as AddressInfo).port;
await new Promise<void>((resolve) => portProbe.close(() => resolve()));
const origin = `http://127.0.0.1:${port}`;
const upstreamOrigin = `http://127.0.0.1:${(upstream.address() as AddressInfo).port}`;
const child = spawn(process.execPath, ["--import", "tsx", "src/server.ts"], {
env: { ...process.env, NODE_ENV: "test", PORT: String(port), FRONTEND_URL: origin, BACKEND_URL: origin,
ANALYSIS_API_URL: upstreamOrigin, SUPABASE_URL: upstreamOrigin,
SUPABASE_ANON_KEY: "test-anon", SUPABASE_SERVICE_ROLE_KEY: "test-service", ANALYSIS_PROXY_SECRET: "x".repeat(32) },
stdio: ["ignore", "pipe", "pipe"],
});
try {
await new Promise<void>((resolve, reject) => {
child.stdout.on("data", (data) => { if (String(data).includes("API listening")) resolve(); });
child.once("exit", (code) => reject(new Error(`API exited: ${code}`)));
child.once("error", reject);
});
const save = (notes: string[]) => fetch(`${origin}/api/admin/sessions/${sessionId}/actions/${actionId}`, {
method: "PATCH", signal: AbortSignal.timeout(10000),
headers: { Cookie: "sp_access=access", Origin: origin, "Content-Type": "application/json" },
body: JSON.stringify({ startFrame: 0, endFrame: 10, type: "Shoot", score: 5, notes }),
});
assert.equal((await save(["Original feedback"])).status, 200);
assert.equal(savedReviews.length, 0, "an action-only edit must preserve the previous feedback author");
assert.equal((await save(["Updated feedback"])).status, 200);
assert.equal(savedReviews.length, 1);
assert.equal(savedReviews[0].p_user_id, adminId);
assert.deepEqual(savedReviews[0].p_notes, ["Updated feedback"]);
assert.equal(savedReviews[0].p_session_id, sessionId);
assert.equal(savedReviews[0].p_action_id, actionId);
} finally {
if (child.exitCode === null && child.signalCode === null) {
const exited = once(child, "exit");
child.kill();
await exited;
}
upstream.closeAllConnections();
await new Promise<void>((resolve) => upstream.close(() => resolve()));
}
});