/** * @file Tests scripts/check-mcp-build.js — the freshness gate for the committed * mcp/build artifact. Content hashing is the whole point: mtimes are meaningless * after a clone, where every file is stamped at checkout time in arbitrary order. * Runs against synthetic trees, never the real mcp/. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, beforeEach, after } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("fs"); const os = require("os"); const path = require("path"); const { sourceHash, mcpBuildStatus, writeHash } = require("../../scripts/check-mcp-build"); const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-mcpbuild-")); const SRC = path.join(ROOT, "mcp", "src"); const BUILD = path.join(ROOT, "mcp", "build"); function makeTree() { fs.rmSync(path.join(ROOT, "mcp"), { recursive: true, force: true }); fs.mkdirSync(SRC, { recursive: true }); fs.mkdirSync(BUILD, { recursive: true }); fs.writeFileSync(path.join(SRC, "index.ts"), "export const a = 1;\n"); fs.writeFileSync(path.join(ROOT, "mcp", "package.json"), '{"name":"x"}\n'); fs.writeFileSync(path.join(BUILD, "index.js"), "exports.a = 1;\n"); } after(() => fs.rmSync(ROOT, { recursive: true, force: true })); describe("mcp build freshness", () => { beforeEach(makeTree); it("fails when the build has no recorded hash", () => { const status = mcpBuildStatus(ROOT); assert.equal(status.ok, false); assert.match(status.reason, /no recorded source hash/); }); it("passes right after the hash is stamped", () => { writeHash(ROOT); assert.equal(mcpBuildStatus(ROOT).ok, true); }); it("fails when a source file changes after the build", () => { writeHash(ROOT); fs.writeFileSync(path.join(SRC, "index.ts"), "export const a = 2;\n"); const status = mcpBuildStatus(ROOT); assert.equal(status.ok, false); assert.match(status.reason, /stale/); }); it("fails when a source file is added after the build", () => { writeHash(ROOT); fs.writeFileSync(path.join(SRC, "extra.ts"), "export const b = 1;\n"); assert.equal(mcpBuildStatus(ROOT).ok, false); }); it("fails when the build output is missing entirely", () => { writeHash(ROOT); fs.rmSync(path.join(BUILD, "index.js")); assert.match(mcpBuildStatus(ROOT).reason, /missing/); }); it("ignores modification times — only content counts", () => { const before = sourceHash(ROOT); const future = Date.now() / 1000 + 10_000; fs.utimesSync(path.join(SRC, "index.ts"), future, future); assert.equal(sourceHash(ROOT), before); }); it("tracks the manifest, so a dependency bump invalidates the build", () => { writeHash(ROOT); fs.writeFileSync(path.join(ROOT, "mcp", "package.json"), '{"name":"x","version":"2"}\n'); assert.equal(mcpBuildStatus(ROOT).ok, false); }); }); describe("the committed mcp/build in this repo", () => { it("matches mcp/src", () => { const status = mcpBuildStatus(path.resolve(__dirname, "..", "..")); assert.equal(status.ok, true, status.reason); }); });