🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
/**
|
||||
* Release Builder Agent Test — SF-07
|
||||
*
|
||||
* 验证 Release Package 生成器的所有关键功能。
|
||||
*/
|
||||
|
||||
import { describe, it, before } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { existsSync, rmSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
|
||||
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
||||
const WORKSPACE = resolve(__dirname, "..");
|
||||
|
||||
let buildRelease, writeRelease;
|
||||
|
||||
before(async () => {
|
||||
const mod = await import("../scripts/release-builder-agent.mjs");
|
||||
buildRelease = mod.buildRelease;
|
||||
writeRelease = mod.writeRelease;
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 1 — Release 目录结构生成
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("1 — Release 目录结构生成", () => {
|
||||
let result;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "PetCare" });
|
||||
});
|
||||
|
||||
it("生成成功(无错误)", () => {
|
||||
assert.ok(!result.error, `Unexpected error: ${result.error}`);
|
||||
assert.ok(result.stats.totalFiles >= 8, `Expected >= 8 files, got ${result.stats.totalFiles}`);
|
||||
});
|
||||
|
||||
it("包含 release/version.json", () => {
|
||||
assert.ok(result.files["release/version.json"], "Missing version.json");
|
||||
});
|
||||
|
||||
it("包含 release/manifests/manifest.json", () => {
|
||||
assert.ok(result.files["release/manifests/manifest.json"], "Missing manifest.json");
|
||||
});
|
||||
|
||||
it("包含 release/checksums/checksums.txt", () => {
|
||||
assert.ok(result.files["release/checksums/checksums.txt"], "Missing checksums.txt");
|
||||
});
|
||||
|
||||
it("包含 release/release-notes/release-notes.md", () => {
|
||||
assert.ok(result.files["release/release-notes/release-notes.md"], "Missing release-notes.md");
|
||||
});
|
||||
|
||||
it("包含 release/build-info.json", () => {
|
||||
assert.ok(result.files["release/build-info.json"], "Missing build-info.json");
|
||||
});
|
||||
|
||||
it("包含 release/README.md", () => {
|
||||
assert.ok(result.files["release/README.md"], "Missing README.md");
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 2 — version.json 验证
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("2 — version.json 验证", () => {
|
||||
let result, version;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "PetCare", version: "2.0.0", buildNumber: 5 });
|
||||
version = JSON.parse(result.files["release/version.json"]);
|
||||
});
|
||||
|
||||
it("包含正确名称", () => {
|
||||
assert.equal(version.name, "petcare");
|
||||
});
|
||||
|
||||
it("包含正确版本", () => {
|
||||
assert.equal(version.version, "2.0.0");
|
||||
});
|
||||
|
||||
it("包含正确构建号", () => {
|
||||
assert.equal(version.buildNumber, 5);
|
||||
});
|
||||
|
||||
it("包含所有平台", () => {
|
||||
assert.deepEqual(version.platforms, ["windows", "macos", "linux"]);
|
||||
});
|
||||
|
||||
it("默认版本是 1.0.0", () => {
|
||||
const r = buildRelease({ projectName: "Test" });
|
||||
const v = JSON.parse(r.files["release/version.json"]);
|
||||
assert.equal(v.version, "1.0.0");
|
||||
});
|
||||
|
||||
it("默认构建号是 1", () => {
|
||||
const r = buildRelease({ projectName: "Test" });
|
||||
const v = JSON.parse(r.files["release/version.json"]);
|
||||
assert.equal(v.buildNumber, 1);
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 3 — manifest.json 验证
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("3 — manifest.json 验证", () => {
|
||||
let result, manifest;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "CRM" });
|
||||
manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
|
||||
});
|
||||
|
||||
it("包含项目名称", () => {
|
||||
assert.equal(manifest.project, "CRM");
|
||||
});
|
||||
|
||||
it("包含版本", () => {
|
||||
assert.equal(manifest.version, "1.0.0");
|
||||
});
|
||||
|
||||
it("包含生成时间", () => {
|
||||
assert.ok(manifest.generatedAt, "Missing generatedAt");
|
||||
assert.ok(!isNaN(new Date(manifest.generatedAt).getTime()), "Invalid date");
|
||||
});
|
||||
|
||||
it("包含文件列表", () => {
|
||||
assert.ok(Array.isArray(manifest.files), "files should be array");
|
||||
assert.ok(manifest.files.length > 0, "files should not be empty");
|
||||
});
|
||||
|
||||
it("每个文件包含 path, size, sha256", () => {
|
||||
for (const f of manifest.files) {
|
||||
assert.ok(f.path, "Missing path");
|
||||
assert.ok(typeof f.size === "number", "Missing size");
|
||||
assert.ok(f.sha256, "Missing sha256");
|
||||
assert.ok(f.sha256.length === 64, "SHA256 should be 64 hex chars");
|
||||
}
|
||||
});
|
||||
|
||||
it("包含平台列表", () => {
|
||||
assert.deepEqual(manifest.platforms, ["windows", "macos", "linux"]);
|
||||
});
|
||||
|
||||
it("包含生成器版本", () => {
|
||||
assert.ok(manifest.generatorVersion, "Missing generatorVersion");
|
||||
});
|
||||
|
||||
it("totalFiles 与 files 数组长度一致", () => {
|
||||
assert.equal(manifest.totalFiles, manifest.files.length);
|
||||
});
|
||||
|
||||
it("totalSize 是所有文件大小之和", () => {
|
||||
const expectedSize = manifest.files.reduce((s, f) => s + f.size, 0);
|
||||
assert.equal(manifest.totalSize, expectedSize);
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 4 — checksums.txt 验证
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("4 — checksums.txt 验证", () => {
|
||||
let result, checksums;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "Inventory" });
|
||||
checksums = result.files["release/checksums/checksums.txt"];
|
||||
});
|
||||
|
||||
it("非空", () => {
|
||||
assert.ok(checksums.length > 0, "Empty checksums");
|
||||
});
|
||||
|
||||
it("每行格式为 SHA256 filename", () => {
|
||||
const lines = checksums.trim().split("\n");
|
||||
for (const line of lines) {
|
||||
const parts = line.split(/\s+/);
|
||||
assert.ok(parts.length >= 2, `Invalid line: ${line}`);
|
||||
assert.ok(parts[0].length === 64, `Invalid SHA256: ${parts[0]}`);
|
||||
assert.ok(parts[1].length > 0, `Missing filename`);
|
||||
}
|
||||
});
|
||||
|
||||
it("行数与 manifest 文件数一致", () => {
|
||||
const manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
|
||||
const lines = checksums.trim().split("\n");
|
||||
assert.equal(lines.length, manifest.files.length);
|
||||
});
|
||||
|
||||
it("SHA256 与 manifest 一致", () => {
|
||||
const manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
|
||||
const lines = checksums.trim().split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const [sha, path] = lines[i].split(/\s+/);
|
||||
assert.equal(sha, manifest.files[i].sha256, `SHA mismatch for ${path}`);
|
||||
assert.equal(path, manifest.files[i].path, `Path mismatch at line ${i}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 5 — release-notes.md 验证
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("5 — release-notes.md 验证", () => {
|
||||
let result, notes;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "PetCare" });
|
||||
notes = result.files["release/release-notes/release-notes.md"];
|
||||
});
|
||||
|
||||
it("非空", () => {
|
||||
assert.ok(notes.length > 0, "Empty release notes");
|
||||
});
|
||||
|
||||
it("包含项目名称", () => {
|
||||
assert.ok(notes.includes("PetCare"), "Missing project name");
|
||||
});
|
||||
|
||||
it("包含版本信息", () => {
|
||||
assert.ok(notes.includes("1.0.0"), "Missing version");
|
||||
});
|
||||
|
||||
it("包含功能列表", () => {
|
||||
assert.ok(notes.includes("Features"), "Missing features section");
|
||||
});
|
||||
|
||||
it("包含构建时间", () => {
|
||||
assert.ok(notes.includes("Build Time"), "Missing build time");
|
||||
});
|
||||
|
||||
it("包含平台信息", () => {
|
||||
assert.ok(notes.includes("windows"), "Missing windows");
|
||||
assert.ok(notes.includes("macos"), "Missing macos");
|
||||
assert.ok(notes.includes("linux"), "Missing linux");
|
||||
});
|
||||
|
||||
it("包含安装方式", () => {
|
||||
assert.ok(notes.includes("Installation"), "Missing installation section");
|
||||
assert.ok(notes.includes(".exe"), "Missing Windows installer");
|
||||
assert.ok(notes.includes(".dmg"), "Missing macOS DMG");
|
||||
assert.ok(notes.includes(".AppImage"), "Missing Linux AppImage");
|
||||
});
|
||||
|
||||
it("包含校验和引用", () => {
|
||||
assert.ok(notes.includes("checksums"), "Missing checksums reference");
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 6 — build-info.json 验证
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("6 — build-info.json 验证", () => {
|
||||
let result, buildInfo;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "CRM" });
|
||||
buildInfo = JSON.parse(result.files["release/build-info.json"]);
|
||||
});
|
||||
|
||||
it("包含 Node Version", () => {
|
||||
assert.ok(buildInfo.nodeVersion, "Missing nodeVersion");
|
||||
assert.ok(buildInfo.nodeVersion.startsWith("v"), "nodeVersion should start with v");
|
||||
});
|
||||
|
||||
it("包含 Generator Version", () => {
|
||||
assert.ok(buildInfo.generatorVersion, "Missing generatorVersion");
|
||||
});
|
||||
|
||||
it("包含 Build Time", () => {
|
||||
assert.ok(buildInfo.buildTime, "Missing buildTime");
|
||||
assert.ok(!isNaN(new Date(buildInfo.buildTime).getTime()), "Invalid buildTime");
|
||||
});
|
||||
|
||||
it("包含 Domain", () => {
|
||||
assert.ok("domain" in buildInfo, "Missing domain");
|
||||
});
|
||||
|
||||
it("包含 Entity Count", () => {
|
||||
assert.ok("entityCount" in buildInfo, "Missing entityCount");
|
||||
assert.ok(typeof buildInfo.entityCount === "number", "entityCount should be number");
|
||||
});
|
||||
|
||||
it("包含 Route Count", () => {
|
||||
assert.ok("routeCount" in buildInfo, "Missing routeCount");
|
||||
assert.ok(typeof buildInfo.routeCount === "number", "routeCount should be number");
|
||||
});
|
||||
|
||||
it("包含 Screen Count", () => {
|
||||
assert.ok("screenCount" in buildInfo, "Missing screenCount");
|
||||
assert.ok(typeof buildInfo.screenCount === "number", "screenCount should be number");
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 7 — Platform Artifact Layout
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("7 — Platform Artifact Layout", () => {
|
||||
let result;
|
||||
before(() => {
|
||||
result = buildRelease({ projectName: "PetCare" });
|
||||
});
|
||||
|
||||
it("包含 Windows 目录文件", () => {
|
||||
const winFiles = Object.keys(result.files).filter(p => p.startsWith("release/windows/"));
|
||||
assert.ok(winFiles.length >= 2, `Expected >= 2 Windows files, got ${winFiles.length}`);
|
||||
});
|
||||
|
||||
it("包含 macOS 目录文件", () => {
|
||||
const macFiles = Object.keys(result.files).filter(p => p.startsWith("release/macos/"));
|
||||
assert.ok(macFiles.length >= 2, `Expected >= 2 macOS files, got ${macFiles.length}`);
|
||||
});
|
||||
|
||||
it("包含 Linux 目录文件", () => {
|
||||
const linuxFiles = Object.keys(result.files).filter(p => p.startsWith("release/linux/"));
|
||||
assert.ok(linuxFiles.length >= 2, `Expected >= 2 Linux files, got ${linuxFiles.length}`);
|
||||
});
|
||||
|
||||
it("Windows 文件包含安装器占位", () => {
|
||||
const winExe = Object.entries(result.files).find(([p]) => p.includes("windows") && p.includes("setup"));
|
||||
assert.ok(winExe, "Missing Windows setup placeholder");
|
||||
assert.ok(winExe[1].includes("PLACEHOLDER"), "Should be placeholder");
|
||||
});
|
||||
|
||||
it("macOS 文件包含 DMG 占位", () => {
|
||||
const macDmg = Object.entries(result.files).find(([p]) => p.includes("macos") && p.endsWith(".dmg"));
|
||||
assert.ok(macDmg, "Missing macOS DMG placeholder");
|
||||
assert.ok(macDmg[1].includes("PLACEHOLDER"), "Should be placeholder");
|
||||
});
|
||||
|
||||
it("Linux 文件包含 AppImage 占位", () => {
|
||||
const linuxApp = Object.entries(result.files).find(([p]) => p.includes("linux") && p.includes("AppImage"));
|
||||
assert.ok(linuxApp, "Missing Linux AppImage placeholder");
|
||||
assert.ok(linuxApp[1].includes("PLACEHOLDER"), "Should be placeholder");
|
||||
});
|
||||
|
||||
it("所有平台文件非空", () => {
|
||||
const platformFiles = Object.entries(result.files).filter(([p]) =>
|
||||
p.startsWith("release/windows/") || p.startsWith("release/macos/") || p.startsWith("release/linux/")
|
||||
);
|
||||
for (const [path, content] of platformFiles) {
|
||||
assert.ok(content.length > 0, `${path}: empty content`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 8 — 文件写入 I/O
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("8 — 文件写入 I/O", () => {
|
||||
it("writeRelease 写入完整目录树", () => {
|
||||
const result = buildRelease({ projectName: "TestApp" });
|
||||
const tmpDir = resolve(WORKSPACE, ".tmp-release-test");
|
||||
|
||||
writeRelease(result, tmpDir);
|
||||
|
||||
assert.ok(existsSync(resolve(tmpDir, "release", "version.json")));
|
||||
assert.ok(existsSync(resolve(tmpDir, "release", "manifests", "manifest.json")));
|
||||
assert.ok(existsSync(resolve(tmpDir, "release", "checksums", "checksums.txt")));
|
||||
assert.ok(existsSync(resolve(tmpDir, "release", "release-notes", "release-notes.md")));
|
||||
assert.ok(existsSync(resolve(tmpDir, "release", "build-info.json")));
|
||||
assert.ok(existsSync(resolve(tmpDir, "release", "README.md")));
|
||||
|
||||
// Platform dirs
|
||||
const winDir = resolve(tmpDir, "release", "windows");
|
||||
const macDir = resolve(tmpDir, "release", "macos");
|
||||
const linuxDir = resolve(tmpDir, "release", "linux");
|
||||
assert.ok(existsSync(winDir), "Missing windows dir");
|
||||
assert.ok(existsSync(macDir), "Missing macos dir");
|
||||
assert.ok(existsSync(linuxDir), "Missing linux dir");
|
||||
|
||||
// Platform files
|
||||
const winFiles = readdirSync(winDir);
|
||||
assert.ok(winFiles.length >= 2, "Windows should have >= 2 files");
|
||||
const macFiles = readdirSync(macDir);
|
||||
assert.ok(macFiles.length >= 2, "macOS should have >= 2 files");
|
||||
const linuxFiles = readdirSync(linuxDir);
|
||||
assert.ok(linuxFiles.length >= 2, "Linux should have >= 2 files");
|
||||
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("所有生成的文件非空", () => {
|
||||
const result = buildRelease();
|
||||
for (const [path, content] of Object.entries(result.files)) {
|
||||
assert.ok(content.length > 0, `${path}: empty content`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 9 — Stats 验证
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("9 — Stats 验证", () => {
|
||||
it("统计正确", () => {
|
||||
const result = buildRelease({ projectName: "PetCare" });
|
||||
const stats = result.stats;
|
||||
|
||||
assert.ok(stats.totalFiles > 0, "totalFiles should be > 0");
|
||||
assert.ok(stats.platformFiles >= 6, "platformFiles should be >= 6 (2 per platform)");
|
||||
assert.ok(stats.manifestFiles >= 1, "manifestFiles should be >= 1");
|
||||
assert.ok(stats.checksumFiles >= 1, "checksumFiles should be >= 1");
|
||||
assert.ok(stats.releaseNotesFiles >= 1, "releaseNotesFiles should be >= 1");
|
||||
assert.ok(stats.metadataFiles >= 3, "metadataFiles should be >= 3 (version, manifest, build-info)");
|
||||
assert.ok(stats.totalSize > 0, "totalSize should be > 0");
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 10 — 多领域覆盖
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("10 — 多领域覆盖(PetCare / CRM / Inventory)", () => {
|
||||
const domains = [
|
||||
{ name: "PetCare", expected: "petcare" },
|
||||
{ name: "CRM", expected: "crm" },
|
||||
{ name: "Inventory", expected: "inventory" },
|
||||
];
|
||||
|
||||
for (const { name, expected } of domains) {
|
||||
it(`${name} — 生成成功`, () => {
|
||||
const result = buildRelease({ projectName: name });
|
||||
assert.ok(!result.error, `${name}: ${result.message}`);
|
||||
assert.ok(result.files["release/version.json"], `${name}: missing version.json`);
|
||||
assert.ok(result.files["release/manifests/manifest.json"], `${name}: missing manifest.json`);
|
||||
assert.ok(result.files["release/checksums/checksums.txt"], `${name}: missing checksums.txt`);
|
||||
assert.ok(result.files["release/release-notes/release-notes.md"], `${name}: missing release-notes.md`);
|
||||
assert.ok(result.files["release/build-info.json"], `${name}: missing build-info.json`);
|
||||
});
|
||||
|
||||
it(`${name} — version.json 包含正确名称`, () => {
|
||||
const result = buildRelease({ projectName: name });
|
||||
const version = JSON.parse(result.files["release/version.json"]);
|
||||
assert.equal(version.name, expected, `${name}: expected '${expected}', got '${version.name}'`);
|
||||
});
|
||||
|
||||
it(`${name} — manifest 包含正确项目名`, () => {
|
||||
const result = buildRelease({ projectName: name });
|
||||
const manifest = JSON.parse(result.files["release/manifests/manifest.json"]);
|
||||
assert.equal(manifest.project, name, `${name}: expected '${name}', got '${manifest.project}'`);
|
||||
});
|
||||
|
||||
it(`${name} — 包含三个平台目录`, () => {
|
||||
const result = buildRelease({ projectName: name });
|
||||
const paths = Object.keys(result.files);
|
||||
assert.ok(paths.some(p => p.startsWith("release/windows/")), `${name}: missing windows/`);
|
||||
assert.ok(paths.some(p => p.startsWith("release/macos/")), `${name}: missing macos/`);
|
||||
assert.ok(paths.some(p => p.startsWith("release/linux/")), `${name}: missing linux/`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 11 — PRD 集成(从项目目录读取元数据)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
describe("11 — PRD 集成", () => {
|
||||
it("从 prd.json 读取项目信息", () => {
|
||||
const petcareDir = resolve(WORKSPACE, ".benchmark", "petcare");
|
||||
if (!existsSync(resolve(petcareDir, "prd.json"))) return; // skip if no benchmark data
|
||||
|
||||
const result = buildRelease({ projectDir: petcareDir });
|
||||
assert.ok(!result.error, `Error: ${result.error}`);
|
||||
|
||||
const version = JSON.parse(result.files["release/version.json"]);
|
||||
assert.equal(version.name, "petcare", "Should read name from PRD");
|
||||
|
||||
const buildInfo = JSON.parse(result.files["release/build-info.json"]);
|
||||
assert.ok(buildInfo.entityCount > 0, "Should have entities from PRD");
|
||||
assert.ok(buildInfo.screenCount > 0, "Should have screens from PRD");
|
||||
});
|
||||
|
||||
it("从 crm benchmark 读取元数据", () => {
|
||||
const crmDir = resolve(WORKSPACE, ".benchmark", "crm");
|
||||
if (!existsSync(resolve(crmDir, "prd.json"))) return;
|
||||
|
||||
const result = buildRelease({ projectDir: crmDir });
|
||||
assert.ok(!result.error, `Error: ${result.error}`);
|
||||
|
||||
const buildInfo = JSON.parse(result.files["release/build-info.json"]);
|
||||
assert.ok(buildInfo.entityCount > 0, "Should have entities");
|
||||
});
|
||||
|
||||
it("从 inventory benchmark 读取元数据", () => {
|
||||
const invDir = resolve(WORKSPACE, ".benchmark", "inventory");
|
||||
if (!existsSync(resolve(invDir, "prd.json"))) return;
|
||||
|
||||
const result = buildRelease({ projectDir: invDir });
|
||||
assert.ok(!result.error, `Error: ${result.error}`);
|
||||
|
||||
const buildInfo = JSON.parse(result.files["release/build-info.json"]);
|
||||
assert.ok(buildInfo.entityCount > 0, "Should have entities");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user