90 lines
4.5 KiB
TypeScript
90 lines
4.5 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import test from "node:test"
|
|
import { createHash } from "node:crypto"
|
|
import { readFileSync } from "node:fs"
|
|
import { createRequire } from "node:module"
|
|
import path from "node:path"
|
|
import vm from "node:vm"
|
|
import ts from "typescript"
|
|
import Fastify from "fastify"
|
|
import { getTableColumns, getTableName } from "drizzle-orm"
|
|
import { PgDialect } from "drizzle-orm/pg-core"
|
|
|
|
// Load the real route with only file storage replaced; tests never contact S3 or a live database.
|
|
const routePath = path.resolve(__dirname, "../src/routes/instanceAgentGateway.ts")
|
|
const routeRequire = createRequire(routePath)
|
|
const compiled = ts.transpileModule(readFileSync(routePath, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true },
|
|
}).outputText
|
|
|
|
const fixture = async (status = "running") => {
|
|
const token = "test-agent-token"
|
|
const agent = { id: "agent", tokenHash: createHash("sha256").update(token).digest("hex"), active: true }
|
|
const job: any = { id: "job", agentId: "agent", tenantId: 7, requestedBy: "user", requestedFilename: "scan.png", target: { folder: "folder" }, status }
|
|
const saved: any[] = []
|
|
const rows: Record<string, any[]> = { instance_agents: [agent], instance_agent_scan_jobs: [job] }
|
|
const dialect = new PgDialect()
|
|
const matching = (table: any, condition: any) => {
|
|
const query = dialect.sqlToQuery(condition)
|
|
const columns = getTableColumns(table) as Record<string, any>
|
|
const clauses = [...query.sql.matchAll(/"\w+"\."(\w+)" (?:= \$(\d+)|in \(([^)]+)\))/g)]
|
|
assert.ok(clauses.length, query.sql)
|
|
return rows[getTableName(table)].filter(row => clauses.every(match => {
|
|
const key = Object.keys(columns).find(key => columns[key].name === match[1])!
|
|
if (match[2]) return row[key] === query.params[Number(match[2]) - 1]
|
|
return [...match[3].matchAll(/\$(\d+)/g)].some(parameter => row[key] === query.params[Number(parameter[1]) - 1])
|
|
}))
|
|
}
|
|
const db = {
|
|
select: () => ({ from: (table: any) => ({ where: (where: any) => ({ limit: async () => matching(table, where) }) }) }),
|
|
update: (table: any) => ({ set: (data: any) => ({ where: (where: any) => ({ returning: async () => matching(table, where).map(row => Object.assign(row, data)) }) }) }),
|
|
}
|
|
const exports: any = {}
|
|
vm.runInNewContext(compiled, {
|
|
exports,
|
|
require: (name: string) => name === "../utils/files"
|
|
? { saveFile: async (...args: any[]) => { saved.push(args); return { id: "saved-file" } } }
|
|
: routeRequire(name),
|
|
})
|
|
const app = Fastify()
|
|
app.decorate("db", db)
|
|
await app.register(exports.default)
|
|
return { app, job, saved, headers: { "x-agent-token": token } }
|
|
}
|
|
|
|
test("late failure status cannot overwrite an already completed scan", async (t) => {
|
|
const f = await fixture("completed")
|
|
t.after(() => f.app.close())
|
|
const response = await f.app.inject({ method: "POST", url: "/scan-jobs/job/status", headers: f.headers, payload: { status: "failed", message: "upload timeout" } })
|
|
assert.equal(response.statusCode, 409)
|
|
assert.equal(f.job.status, "completed")
|
|
})
|
|
|
|
test("running jobs accept progress and scanner failures", async (t) => {
|
|
const f = await fixture()
|
|
t.after(() => f.app.close())
|
|
for (const status of ["running", "failed"]) {
|
|
const response = await f.app.inject({ method: "POST", url: "/scan-jobs/job/status", headers: f.headers, payload: { status, message: "4 Seiten" } })
|
|
assert.equal(response.statusCode, 200)
|
|
assert.equal(f.job.status, status)
|
|
}
|
|
})
|
|
|
|
test("upload preserves the actual PDF filename, tenant and target folder", async (t) => {
|
|
const f = await fixture()
|
|
t.after(() => f.app.close())
|
|
const boundary = "fedeo-test-boundary"
|
|
const response = await f.app.inject({
|
|
method: "POST", url: "/scan-jobs/job/upload",
|
|
headers: { ...f.headers, "content-type": `multipart/form-data; boundary=${boundary}` },
|
|
payload: `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="scan.pdf"\r\nContent-Type: application/pdf\r\n\r\n%PDF-test\r\n--${boundary}--\r\n`,
|
|
})
|
|
assert.equal(response.statusCode, 200, response.body)
|
|
assert.equal(f.saved[0][1], 7)
|
|
assert.equal(f.saved[0][3].filename, "scan.pdf")
|
|
assert.equal(f.saved[0][3].contentType, "application/pdf")
|
|
assert.equal(f.saved[0][4], "folder")
|
|
assert.equal(f.job.status, "completed")
|
|
assert.equal(f.job.fileId, "saved-file")
|
|
})
|