// @vitest-environment node import { readFileSync, readdirSync } from "node:fs"; import { describe, expect, it } from "vitest"; // 사용자 지정 문자열은 편집자가 회사 이름을 추측해 정규화하지 못하도록 // 화면 테스트와 문서 계약에서 원문 그대로 고정한다. describe("릴리스 문서 계약", () => { it("Node 최소 버전은 잠긴 의존성이 요구하는 22.22.2와 일치한다", () => { const metadata = JSON.parse(readFileSync("package.json", "utf8")); const lock = JSON.parse(readFileSync("package-lock.json", "utf8")); expect.soft(metadata.engines.node).toBe(">=22.22.2"); expect.soft(lock.packages[""].engines.node).toBe(">=22.22.2"); expect(lock.packages["node_modules/jsdom"].engines.node).toContain( "^22.22.2", ); for (const path of ["README.md", "docs/DEPLOYMENT.md"]) { const document = readFileSync(path, "utf8"); expect.soft(document, path).toContain("22.22.2"); expect.soft(document, path).not.toContain("22.12"); } }); it("프로젝트는 비공개 권리 유보이며 저장소 주소를 명시한다", () => { const metadata = JSON.parse(readFileSync("package.json", "utf8")); const lock = JSON.parse(readFileSync("package-lock.json", "utf8")); expect.soft(metadata.private).toBe(true); expect.soft(metadata.license).toBe("UNLICENSED"); expect.soft(lock.packages[""].license).toBe("UNLICENSED"); expect.soft(metadata.repository).toEqual({ type: "git", url: "https://source.ibit.kr/OZ-Group/Find-Word.git", }); expect .soft(metadata.homepage) .toBe("https://source.ibit.kr/OZ-Group/Find-Word"); const license = readFileSync("LICENSE", "utf8"); expect(license).toMatch(/private/i); expect(license).toContain("All rights reserved."); expect(readFileSync("README.md", "utf8")).toContain(metadata.homepage); }); it("잠금 파일의 모든 런타임 의존성 notice 전문을 배포용 사본에 보존한다", () => { const lock = JSON.parse(readFileSync("package-lock.json", "utf8")) as { packages: Record; }; const notice = readFileSync("THIRD_PARTY_NOTICES.md", "utf8"); expect(readFileSync("public/THIRD_PARTY_NOTICES.md", "utf8")).toBe(notice); for (const [path, pkg] of Object.entries(lock.packages)) { if (!path || pkg.dev) continue; const name = path.split("node_modules/").at(-1)!; const license = readFileSync(`${path}/LICENSE`, "utf8").trim(); expect(notice).toContain(`## ${name} ${pkg.version}\n\n${license}`); } }); it("브랜드 문구를 사용자 원문 그대로 문서에도 보존한다", () => { const credit = "Designed by OZ at Heat-T Medical Inc."; for (const path of [ "README.md", ".agent/TASK.md", ".agent/ACCEPTANCE.md", ".agent/CONTEXT.md", ]) { expect.soft(readFileSync(path, "utf8"), path).toContain(credit); } for (const path of [ "README.md", ...["docs", ".agent"].flatMap((dir) => readdirSync(dir) .filter((file) => file.endsWith(".md")) .map((file) => `${dir}/${file}`), ), ]) { // 잘못된 표기를 다시 권장하는 문서가 남아 있으면 다음 편집 때 회귀한다. expect .soft(readFileSync(path, "utf8"), path) .not.toContain(["HearT", "M"].join("-")); } }); }); // 명시적 아이콘이 없으면 Chrome이 /favicon.ico를 요청하여 정상 실행 콘솔에 404를 남긴다. it("HTML은 같은 문서의 내장 아이콘을 제공해 누락 favicon 요청을 막는다", () => { const html = readFileSync("index.html", "utf8"); expect(html).toMatch(/