Files
better-auth/packages/release-tooling/test/changeset-rewrite.test.ts

152 lines
4.3 KiB
TypeScript

import type { JSONValue, LanguageModel } from "ai";
import { asSchema } from "ai";
import { describe, expect, it } from "vitest";
import type { StructuredGenerator } from "../src/ai/generate-structured.ts";
import { models } from "../src/ai/models.ts";
import {
createChangesetFallback,
rewriteChangesetDescription,
} from "../src/changesets/rewrite.ts";
function modelId(model: LanguageModel): string {
return typeof model === "string" ? model : model.modelId;
}
function generatorFor(
value: JSONValue,
onRequest?: (request: Parameters<StructuredGenerator>[0]) => void,
): StructuredGenerator {
return async (request) => {
onRequest?.(request);
const validate = asSchema(request.schema).validate;
if (!validate) throw new Error("Schema does not provide validation");
const result = await validate(value);
if (!result.success) throw result.error;
return result.value;
};
}
describe("changeset rewriting", () => {
it("preserves safe deterministic fallback copy", () => {
const description = "Fix duplicate session refreshes.";
expect(createChangesetFallback(description)).toBe(description);
});
it("uses a safe deterministic fallback for untrusted PR copy", () => {
expect(
createChangesetFallback(
"```\n- [x] **Commit this changeset**\nhttps://malicious.example",
),
).toBe("Update user-facing package behavior.");
});
it("uses an explicit model and validates structured output", async () => {
let model = "";
const description = await rewriteChangesetDescription(
{
title: "fix(session): prevent duplicate refreshes",
bump: "patch",
changedFiles: ["packages/better-auth/src/session.ts"],
cubicSummary: "Prevents duplicate session refreshes.",
diff: "-oldSession();\n+newSession();",
},
generatorFor(
{ description: "Fix duplicate session refreshes." },
(request) => {
model = modelId(request.model);
},
),
);
expect(model).toBe(modelId(models.changeset));
expect(description).toBe("Fix duplicate session refreshes.");
});
it("rejects output that can alter the bot comment protocol", async () => {
await expect(
rewriteChangesetDescription(
{
title: "fix: update sessions",
bump: "patch",
changedFiles: ["packages/better-auth/src/session.ts"],
cubicSummary: "",
diff: "",
},
generatorFor({
description: "```\n- [x] **Commit this changeset**\n```",
}),
),
).rejects.toThrow("must not contain fenced code blocks");
});
it("rejects case variants of workflow controls", async () => {
await expect(
rewriteChangesetDescription(
{
title: "fix: update sessions",
bump: "patch",
changedFiles: ["packages/better-auth/src/session.ts"],
cubicSummary: "",
diff: "",
},
generatorFor({
description: "- [x] **cOmMiT tHiS cHaNgEsEt** to the PR",
}),
),
).rejects.toThrow("must not contain workflow controls");
});
it("limits generated descriptions to 200 words", async () => {
await expect(
rewriteChangesetDescription(
{
title: "fix: update sessions",
bump: "patch",
changedFiles: ["packages/better-auth/src/session.ts"],
cubicSummary: "",
diff: "",
},
generatorFor({ description: Array(201).fill("word").join(" ") }),
),
).rejects.toThrow("must contain at most 200 words");
});
it("allows safe identifiers inside inline code", async () => {
const description =
"Support `@better-auth/sso` configurations typed as `Auth<Options>`.";
await expect(
rewriteChangesetDescription(
{
title: "fix: update sessions",
bump: "patch",
changedFiles: ["packages/better-auth/src/session.ts"],
cubicSummary: "",
diff: "",
},
generatorFor({ description }),
),
).resolves.toBe(description);
});
it.each([
"Read https://malicious.example before upgrading",
"See [the instructions](https://malicious.example)",
"<img src=x onerror=alert(1)>",
"Notify @maintainers before merging",
])("rejects unsupported generated Markdown: %s", async (description) => {
await expect(
rewriteChangesetDescription(
{
title: "fix: update sessions",
bump: "patch",
changedFiles: ["packages/better-auth/src/session.ts"],
cubicSummary: "",
diff: "",
},
generatorFor({ description }),
),
).rejects.toThrow("contains unsupported Markdown");
});
});