openclaw/test/scripts/render-github-release-notes.test.ts
Peter Steinberger f05a7a8c14
test(agents,ai,codex,gateway,infra,scripts): remove low-value tests (batch d397) (#179639)
* test: remove low-value tests (batch d397)

Retain independent test and fixture cleanup while preserving main's test
file inventory, CI metadata, and distinct authorization/lifecycle proof.
Repair fixture callback types and retain a portable WebSocket mock contract.

Retained campaign changes, with current-main conflict and review exclusions:
af1067dcd12debe2947c34cd20d89a8600d25964 test(gateway): deslop y0054 tests
cc8aaaddb712d5a31c0998408585abe80dd88289 test(scripts): deslop y0055 tests
3f124d71f12a0c9123f7d6db1faad13ccdc10715 test(gateway): deslop y0057 tests
6b486a8191a8bd5c77076e2f37018177fd3ed679 test(ui): deslop y0056 tests
407d44c63a987b8001c2d6706b30681c0697bfae test(infra): deslop y0058 tests
b4339db99e4eda03669e32841d7e86fb214f8794 test(ai): deslop y0059 tests
72dee977a7a5ce88b481a3997d3cc0ed951b596b test(agents): deslop y0061 tests
b64b50b13f41d9530c77d524f665e11f9843e818 test(codex): deslop y0062 tests
b317b9a6f7f87ab5b0865ba80e3d50dac48b67bc test(scripts): deslop y0064 tests

Plain changed-file formatting/lint and diff checks pass. Runtime/type/build
proof is delegated to hosted CI under the operator's CI-only compute budget.

* test(gateway): preserve async publication effect contract

* test: withdraw UI cleanup from batch d397

Preserve main UI tests unchanged after an unproven UI unit timeout. The newer scheduled main UI shard passed, so no inherited-failure exception is claimed. Retain only the independently reviewed non-UI deslop changes.
2026-10-21 18:54:19 +00:00

399 lines
14 KiB
TypeScript

import { execFileSync } from "node:child_process";
import { writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { splitChangelog } from "../../scripts/lib/release-changelog.mjs";
import {
GITHUB_RELEASE_BODY_MAX_BYTES,
GITHUB_RELEASE_BODY_MAX_CHARACTERS,
extractChangelogSection,
formatContributionRecordProvenance,
formatShippedBaselineExclusions,
parseContributionRecordProvenance,
parseShippedBaselineExclusions,
releaseNotesVersionForTag,
renderGithubReleaseNotes,
verifyGithubReleaseNotes,
} from "../../scripts/render-github-release-notes.mts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const repository = "openclaw/openclaw";
const tag = "v2026.7.1-beta.3";
const version = "2026.7.1";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function changelogFor(record: string): string {
return [
"# Changelog",
`## ${version}`,
"### Highlights\n\n- A grouped user-facing highlight.",
"### Fixes\n\n- A grouped user-facing fix.",
`### Complete contribution record\n\n${record}`,
"## 2026.6.11\n\n- Previous release.\n",
].join("\n\n");
}
function renderNotes(
options: Pick<Parameters<typeof renderGithubReleaseNotes>[0], "changelog"> &
Partial<Parameters<typeof renderGithubReleaseNotes>[0]>,
) {
return renderGithubReleaseNotes({ version, tag, repository, ...options });
}
function releaseRepo() {
const rootDir = tempDirs.make("openclaw-release-render-");
const git = (...args: string[]) =>
execFileSync("git", args, { cwd: rootDir, encoding: "utf8" }).trim();
git("init", "-q");
git("config", "user.name", "Release Fixture");
git("config", "user.email", "release-fixture@openclaw.invalid");
git("config", "commit.gpgsign", "false");
const render = (ref: string, releaseTag = tag, extra: string[] = []) =>
execFileSync(
process.execPath,
[
resolve("scripts/render-github-release-notes.mts"),
"--root",
rootDir,
"--ref",
ref,
"--tag",
releaseTag,
"--repository",
repository,
...extra,
],
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
);
return { rootDir, git, render };
}
describe("GitHub release-note rendering", () => {
it("refuses docs-mirror source in the initial renderer", () => {
expect(() =>
renderNotes({
changelog: `## ${version}\n\n<!-- openclaw-docs-mirror-v1 {} -->\n\nPublished reader prose.\n`,
}),
).toThrow("docs-publication renderer");
});
it.each([false, true])(
"renders pinned legacy and split sources through the CLI (compact=%s)",
(compact) => {
const { rootDir, git, render } = releaseRepo();
const changelog = changelogFor(
`- **PR #123** ${compact ? "record ".repeat(20_000) : "fix: example."}`,
);
writeFileSync(join(rootDir, "CHANGELOG.md"), changelog);
git("add", ".");
git("commit", "-qm", "legacy release");
const legacy = git("rev-parse", "HEAD");
splitChangelog({ rootDir });
git("add", ".");
git("commit", "-qm", "split release");
const split = git("rev-parse", "HEAD");
writeFileSync(
join(rootDir, `CHANGELOG/${version}.md`),
`## ${version}\n\nUncommitted drift.\n`,
);
const legacyBody = render(legacy);
const splitBody = render(split);
const bodyPath = join(rootDir, "release-body.md");
writeFileSync(bodyPath, splitBody);
const verify = () => render(split, tag, ["--verify-body", bodyPath]);
expect(verify()).toBe("");
writeFileSync(bodyPath, `${splitBody}\nUnapproved appended prose.\n`);
expect(verify).toThrow("Release body does not match canonical release notes.");
expect(legacyBody).not.toContain("Uncommitted drift");
expect(splitBody).not.toContain("Uncommitted drift");
if (compact) {
expect(legacyBody).toContain(`/blob/${tag}/CHANGELOG.md#complete-contribution-record`);
expect(splitBody).toContain(
`/blob/${tag}/CHANGELOG/records/${version}.md#complete-contribution-record`,
);
expect(splitBody).toBe(
legacyBody.replace("/CHANGELOG.md#", `/CHANGELOG/records/${version}.md#`),
);
} else {
expect(splitBody).toBe(legacyBody);
expect(splitBody).toBe(extractChangelogSection(changelog, version));
}
},
);
it("renders and verifies a pinned beta delta instead of cumulative stable notes", () => {
const { rootDir, git, render } = releaseRepo();
const delta = `## ${tag.slice(1)}\n\n### Fixes\n\n- New beta-only fix.`;
writeFileSync(
join(rootDir, "CHANGELOG.md"),
`${delta}\n\n## ${version}\n\n- Cumulative stable feature.\n`,
);
splitChangelog({ rootDir });
git("add", ".");
git("commit", "-qm", "beta delta");
const ref = git("rev-parse", "HEAD");
writeFileSync(join(rootDir, `CHANGELOG/${tag.slice(1)}.md`), `${delta}\nUncommitted drift.\n`);
expect(render(ref)).toBe(delta);
expect(render(ref, `v${version}`)).toBe(`## ${version}\n\n- Cumulative stable feature.`);
const bodyPath = join(rootDir, "body.md");
writeFileSync(bodyPath, delta);
expect(render(ref, tag, ["--version", tag.slice(1), "--verify-body", bodyPath])).toBe("");
});
it("round-trips canonical contribution provenance and accepts published legacy lines", () => {
const target = "a".repeat(40);
const canonical = {
base: "v2026.7.2-beta.7",
target,
inRangePullRequests: 1,
retainedSeedOnlyPullRequests: 0,
uniquePullRequests: 1,
};
const singular = formatContributionRecordProvenance(canonical);
const commaSeparated = formatContributionRecordProvenance({
...canonical,
inRangePullRequests: 1_234,
retainedSeedOnlyPullRequests: 56,
uniquePullRequests: 1_290,
});
expect(singular).toContain("1 in-range PR + 0 retained seed-only PRs = 1 unique PR.");
expect(commaSeparated).toContain(
"1,234 in-range PRs + 56 retained seed-only PRs = 1,290 unique PRs.",
);
expect(
parseContributionRecordProvenance(
[singular, "", "#### Pull requests", "", "- **PR #123** fix: canonical example."].join(
"\n",
),
),
).toEqual(canonical);
const legacy = parseContributionRecordProvenance(
[
`This audited record covers the complete v2026.7.2-beta.6..02d06caeb0febe7ec3c0df1454b85c38f3fb27d1 history: 1 merged PR. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.`,
"",
"#### Pull requests",
"",
"- **PR #123** fix: legacy example.",
].join("\n"),
);
expect(legacy).toMatchObject({ uniquePullRequests: 1 });
expect(() => formatContributionRecordProvenance(legacy!)).toThrow("requires split PR counts");
for (const [line, error] of [
[
commaSeparated.replace("= 1,290 unique PRs", "= 1,291 unique PRs"),
"provenance arithmetic is invalid",
],
[commaSeparated.replace("1,234", "1234"), "provenance is malformed"],
[singular.replace("1 in-range", "001 in-range"), "provenance is malformed"],
] as const) {
expect(() => parseContributionRecordProvenance(line)).toThrow(error);
}
expect(() => parseContributionRecordProvenance(singular)).toThrow(
"positive contribution record requires a Pull requests section",
);
});
it("prefixes extended-stable notes with immutable regular-stable context", () => {
const extendedVersion = "2026.8.35";
const extendedTag = `v${extendedVersion}`;
const regularStableVersion = "2026.9.5";
const changelog = changelogFor("- **PR #123** fix: example.").replaceAll(
version,
extendedVersion,
);
const rendered = renderNotes({
changelog,
version: extendedVersion,
tag: extendedTag,
regularStableVersion,
});
expect(
rendered.body.startsWith(
"This is a gateway-only `extended-stable` release, which is our current equivalent to LTS. " +
"This release is OpenClaw from the end of August 2026, plus critical security updates, " +
"reliability and performance fixes, and features like new model support. " +
"The latest version of OpenClaw at the time of this release is " +
"[2026.9.5](https://github.com/openclaw/openclaw/releases#release-v2026.9.5)\n\n" +
"## 2026.8.35",
),
).toBe(true);
expect(
verifyGithubReleaseNotes({
body: rendered.body,
changelog,
version: extendedVersion,
tag: extendedTag,
repository,
regularStableVersion,
}).matches,
).toBe(true);
expect(() =>
renderNotes({
changelog,
version: extendedVersion,
tag: extendedTag,
}),
).toThrow("regular stable version must be a string");
});
it("uses the full form at exactly 125,000 bytes and compacts at 125,001", () => {
const seed = renderNotes({
changelog: changelogFor("x"),
});
const exactRecordLength =
GITHUB_RELEASE_BODY_MAX_BYTES - seed.size.bytes + Buffer.byteLength("x");
const exact = renderNotes({
changelog: changelogFor("x".repeat(exactRecordLength)),
});
const over = renderNotes({
changelog: changelogFor("x".repeat(exactRecordLength + 1)),
});
expect(exact.mode).toBe("full");
expect(exact.size).toEqual({
characters: GITHUB_RELEASE_BODY_MAX_CHARACTERS,
bytes: GITHUB_RELEASE_BODY_MAX_BYTES,
});
expect(over.mode).toBe("compact");
});
it("normalizes correction tags to the stable changelog section", () => {
expect(releaseNotesVersionForTag("v2026.7.1-2")).toBe("2026.7.1");
const rendered = renderNotes({
changelog: changelogFor("- **PR #123** fix: correction."),
tag: "v2026.7.1-2",
});
expect(rendered.body).toContain("## 2026.7.1");
});
it("round-trips canonical shipped baseline exclusions and rejects malformed metadata", () => {
const line = formatShippedBaselineExclusions([
{ ref: "v2026.6.11", count: 2, pullRequests: [108, 101] },
{ ref: "v2026.6.10-beta.2", count: 0, pullRequests: [] },
]);
expect(line).toBe(
"Shipped baseline exclusions: v2026.6.10-beta.2 (0 PRs); v2026.6.11 (2 PRs: #101, #108).",
);
expect(parseShippedBaselineExclusions(line)).toEqual([
{ ref: "v2026.6.10-beta.2", count: 0, pullRequests: [] },
{ ref: "v2026.6.11", count: 2, pullRequests: [101, 108] },
]);
expect(() =>
parseShippedBaselineExclusions("Shipped baseline exclusion: v2026.6.11 (8 PRs)."),
).toThrow("malformed shipped baseline exclusion");
expect(() =>
parseShippedBaselineExclusions("Shipped baseline exclusions: v2026.6.11 (2 PRs: #101)."),
).toThrow("invalid shipped baseline exclusion count");
});
it("rejects tag/version drift and legacy oversized ledger anchors", () => {
expect(() =>
renderNotes({
changelog: changelogFor("- **PR #123** fix: example."),
tag: "v2026.7.2-beta.1",
}),
).toThrow("requires CHANGELOG.md version 2026.7.2");
expect(() =>
renderNotes({
changelog: changelogFor(
`### Complete contribution ledger\n\n${"legacy ".repeat(20_000)}`,
).replace("### Complete contribution record\n\n", ""),
}),
).toThrow("cannot be compacted without a complete contribution record");
});
it("prefers an alpha tag's exact changelog heading over the Unreleased fallback", () => {
// Shipped alpha tags carry their own heading with no base section,
// matching the tagged CHANGELOG.md shape of v2026.6.20-alpha.1.
const changelog = [
"# Changelog",
"## 2026.7.1-alpha.2\n\n- Alpha-only fix.",
"## 2026.6.11\n\n- Previous release.\n",
].join("\n\n");
const rendered = renderNotes({
changelog,
tag: "v2026.7.1-alpha.2",
});
expect(rendered.body).toContain("## 2026.7.1-alpha.2");
expect(rendered.body).toContain("Alpha-only fix.");
expect(rendered.body).not.toContain("Previous release.");
});
it("permits the Unreleased fallback only for alpha tags", () => {
const changelog = changelogFor("- **PR #123** fix: example.").replace(
"## 2026.7.1",
"## Unreleased",
);
const rendered = renderNotes({
changelog,
tag: "v2026.7.1-alpha.1",
});
expect(rendered.body).toContain("## 2026.7.1");
expect(rendered.body).not.toContain("## Unreleased");
expect(() => renderNotes({ changelog })).toThrow("CHANGELOG.md does not contain ## 2026.7.1");
expect(() =>
renderNotes({
changelog,
version: "foo",
tag: "vfoo-alpha.1",
}),
).toThrow("invalid release tag");
});
it("compacts at the real contribution record instead of a fenced pseudo-heading", () => {
const changelog = changelogFor(`- **PR #123** ${"record-only-detail ".repeat(9_000)}`).replace(
"### Fixes",
["```md", "### Complete contribution record", "```", "", "### Fixes"].join("\n"),
);
const rendered = renderNotes({ changelog });
expect(rendered.mode).toBe("compact");
expect(rendered.body).toContain("```md\n### Complete contribution record\n```");
expect(rendered.body).toContain("- A grouped user-facing fix.");
expect(rendered.body).not.toContain("record-only-detail");
});
it("verifies the exact generated compact body and optional proof tail", () => {
const changelog = changelogFor(`- **PR #123** ${"z".repeat(130_000)}`);
const verification = "### Release verification\n\n- release SHA: `abc123`";
const rendered = renderNotes({
changelog,
verification,
});
expect(
verifyGithubReleaseNotes({
body: rendered.body,
changelog,
version,
tag,
repository,
}),
).toMatchObject({ matches: true, mode: "compact" });
expect(
verifyGithubReleaseNotes({
body: rendered.body.replace("tag-pinned", "mutable"),
changelog,
version,
tag,
repository,
}).matches,
).toBe(false);
expect(
verifyGithubReleaseNotes({
body: rendered.body,
changelog,
version,
tag: "v2026.7.1-beta.2",
repository,
}).matches,
).toBe(false);
});
});