import assert from "node:assert/strict" import test from "node:test" import { createHash } from "node:crypto" import Fastify from "fastify" import { PgDialect } from "drizzle-orm/pg-core" import routes from "../src/routes/instanceAgents" const id = "b48c0e33-1abd-4b11-89fa-1bb7b39a3057" async function fixture(isAdmin: boolean) { const updates: any[] = [] const app = Fastify() app.addHook("onRequest", async (request) => { request.user = { is_admin: isAdmin } as any }) app.decorate("db", { update: () => ({ set: (data: any) => ({ where: (condition: any) => ({ returning: async () => { if (new PgDialect().sqlToQuery(condition).params[0] !== id) return [] updates.push(data) return [{ id }] } }) }) }), }) await app.register(routes) return { app, updates } } test("only administrators can rotate an agent token", async (t) => { const f = await fixture(false) t.after(() => f.app.close()) const response = await f.app.inject({ method: "POST", url: "/instance-agents/" + id + "/token" }) assert.equal(response.statusCode, 403) assert.equal(f.updates.length, 0) }) test("rotation returns a new token, stores only its hash and requires a new heartbeat", async (t) => { const f = await fixture(true) t.after(() => f.app.close()) const tokens = [] for (let i = 0; i < 2; i++) { const response = await f.app.inject({ method: "POST", url: "/instance-agents/" + id + "/token" }) assert.equal(response.statusCode, 200) assert.equal(response.headers["cache-control"], "no-store") const { token } = response.json() assert.match(token, /^fedeo_agent_[a-f0-9]{64}$/) assert.equal(f.updates[i].tokenHash, createHash("sha256").update(token).digest("hex")) assert.equal(f.updates[i].tokenPrefix, token.slice(0, 24)) assert.equal(f.updates[i].lastSeenAt, null) assert.equal(f.updates[i].token, undefined) tokens.push(token) } assert.notEqual(tokens[0], tokens[1]) }) test("unknown agents cannot receive setup credentials", async (t) => { const f = await fixture(true) t.after(() => f.app.close()) const response = await f.app.inject({ method: "POST", url: "/instance-agents/00000000-0000-4000-8000-000000000000/token" }) assert.equal(response.statusCode, 404) assert.equal(f.updates.length, 0) }) test("OCR and background cleanup require an agent that supports the requested processing", async t => { const app = Fastify() t.after(() => app.close()) const jobs: any[] = [] let capabilities: Record = { scan: true } app.addHook("onRequest", async request => { request.user = { is_admin: true, tenant_id: 1 } as any }) app.decorate("db", { select: () => ({ from: () => ({ where: () => ({ limit: async () => [{ id, active: true, scanDefaults: {}, capabilities }] }) }) }), insert: () => ({ values: (data: any) => ({ returning: async () => { jobs.push(data); return [{ id: "job", ...data }] } }) }), }) await app.register(routes) for (const settings of [{ ocr: true }, { ocr: "true" }, { backgroundCleanup: true }]) { const response = await app.inject({ method: "POST", url: "/scan-jobs", payload: { agentId: id, settings } }) assert.equal(response.statusCode, 409) assert.equal(jobs.length, 0) } capabilities = { scan: true, ocr: true, backgroundCleanup: true } const response = await app.inject({ method: "POST", url: "/scan-jobs", payload: { agentId: id, settings: { ocr: true, ocrLanguage: "deu+eng", backgroundCleanup: true }, target: { folder: null, type: null } }, }) assert.equal(response.statusCode, 200) assert.equal(jobs.length, 1) assert.equal(jobs[0].settings.ocr, true) assert.equal(jobs[0].target.type, null) })