108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { readdirSync, existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
function walkFiles(dir: string, exts = [".ts", ".js"]): string[] {
|
|
const out: string[] = [];
|
|
function walk(d: string) {
|
|
if (!existsSync(d)) return;
|
|
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
const p = join(d, e.name);
|
|
if (e.isDirectory()) walk(p);
|
|
else if (exts.some((ext) => p.endsWith(ext))) out.push(p);
|
|
}
|
|
}
|
|
walk(dir);
|
|
return out;
|
|
}
|
|
|
|
describe("architectural boundaries — Stage 1 gate (B-1…B-7)", () => {
|
|
it("src/ contains shell, verifier, and Stage 8 activation coordinator", () => {
|
|
const tsFiles = walkFiles("src", [".ts"]).sort();
|
|
// Stage 6 adds pipeline (canonical-json/hash/budgets/gates/manifest/package/sign/emergency/fixtures) on top of Stage 5 persistence
|
|
expect(tsFiles).toEqual(
|
|
expect.arrayContaining([
|
|
"src/app/layout.ts",
|
|
"src/app/main.ts",
|
|
"src/ui/router/router.ts",
|
|
"src/data/festival-package/types.ts",
|
|
"src/domain/clock/logic.ts",
|
|
"src/platform/idb/wrapper.ts",
|
|
"src/platform/idb/names.ts",
|
|
"src/data/system-meta/store.ts",
|
|
"src/data/slot/store.ts",
|
|
"src/data/user/store.ts",
|
|
"src/sync/verifier/ed25519.ts",
|
|
"src/sync/verifier/hash.ts",
|
|
"src/sync/verifier/types.ts",
|
|
"src/sync/activation.ts",
|
|
]),
|
|
);
|
|
const pipelineFiles = walkFiles("pipeline", [".ts"]).sort();
|
|
expect(pipelineFiles).toEqual(
|
|
expect.arrayContaining([
|
|
"pipeline/package.ts",
|
|
"pipeline/manifest.ts",
|
|
"pipeline/gates.ts",
|
|
"pipeline/hash.ts",
|
|
"pipeline/canonical-json.ts",
|
|
"pipeline/budgets.ts",
|
|
"pipeline/emergency.ts",
|
|
"pipeline/sign.ts",
|
|
]),
|
|
);
|
|
// Stage 9 adds only the byte transport seam; no later sync orchestration is present.
|
|
expect(tsFiles).toEqual(
|
|
expect.arrayContaining([
|
|
"src/sync/transport/types.ts",
|
|
"src/sync/transport/http.ts",
|
|
"src/sync/pull.ts",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("directory structure matches IMPLEMENTATION-CONTRACT.md §4", () => {
|
|
const requiredDirs = [
|
|
"src/platform/idb",
|
|
"src/platform/cache",
|
|
"src/platform/sw",
|
|
"src/storage",
|
|
"src/data",
|
|
"src/sync/transport",
|
|
"src/sync/verifier",
|
|
"src/domain/emergency",
|
|
"src/domain/schedule",
|
|
"src/domain/map",
|
|
"src/domain/festival",
|
|
"src/domain/readiness",
|
|
"src/domain/clock",
|
|
"src/domain/favorites",
|
|
"src/ui/components",
|
|
"src/ui/views",
|
|
"src/ui/router",
|
|
"src/ui/render",
|
|
"src/app",
|
|
"src/emergency-baseline",
|
|
"public",
|
|
"content",
|
|
"pipeline",
|
|
"tests/unit",
|
|
"tests/integration",
|
|
"tests/device-matrix",
|
|
];
|
|
for (const d of requiredDirs) expect(existsSync(d), `missing dir ${d}`).toBe(true);
|
|
});
|
|
|
|
it("forbidden v1 scope is not present (§2 — mesh/accounts/auth)", () => {
|
|
const allFiles = [...walkFiles("src"), ...walkFiles("tests")].join("\n");
|
|
expect(allFiles).not.toMatch(/mesh/i);
|
|
expect(allFiles).not.toMatch(/auth/i);
|
|
});
|
|
|
|
it("eslint boundaries config exists and lists B-1…B-7 elements", async () => {
|
|
// eslint check is via lint job; dynamic import of .js config has no types
|
|
// @ts-expect-error — no types for flat config
|
|
const cfg: unknown = await import("../../eslint.config.js");
|
|
expect(cfg).toBeDefined();
|
|
});
|
|
});
|