import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { test } from "vitest"; import canonicalSource from "../../../src/features/tech-log/contracts/studio/canonical-source.json" with { type: "json" }; import ciGates from "../../../config/ci/gates.json" with { type: "json" }; import packageDocument from "../../../package.json" with { type: "json" }; const YAML_PATH = "src/features/tech-log/contracts/studio/studio-api.openapi.yaml"; const DRIFT_GATE_SCRIPT = "check:tech-log-contract"; test("vendored contract matches the recorded canonical digest", () => { const bytes = readFileSync(YAML_PATH); const digest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`; assert.equal(digest, canonicalSource.digest); }); test("canonical source records the pinned revision and version", () => { assert.equal(canonicalSource.packageId, "@tech-log/studio-contract"); assert.equal(canonicalSource.version, "2.0.0"); // revision은 생성 시점에 기록된다. canonical 저장소는 활발히 편집 중이므로 // 특정 값을 박아두면 계약이 그대로인데도 테스트가 깨진다. 형식만 고정한다. assert.match(canonicalSource.sourceRevision, /^[0-9a-f]{7,64}$/); assert.match(canonicalSource.digest, /^sha256:[0-9a-f]{64}$/); }); test("canonical source lists all 19 operationIds", () => { assert.equal(canonicalSource.operationIds.length, 19); assert.ok(canonicalSource.operationIds.includes("uploadStudioAsset")); assert.ok(canonicalSource.operationIds.includes("getStudioSession")); }); test("vendored contract declares the CSRF header", () => { const yaml = readFileSync(YAML_PATH, "utf8"); assert.ok(yaml.includes("X-CSRF-TOKEN")); }); // 이 브랜치의 중심 산출물은 "canonical 계약이 다시 갈라지지 못하게 빌드로 막는다" // (§선택한 접근 A)이다. 스크립트가 존재하는 것만으로는 그 약속이 지켜지지 않는다. // 누군가 손으로 vendor yaml이나 generated.ts를 고쳐도, 실제로 실행되는 게이트가 // 하나도 그것을 보지 않으면 digest 고정은 의미를 잃는다. 아래 두 테스트가 // "실행 경로에 실제로 연결돼 있는가"를 검증한다. test("the contract drift check is a real CI gate command, not just a package script", () => { const command = ciGates.commands.find((entry) => entry.script === DRIFT_GATE_SCRIPT); assert.ok(command, `config/ci/gates.json declares no ${DRIFT_GATE_SCRIPT} command`); assert.equal(command.expect, "pass"); const owningGates = ciGates.gates.filter((gate) => (gate.commandIds as readonly string[]).includes(command.id), ); assert.equal( owningGates.length, 1, `${command.id} must be referenced by exactly one gate; found ${owningGates.length}`, ); }); test("test:all runs the contract drift check alongside the TechLog suite", () => { const segments = packageDocument.scripts["test:all"].split(" && ").map((value) => value.trim()); assert.ok( segments.includes(`corepack pnpm ${DRIFT_GATE_SCRIPT}`), `test:all does not run ${DRIFT_GATE_SCRIPT}: ${packageDocument.scripts["test:all"]}`, ); assert.ok(segments.includes("corepack pnpm test:tech-log"), packageDocument.scripts["test:all"]); });