Files
pikasTech-HWLAB/internal/hwpod/spec-registry.test.ts
T
2026-07-21 02:15:34 +02:00

70 lines
5.1 KiB
TypeScript

/*
* SPEC: PJ2026-010102 HWPOD 工具;PJ2026-010103 HWPOD 服务;
* PJ2026-01010305 71FREQ 预装 draft-2026-06-26-71freq-v03-hwpod-preinstall。
* 责任: 验证 runtime spec CRUD、重启持久化与 YAML-first 内置冻结合同。
*/
import { afterEach, expect, test } from "bun:test";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { createHwpodHttpApp } from "./http.ts";
import { createHwpodSpecRegistry } from "./spec-registry.ts";
const temporaryDirs: string[] = [];
afterEach(async () => { await Promise.all(temporaryDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); });
test("runtime spec repository persists CRUD and freezes YAML-first built-ins", async () => {
const runtimeDir = await tempDir();
const builtIn = spec("builtin-pod", "/builtin");
const registry = createHwpodSpecRegistry({ runtimeDir, builtIns: [{ configRef: "config/hwpod.yaml#spec", sha256: "source-sha", document: builtIn }] });
const initial = await registry.list();
expect(initial.specs).toHaveLength(1);
expect(initial.specs[0]).toMatchObject({ hwpodId: "builtin-pod", authority: "yaml-first-builtin", mutable: false, frozen: true });
const created = await registry.create(spec("runtime-pod", "/runtime-v1"));
expect(created).toMatchObject({ status: "created", hwpodId: "runtime-pod", authority: "runtime", mutable: true, frozen: false, mutation: true });
const restarted = createHwpodSpecRegistry({ runtimeDir, builtIns: [{ configRef: "config/hwpod.yaml#spec", document: builtIn }] });
expect((await restarted.get("runtime-pod")).document.spec.workspace.path).toBe("/runtime-v1");
expect((await restarted.update("runtime-pod", spec("runtime-pod", "/runtime-v2"))).status).toBe("updated");
expect((await restarted.get("runtime-pod")).document.spec.workspace.path).toBe("/runtime-v2");
expect((await restarted.delete("runtime-pod")).status).toBe("deleted");
await expect(restarted.get("runtime-pod")).rejects.toMatchObject({ code: "hwpod_spec_not_found" });
await expect(restarted.create(spec("builtin-pod", "/shadow"))).rejects.toMatchObject({ code: "hwpod_spec_frozen", details: { mutation: false } });
await expect(restarted.update("builtin-pod", builtIn)).rejects.toMatchObject({ code: "hwpod_spec_frozen" });
await expect(restarted.delete("builtin-pod")).rejects.toMatchObject({ code: "hwpod_spec_frozen" });
expect((await restarted.get("builtin-pod")).document.spec.workspace.path).toBe("/builtin");
});
test("L1 HWPOD HTTP exposes runtime CRUD and typed frozen errors", async () => {
const runtimeDir = await tempDir();
const registry = createHwpodSpecRegistry({ runtimeDir, builtIns: [{ configRef: "config/hwpod.yaml#spec", document: spec("builtin-pod", "/builtin") }] });
const temporal = { close: async () => {}, start: async () => ({}), describe: async () => ({}), result: async () => ({}) };
const app = createHwpodHttpApp({ runtimeApiUrl: "http://runtime.invalid", runtimeApiAuthorization: "Bearer test", temporal, specRegistry: registry });
expect(await request(app, "GET", "/health/ready")).toMatchObject({ status: 200, body: { ok: true, specRegistry: "ready", specCount: 1, runtimeApi: "configured" } });
const created = await request(app, "POST", "/v1/hwpod/specs", { document: spec("runtime-http", "/http-v1") });
expect(created).toMatchObject({ status: 201, body: { ok: true, status: "created", hwpodId: "runtime-http" } });
expect(await request(app, "GET", "/v1/hwpod/specs/runtime-http")).toMatchObject({ status: 200, body: { document: { spec: { workspace: { path: "/http-v1" } } } } });
expect(await request(app, "PUT", "/v1/hwpod/specs/runtime-http", { document: spec("runtime-http", "/http-v2") })).toMatchObject({ status: 200, body: { status: "updated" } });
expect(await request(app, "DELETE", "/v1/hwpod/specs/runtime-http")).toMatchObject({ status: 200, body: { status: "deleted", mutation: true } });
const frozen = await request(app, "DELETE", "/v1/hwpod/specs/builtin-pod");
expect(frozen).toMatchObject({ status: 409, body: { ok: false, error: { code: "hwpod_spec_frozen", authority: "yaml-first-builtin", mutation: false } } });
expect((await request(app, "GET", "/v1/hwpod/specs/builtin-pod")).body.document.spec.workspace.path).toBe("/builtin");
await app.close();
});
async function request(app: any, method: string, pathname: string, body?: unknown) {
const response = await app.fetch(new Request(`http://hwpod.test${pathname}`, { method, headers: { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) }));
return { status: response.status, body: await response.json() };
}
async function tempDir() { const dir = await mkdtemp(path.join(tmpdir(), "hwpod-spec-registry-")); temporaryDirs.push(dir); return dir; }
function spec(name: string, workspacePath: string) { return { apiVersion: "hwlab.dev/v0alpha1", kind: "Hwpod", metadata: { name }, spec: { targetDevice: { board: "test" }, workspace: { path: workspacePath }, debugProbe: { type: "test" }, ioProbe: { type: "test" }, nodeBinding: { nodeId: "node-test" } } }; }