This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// The platform's services are really bound (plan item AT4.1, spec §9): the AI gateway, OCR, Blob, the
|
||||
// audit log and usage metering, built from the deployment contract by `platformClients`, bound to the
|
||||
// tokens modules resolve them by in BOTH composition roots (apps/web/src/server/ports.ts and
|
||||
// apps/worker/src/ports.ts), and each one reaching the platform over HTTP. The platform here is the
|
||||
// fake of @conusai-kit/testing; nothing between a token and its HTTP request is stubbed.
|
||||
|
||||
import { readFileSync } from "node:fs"
|
||||
import { loadAppEnv, PlatformEnvError, platformClients, probePlatform, publicUrlOf } from "@app/config"
|
||||
import { appModules } from "@app/modules"
|
||||
import { DOCUMENT_MODEL, DOCUMENT_OCR, MODEL_GATEWAY } from "@app/modules/documents"
|
||||
import { buildRegistry, Container } from "@conusai-kit/core"
|
||||
import { OBJECT_STORE } from "@conusai-kit/data"
|
||||
import { FILES_AUDIT, FILES_REGISTRY, FILES_USAGE } from "@conusai-kit/files"
|
||||
import { PROPOSALS_AUDIT, PROPOSALS_REGISTRY } from "@conusai-kit/proposals"
|
||||
import { startFakePlatform } from "@conusai-kit/testing/fake-platform"
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest"
|
||||
import { z } from "zod"
|
||||
import { bindWebPorts } from "../apps/web/src/server/ports.js"
|
||||
import { bindModulePorts } from "../apps/worker/src/ports.js"
|
||||
|
||||
const fixture = JSON.parse(
|
||||
readFileSync(new URL("../modules/documents/__tests__/fixtures/ocr-document.json", import.meta.url), "utf8"),
|
||||
) as Record<string, unknown>
|
||||
|
||||
type Platform = Awaited<ReturnType<typeof startFakePlatform>>
|
||||
|
||||
let platform: Platform
|
||||
beforeEach(async () => {
|
||||
platform = await startFakePlatform()
|
||||
})
|
||||
afterEach(async () => {
|
||||
await platform.close()
|
||||
})
|
||||
|
||||
const environment = (extra: Record<string, string> = {}): Record<string, string | undefined> => ({
|
||||
NODE_ENV: "test",
|
||||
CONUSAI_FAKE_PLATFORM: "1",
|
||||
CONUSAI_API_URL: `${platform.url}/api`,
|
||||
CONUSAI_API_TOKEN: "test-token",
|
||||
CONUSAI_PROJECT_ID: "42",
|
||||
CONUSAI_ORG_ID: "42",
|
||||
CONUSAI_ORG_SLUG: "owner-org",
|
||||
POSTGRES_URL: "postgresql://app_app:secret@db:5432/app",
|
||||
POSTGRES_READER_URL: "postgresql://app_reader:secret@db:5432/app",
|
||||
...extra,
|
||||
})
|
||||
|
||||
const setup = (service: "web" | "worker") => {
|
||||
const env = loadAppEnv(service, environment())
|
||||
const registry = buildRegistry([...appModules])
|
||||
const clients = platformClients(env, registry, environment())
|
||||
return { env, registry, clients }
|
||||
}
|
||||
|
||||
/**
|
||||
* What the fake platform recorded for one organization of the platform. It keeps every tenant apart,
|
||||
* as the real one does: usage arrives under the organization in its URL, everything else under the
|
||||
* default one.
|
||||
*/
|
||||
const stateOf = async (organization?: string) =>
|
||||
(await (
|
||||
await fetch(
|
||||
`${platform.url}/__e2e/state`,
|
||||
organization ? { headers: { "x-conusai-organization": organization } } : {},
|
||||
)
|
||||
).json()) as {
|
||||
audit: Array<{ action: string; resource_id: string | null }>
|
||||
usage: Array<Record<string, unknown>>
|
||||
blobs: Array<{ pathname: string; size: number }>
|
||||
ocr: unknown[]
|
||||
ai: Array<{ request?: { model?: string } }>
|
||||
}
|
||||
|
||||
describe("platformClients", () => {
|
||||
it("writes an audit event to the platform's audit log", async () => {
|
||||
const { clients } = setup("web")
|
||||
await clients.audit.log("tenancy.organization_created", {
|
||||
actor: { id: "user-1", kind: "human" },
|
||||
target: { id: "acme", type: "organization" },
|
||||
outcome: "success",
|
||||
})
|
||||
await clients.audit.flush()
|
||||
expect((await stateOf()).audit.map((event) => event.action)).toContain("tenancy.organization_created")
|
||||
})
|
||||
|
||||
it("meters usage against the ConusAI organization that owns the deployment, not against a customer", async () => {
|
||||
const { clients } = setup("web")
|
||||
clients.usage.track({
|
||||
customer: { externalId: "acme" },
|
||||
meter: "notes_created",
|
||||
quantity: 1,
|
||||
idempotencyKey: "n-1",
|
||||
})
|
||||
await clients.usage.flush()
|
||||
// The fake separates numeric ID from slug, matching the production billing route contract.
|
||||
const [fact] = (await stateOf("owner-org")).usage
|
||||
expect((await stateOf()).usage).toEqual([])
|
||||
expect(fact).toMatchObject({ meter: "notes_created", quantity: "1", idempotency_key: "n-1" })
|
||||
})
|
||||
|
||||
it("stores an object in Blob and reads its bytes back", async () => {
|
||||
const { clients } = setup("worker")
|
||||
const stored = await clients.objectStore.put("files/a.txt", "hello", { contentType: "text/plain" })
|
||||
expect((await stateOf()).blobs.map((blob) => blob.pathname)).toContain("files/a.txt")
|
||||
expect(await (await clients.objectStore.download(stored.url)).text()).toBe("hello")
|
||||
})
|
||||
|
||||
it("asks the AI gateway for a validated object, naming the model the app is configured with", async () => {
|
||||
platform.ai.respond(() => ({ content: JSON.stringify({ answer: "four" }) }))
|
||||
const { clients } = setup("worker")
|
||||
const reply = await clients.models.generateObject(z.object({ answer: z.string() }), [
|
||||
{ role: "user", content: "2 + 2?" },
|
||||
])
|
||||
expect(reply).toEqual({ answer: "four" })
|
||||
const [call] = (await stateOf()).ai
|
||||
expect(call?.request?.model).toBe(clients.model)
|
||||
})
|
||||
|
||||
it("reads a document through the OCR service", async () => {
|
||||
platform.ocr.setDocument(fixture)
|
||||
const { clients } = setup("worker")
|
||||
const job = await clients.documents.submit(new TextEncoder().encode("%PDF-1.4"), {
|
||||
filename: "a.pdf",
|
||||
idempotencyKey: "k-1",
|
||||
})
|
||||
const read = await clients.documents.wait(job, { intervalMs: 10 })
|
||||
expect(read.pages).toHaveLength((fixture as { pages: unknown[] }).pages.length)
|
||||
expect((await stateOf()).ocr).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("what each process binds", () => {
|
||||
const resolvable = (container: Container, tokens: Array<Parameters<Container["resolve"]>[0]>): boolean[] =>
|
||||
tokens.map((token) => {
|
||||
try {
|
||||
container.resolve(token)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
it("binds Blob, the AI gateway, OCR and the registries in the web process", () => {
|
||||
const { registry, clients } = setup("web")
|
||||
const container = new Container()
|
||||
bindWebPorts(container, clients, registry)
|
||||
expect(
|
||||
resolvable(container, [
|
||||
OBJECT_STORE,
|
||||
MODEL_GATEWAY,
|
||||
DOCUMENT_MODEL,
|
||||
DOCUMENT_OCR,
|
||||
FILES_REGISTRY,
|
||||
PROPOSALS_REGISTRY,
|
||||
]),
|
||||
).toEqual([true, true, true, true, true, true])
|
||||
// The sinks that belong to the outbox are the worker's alone.
|
||||
expect(resolvable(container, [FILES_AUDIT, FILES_USAGE, PROPOSALS_AUDIT])).toEqual([false, false, false])
|
||||
})
|
||||
|
||||
it("binds the same five and the audit and usage sinks in the worker process", () => {
|
||||
const { registry, clients } = setup("worker")
|
||||
const container = new Container()
|
||||
bindModulePorts(container, clients, registry)
|
||||
expect(
|
||||
resolvable(container, [
|
||||
OBJECT_STORE,
|
||||
MODEL_GATEWAY,
|
||||
DOCUMENT_MODEL,
|
||||
DOCUMENT_OCR,
|
||||
FILES_REGISTRY,
|
||||
PROPOSALS_REGISTRY,
|
||||
FILES_AUDIT,
|
||||
FILES_USAGE,
|
||||
PROPOSALS_AUDIT,
|
||||
]),
|
||||
).toEqual([true, true, true, true, true, true, true, true, true])
|
||||
})
|
||||
|
||||
it("sends the worker's usage and audit sinks to the platform, through the bound tokens", async () => {
|
||||
const { registry, clients } = setup("worker")
|
||||
const container = new Container()
|
||||
bindModulePorts(container, clients, registry)
|
||||
const usage = container.resolve(FILES_USAGE)
|
||||
await usage.track({
|
||||
customer: { externalId: "acme" },
|
||||
meter: "files_bytes_stored",
|
||||
quantity: 12,
|
||||
idempotencyKey: "f-1",
|
||||
})
|
||||
await usage.flush()
|
||||
await container.resolve(FILES_AUDIT).log("files.file_uploaded", {
|
||||
actor: { id: "user-1", kind: "human" },
|
||||
target: { id: "acme/file-1", type: "file" },
|
||||
})
|
||||
await clients.audit.flush()
|
||||
const state = await stateOf()
|
||||
expect((await stateOf("owner-org")).usage).toEqual([
|
||||
expect.objectContaining({ meter: "files_bytes_stored", quantity: "12" }),
|
||||
])
|
||||
expect(state.audit.map((event) => event.action)).toContain("files.file_uploaded")
|
||||
})
|
||||
})
|
||||
|
||||
describe("the platform contract at start-up", () => {
|
||||
const fake = (): Record<string, string | undefined> => environment()
|
||||
|
||||
it("refuses to start in production without the platform URL, naming the variable", () => {
|
||||
const { CONUSAI_API_URL: _url, CONUSAI_FAKE_PLATFORM: _fake, ...rest } = fake()
|
||||
const error = (() => {
|
||||
try {
|
||||
loadAppEnv("web", { ...rest, NODE_ENV: "production" })
|
||||
} catch (thrown) {
|
||||
return thrown
|
||||
}
|
||||
})()
|
||||
expect(error).toBeInstanceOf(PlatformEnvError)
|
||||
expect(error).toMatchObject({ variable: "CONUSAI_API_URL" })
|
||||
expect((error as Error).message).toMatch(/CONUSAI_API_URL is not set: link this project to the platform/)
|
||||
})
|
||||
|
||||
it("refuses to start without the platform token, whichever mode, naming the variable", () => {
|
||||
for (const NODE_ENV of ["production", "development", undefined]) {
|
||||
const { CONUSAI_API_TOKEN: _token, ...rest } = fake()
|
||||
const run = () =>
|
||||
loadAppEnv("worker", { ...rest, ...(NODE_ENV ? { NODE_ENV } : {}), CONUSAI_FAKE_PLATFORM: undefined })
|
||||
expect(run, String(NODE_ENV)).toThrow(PlatformEnvError)
|
||||
expect(run, String(NODE_ENV)).toThrow(/CONUSAI_API_TOKEN is not set/)
|
||||
}
|
||||
})
|
||||
|
||||
it("treats a run with no NODE_ENV as production, so the fake platform must be asked for in a development run", () => {
|
||||
const { NODE_ENV: _mode, ...rest } = fake()
|
||||
expect(() => loadAppEnv("web", rest)).toThrow(/CONUSAI_FAKE_PLATFORM is set in a production run/)
|
||||
})
|
||||
|
||||
it("points a development run that has no platform at `pnpm dev` and the explicit switch", () => {
|
||||
const { CONUSAI_API_URL: _url, CONUSAI_FAKE_PLATFORM: _fake, ...rest } = fake()
|
||||
expect(() => loadAppEnv("web", { ...rest, NODE_ENV: "development" })).toThrow(/pnpm dev.*CONUSAI_FAKE_PLATFORM=1/)
|
||||
})
|
||||
|
||||
it("accepts the fake platform in development and in a test, and says which it is using", () => {
|
||||
for (const NODE_ENV of ["development", "test"]) {
|
||||
expect(loadAppEnv("web", { ...fake(), NODE_ENV })).toMatchObject({ platform: "fake", production: false })
|
||||
}
|
||||
const { CONUSAI_FAKE_PLATFORM: _fake, ...real } = fake()
|
||||
expect(loadAppEnv("web", { ...real, NODE_ENV: "development" }).platform).toBe("platform")
|
||||
})
|
||||
|
||||
it("refuses the fake platform in a production run even when everything else is set", () => {
|
||||
expect(() => loadAppEnv("web", { ...fake(), NODE_ENV: "production" })).toThrow(PlatformEnvError)
|
||||
expect(() => loadAppEnv("web", { ...fake(), NODE_ENV: "production" })).toThrow(/CONUSAI_FAKE_PLATFORM/)
|
||||
})
|
||||
|
||||
it("refuses the fake switch when the platform URL is somebody's public address", () => {
|
||||
expect(() => loadAppEnv("web", { ...fake(), CONUSAI_API_URL: "https://api.conusai.example/api" })).toThrow(
|
||||
/CONUSAI_API_URL names api\.conusai\.example/,
|
||||
)
|
||||
// ... while a compose service name and loopback are what the fake platform runs at.
|
||||
expect(() => loadAppEnv("web", { ...fake(), CONUSAI_API_URL: "http://platform:4010/api" })).not.toThrow()
|
||||
expect(() => loadAppEnv("web", { ...fake(), CONUSAI_API_URL: "http://127.0.0.1:4010/api" })).not.toThrow()
|
||||
})
|
||||
|
||||
it("requires the owning organization in production, where usage would otherwise be metered against nothing", () => {
|
||||
const { CONUSAI_FAKE_PLATFORM: _fake, CONUSAI_ORG_ID: _org, ...rest } = fake()
|
||||
const production = { ...rest, NODE_ENV: "production" }
|
||||
expect(() => loadAppEnv("web", production)).toThrow(/CONUSAI_ORG_ID is not set/)
|
||||
expect(() => loadAppEnv("worker", production)).toThrow(/CONUSAI_ORG_ID is not set/)
|
||||
// The migrate job meters nothing.
|
||||
expect(() =>
|
||||
loadAppEnv("migrate", { ...production, POSTGRES_MIGRATE_URL: "postgresql://app_owner:secret@db:5432/app" }),
|
||||
).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("probePlatform", () => {
|
||||
it("uses public readiness when the platform descriptor is protected", async () => {
|
||||
const authorization: Array<string | null> = []
|
||||
const answered = await probePlatform(
|
||||
{ CONUSAI_API_URL: "https://platform.example.test/api" },
|
||||
{
|
||||
fetch: async (input, init) => {
|
||||
authorization.push(new Headers(init?.headers).get("authorization"))
|
||||
const path = new URL(String(input)).pathname
|
||||
// The real platform mounts its descriptor under authenticated /api;
|
||||
// /readyz is its public readiness contract.
|
||||
if (path === "/readyz") return new Response("ready")
|
||||
return new Response("not public", { status: path.startsWith("/api/") ? 401 : 404 })
|
||||
},
|
||||
},
|
||||
)
|
||||
expect(answered).toBe(true)
|
||||
expect(authorization).toEqual([null])
|
||||
})
|
||||
|
||||
it("is true when the platform answers its public readiness probe, and sends no credential", async () => {
|
||||
const seen: Array<string | null> = []
|
||||
const answered = await probePlatform(
|
||||
{ CONUSAI_API_URL: `${platform.url}/api` },
|
||||
{
|
||||
fetch: async (input, init) => {
|
||||
seen.push(new Headers(init?.headers).get("authorization"))
|
||||
return fetch(input, init)
|
||||
},
|
||||
},
|
||||
)
|
||||
expect(answered).toBe(true)
|
||||
expect(seen).toEqual([null])
|
||||
})
|
||||
|
||||
it("is false when nothing answers, without throwing", async () => {
|
||||
expect(await probePlatform({ CONUSAI_API_URL: "http://127.0.0.1:9/api" }, { timeoutMs: 500 })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("the public address", () => {
|
||||
it("is the one the deployment states", () => {
|
||||
expect(publicUrlOf({ NODE_ENV: "production", CONUSAI_PUBLIC_URL: "https://app.example.test" })).toBe(
|
||||
"https://app.example.test",
|
||||
)
|
||||
})
|
||||
|
||||
it("is refused by name in a production run that has none, and never guessed", () => {
|
||||
for (const env of [{ NODE_ENV: "production" }, {}]) {
|
||||
expect(() => publicUrlOf(env)).toThrow(PlatformEnvError)
|
||||
try {
|
||||
publicUrlOf(env)
|
||||
} catch (error) {
|
||||
expect((error as PlatformEnvError).variable).toBe("CONUSAI_PUBLIC_URL")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("falls back to the development address only in development and tests", () => {
|
||||
expect(publicUrlOf({ NODE_ENV: "development" })).toBe("http://localhost:3000")
|
||||
expect(publicUrlOf({ NODE_ENV: "test" })).toBe("http://localhost:3000")
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user