Files
template-app/modules/archive/__tests__/core.test.ts
T
2026-11-14 09:56:45 +00:00

147 lines
4.8 KiB
TypeScript

// The archive use cases over a recording port: what each asks the service, with no client and no network.
import type { ProjectScope } from "@conusai-kit/core"
import { createMemoryDataSource } from "@conusai-kit/data"
import { DEFAULT_FILES_POLICY, files } from "@conusai-kit/files"
import { fakeObjectStore } from "@conusai-kit/testing"
import { describe, expect, it } from "vitest"
import { addVersion, listDocuments, registerDocument, requestKey, versionFacts } from "../core/archive.js"
import { type ArchiveService, archiveReadsOf } from "../core/ports.js"
const scope: ProjectScope = {
level: "project",
actor: { userId: "01900000-0000-7000-0000-000000000009" },
orgId: "01900000-0000-7000-0000-000000000001",
workspaceId: "01900000-0000-7000-0000-000000000002",
projectId: "01900000-0000-7000-0000-000000000003",
}
const receipt = { documentId: "doc_1", revision: "1" }
function recording() {
const asked: Array<[string, unknown]> = []
const service: ArchiveService = {
listDocuments: async (query) => {
asked.push(["listDocuments", query])
return { rows: [] }
},
getDocument: async () => {
throw new Error("not asked")
},
listVersions: async () => ({ rows: [] }),
listRetentionClasses: async () => ({ rows: [] }),
listDisposalDue: async () => ({ rows: [] }),
registerDocument: async (input) => {
asked.push(["registerDocument", input])
return receipt
},
addVersion: async (input) => {
asked.push(["addVersion", input])
return receipt
},
setLegalHold: async () => receipt,
disposeDocument: async () => receipt,
createRetentionClass: async () => undefined,
}
return { service, asked }
}
async function storedFile() {
const data = createMemoryDataSource({ entities: [files] })
const deps = { data, store: fakeObjectStore(), policy: DEFAULT_FILES_POLICY }
const row = await data
.forScope(scope)
.of(files)
.insert({
status: "stored",
storageKey: "files/w/p/f",
filename: "record.pdf",
contentType: "application/pdf",
size: 1234,
sha256: "a".repeat(64),
uploadedBy: scope.actor.userId,
storedAt: "2026-11-02T10:00:00.000Z",
uploadId: null,
declaredSize: null,
partSize: null,
deletingAt: null,
})
return { deps, fileId: row.id }
}
describe("the archive use cases", () => {
it("list documents with a trimmed search and the module's page size", async () => {
const { service, asked } = recording()
await listDocuments(service, { q: " invoice " })
await listDocuments(service, { q: " " })
expect(asked).toEqual([
["listDocuments", { q: "invoice", limit: 50 }],
["listDocuments", { limit: 50 }],
])
})
it("take a version's facts from the stored file, with an opaque Blob reference", async () => {
const { deps, fileId } = await storedFile()
expect(await versionFacts(deps, scope, fileId)).toEqual({
sha256: "a".repeat(64),
mediaType: "application/pdf",
size: 1234,
blobRef: `conusai://blob/files/${fileId}`,
})
})
it("register a document with the project's request key and a trimmed title, and no kind unless asked", async () => {
const { deps, fileId } = await storedFile()
const { service, asked } = recording()
await registerDocument(service, deps, scope, {
title: " Signed contract ",
kind: " ",
retentionClass: "contracts",
fileId,
requestId: "r1",
})
expect(asked).toEqual([
[
"registerDocument",
{
title: "Signed contract",
retentionClass: "contracts",
version: await versionFacts(deps, scope, fileId),
key: requestKey(scope, "r1"),
},
],
])
})
it("add a version over the revision the page read", async () => {
const { deps, fileId } = await storedFile()
const { service, asked } = recording()
await addVersion(service, deps, scope, { documentId: "doc_1", revision: "4", fileId, requestId: "r2" })
expect(asked[0]?.[1]).toMatchObject({ documentId: "doc_1", revision: "4", key: requestKey(scope, "r2") })
})
it("refuse a file this project does not hold before the service is asked", async () => {
const { deps } = await storedFile()
const { service, asked } = recording()
await expect(
registerDocument(service, deps, scope, {
title: "Someone else's",
retentionClass: "contracts",
fileId: "01900000-0000-7000-0000-0000000000ff",
requestId: "r3",
}),
).rejects.toMatchObject({ problem: { status: 404 } })
expect(asked).toEqual([])
})
it("hand a read handler a view without a single write", () => {
expect(Object.keys(archiveReadsOf(recording().service)).sort()).toEqual([
"getDocument",
"listDisposalDue",
"listDocuments",
"listRetentionClasses",
"listVersions",
])
})
})