Files
openclaw/test/scripts/package-changelog.test.ts
2026-05-30 03:01:27 +02:00

196 lines
5.8 KiB
TypeScript

import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
extractCurrentPackageChangelog,
preparePackageChangelog,
resolvePackageChangelogVersions,
restorePackageChangelog,
} from "../../scripts/package-changelog.mjs";
function changelog(strings: TemplateStringsArray, ...values: string[]) {
return `${String.raw({ raw: strings }, ...values)
.replace(/^\n/u, "")
.trimEnd()}\n`;
}
const cumulativeChangelog = changelog`
# Changelog
Docs: https://docs.openclaw.ai
## Unreleased
### Fixes
- Pending note.
## 2026.5.28
### Highlights
- Current highlight.
### Changes
- Current change.
### Fixes
- Current fix.
## 2026.5.27
### Highlights
- Older highlight.
`;
describe("package-changelog", () => {
it("maps release-channel package versions to package changelog candidate headings", () => {
expect(resolvePackageChangelogVersions("2026.5.28")).toEqual(["2026.5.28"]);
expect(resolvePackageChangelogVersions("2026.5.28-1")).toEqual(["2026.5.28-1"]);
expect(resolvePackageChangelogVersions("2026.5.28-beta.1")).toEqual([
"2026.5.28-beta.1",
"2026.5.28",
"Unreleased",
]);
expect(resolvePackageChangelogVersions("2026.5.28-alpha.2")).toEqual([
"2026.5.28-alpha.2",
"2026.5.28",
"Unreleased",
]);
});
it("extracts only the package version stable release section", () => {
expect(extractCurrentPackageChangelog(cumulativeChangelog, "2026.5.28-beta.1")).toBe(
changelog`
# Changelog
Docs: https://docs.openclaw.ai
## 2026.5.28
### Highlights
- Current highlight.
### Changes
- Current change.
### Fixes
- Current fix.
`,
);
});
it("prefers an exact prerelease section when it exists", () => {
const source = changelog`
# Changelog
## 2026.5.28-beta.2
- Beta 2 package notes with enough release detail.
## 2026.5.28
- Stable.
`;
expect(extractCurrentPackageChangelog(source, "2026.5.28-beta.2")).toBe(changelog`
# Changelog
## 2026.5.28-beta.2
- Beta 2 package notes with enough release detail.
`);
});
it("uses Unreleased only as a prerelease fallback when no release heading exists", () => {
const source = changelog`
# Changelog
## Unreleased
- Pending beta package notes with enough release detail.
## 2026.5.27
- Older stable.
`;
expect(extractCurrentPackageChangelog(source, "2026.5.28-beta.1")).toBe(changelog`
# Changelog
## Unreleased
- Pending beta package notes with enough release detail.
`);
});
it("extracts exact correction release sections", () => {
const source = changelog`
# Changelog
## 2026.5.28-1
- Correction release notes with enough detail.
## 2026.5.28
- Stable.
`;
expect(extractCurrentPackageChangelog(source, "2026.5.28-1")).toBe(changelog`
# Changelog
## 2026.5.28-1
- Correction release notes with enough detail.
`);
});
it("fails closed when package version has no matching release section", () => {
expect(() => extractCurrentPackageChangelog(cumulativeChangelog, "2026.5.29")).toThrow(
"CHANGELOG.md does not contain a release section for 2026.5.29.",
);
});
it("fails closed when the packaged changelog is unexpectedly large", () => {
const source = changelog`
# Changelog
## 2026.5.28
${"é".repeat(260_000)}
`;
expect(() => extractCurrentPackageChangelog(source, "2026.5.28")).toThrow(
"exceeds the 512000 byte safety limit",
);
});
it("fails closed when the extracted release section is effectively empty", () => {
const source = changelog`
# Changelog
Docs: https://docs.openclaw.ai
## 2026.5.28
### Fixes
## 2026.5.27
- Older stable release notes with enough detail.
`;
expect(() => extractCurrentPackageChangelog(source, "2026.5.28")).toThrow(
"below the 32 byte safety minimum",
);
});
it("prepares and restores the packaged changelog without changing the source permanently", async () => {
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-package-changelog-"));
try {
writeFileSync(path.join(root, "package.json"), '{"version":"2026.5.28-beta.1"}\n', "utf8");
writeFileSync(path.join(root, "CHANGELOG.md"), cumulativeChangelog, "utf8");
await expect(preparePackageChangelog(root)).resolves.toBe(true);
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).not.toContain("## Unreleased");
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).not.toContain("## 2026.5.27");
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).toContain("## 2026.5.28");
await expect(restorePackageChangelog(root)).resolves.toBe(true);
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).toBe(cumulativeChangelog);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("refuses to restore stale backups over current changelog edits", async () => {
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-package-changelog-"));
const backupPath = path.join(
root,
".artifacts",
"package-changelog",
"CHANGELOG.md.prepack-backup",
);
const editedChangelog = cumulativeChangelog.replace("- Current fix.", "- Current fix edited.");
try {
writeFileSync(path.join(root, "package.json"), '{"version":"2026.5.28-beta.1"}\n', "utf8");
writeFileSync(path.join(root, "CHANGELOG.md"), editedChangelog, "utf8");
mkdirSync(path.dirname(backupPath), { recursive: true });
writeFileSync(backupPath, cumulativeChangelog, "utf8");
await expect(restorePackageChangelog(root)).rejects.toThrow(
"Refusing to restore packaged changelog backup",
);
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).toBe(editedChangelog);
expect(existsSync(backupPath)).toBe(true);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});