85 lines
3.8 KiB
TypeScript
85 lines
3.8 KiB
TypeScript
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<string, boolean> = { 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)
|
|
})
|