Files
cloudflare--workers-sdk/tools/dependabot/__tests__/generate-dependabot-pr-changesets.test.ts
wehub-resource-sync 70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:11 +08:00

400 lines
10 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";
import dedent from "ts-dedent";
import { describe, it, vitest } from "vitest";
import {
commitAndPush,
generateChangesetHeader,
generateCommitMessage,
getDependencyChanges,
getPackageJsonDiff,
parseDiffForChanges,
writeChangeSet,
} from "../generate-dependabot-pr-changesets";
import type { Mock } from "vitest";
vitest.mock("node:child_process", async () => {
return {
spawnSync: vitest.fn(),
};
});
vitest.mock("node:fs", async () => {
return {
writeFileSync: vitest.fn(),
};
});
describe("getPackageJsonDiff()", () => {
it("should call spawnSync to the appropriate git diff command", ({
expect,
}) => {
(spawnSync as Mock).mockReturnValue({ output: [] });
getPackageJsonDiff("/path/to/package.json");
expect(spawnSync).toHaveBeenCalledOnce();
expect((spawnSync as Mock).mock.lastCall).toMatchInlineSnapshot(`
[
"git",
[
"diff",
"HEAD~1",
"/path/to/package.json",
],
{
"encoding": "utf-8",
},
]
`);
});
it("should split the output into lines", ({ expect }) => {
(spawnSync as Mock).mockReturnValue({
output: ["line 1", "line 2\nline 3", "line 4"],
});
const diffLines = getPackageJsonDiff("/path/to/package.json");
expect(diffLines).toMatchInlineSnapshot(`
[
"line 1",
"line 2",
"line 3",
"line 4",
]
`);
});
});
describe("parseDiffForChanges()", () => {
it("should return a map containing the changes for each package", ({
expect,
}) => {
const changes = parseDiffForChanges([
`- "package": "^0.0.1"`,
`+ "package": "^0.0.2",`,
`- "@namespace/package": "1.3.4"`,
`+ "@namespace/package": "1.4.5",`,
]);
expect(changes).toEqual(
new Map([
["package", { from: "^0.0.1", to: "^0.0.2" }],
["@namespace/package", { from: "1.3.4", to: "1.4.5" }],
])
);
});
it("should ignore lines that do not match a change", ({ expect }) => {
const changes = parseDiffForChanges(["", undefined, "random text"]);
expect(changes.size).toBe(0);
});
it("should capture both quoted and unquoted pnpm catalog entries", ({
expect,
}) => {
const changes = parseDiffForChanges([
// A literal dependency in a package.json (quoted, JSON key)
`- "workerd": "1.20260702.1",`,
`+ "workerd": "1.20260706.1",`,
// A quoted catalog entry in pnpm-workspace.yaml (caret preserved)
`- "@cloudflare/workers-types": "^4.20260702.1"`,
`+ "@cloudflare/workers-types": "^5.20260706.1"`,
// workerd also appears in pnpm-workspace.yaml, but as an *unquoted*
// YAML key. The catalog is the source of truth and always carries the
// same version as the package.json pin, so capturing it here merges
// into the same map entry rather than conflicting.
`- workerd: "1.20260702.1"`,
`+ workerd: "1.20260706.1"`,
]);
expect(changes).toEqual(
new Map([
[
"workerd",
{
from: "1.20260702.1",
to: "1.20260706.1",
},
],
[
"@cloudflare/workers-types",
{
from: "^4.20260702.1",
to: "^5.20260706.1",
},
],
])
);
});
it("should capture an unquoted catalog key even when no package.json bumps it", ({
expect,
}) => {
// When Dependabot bumps workerd only in the pnpm-workspace.yaml catalog
// (an unquoted YAML key), leaving the package.json pins untouched, workerd
// must still make it into the changeset.
const changes = parseDiffForChanges([
`- "@cloudflare/workers-types": "^5.20260708.1"`,
`+ "@cloudflare/workers-types": "^5.20260710.1"`,
`- workerd: "1.20260708.1"`,
`+ workerd: "1.20260710.1"`,
]);
expect(changes).toEqual(
new Map([
[
"@cloudflare/workers-types",
{
from: "^5.20260708.1",
to: "^5.20260710.1",
},
],
[
"workerd",
{
from: "1.20260708.1",
to: "1.20260710.1",
},
],
])
);
});
});
describe("getDependencyChanges()", () => {
it("should diff every provided path and merge the changes", ({ expect }) => {
(spawnSync as Mock).mockClear();
(spawnSync as Mock).mockImplementation((_command, args) => {
const path: string = args[2];
if (path.endsWith("package.json")) {
return {
output: [
`- "workerd": "1.20260702.1",`,
`+ "workerd": "1.20260706.1",`,
],
};
}
if (path.endsWith("pnpm-workspace.yaml")) {
return {
output: [
`- "@cloudflare/workers-types": "^4.20260702.1"`,
`+ "@cloudflare/workers-types": "^5.20260706.1"`,
],
};
}
return {
output: [],
};
});
const changes = getDependencyChanges([
"packages/miniflare/package.json",
"pnpm-workspace.yaml",
]);
expect(spawnSync).toHaveBeenCalledTimes(2);
expect(changes).toEqual(
new Map([
[
"workerd",
{
from: "1.20260702.1",
to: "1.20260706.1",
},
],
[
"@cloudflare/workers-types",
{
from: "^4.20260702.1",
to: "^5.20260706.1",
},
],
])
);
});
});
describe("generateChangesetHeader()", () => {
it("should return a header with a single package and 'patch' version bump", ({
expect,
}) => {
const header = generateChangesetHeader(["package-name"]);
expect(header).toMatchInlineSnapshot(`
"---
"package-name": patch
---"
`);
});
it("should return a header with multiple packages and 'patch' version bump", ({
expect,
}) => {
const header = generateChangesetHeader(["package-name", "another-package"]);
expect(header).toMatchInlineSnapshot(`
"---
"package-name": patch
"another-package": patch
---"
`);
});
});
describe("generateCommitMessage()", () => {
it("should return a commit message about a single changed package", ({
expect,
}) => {
const message = generateCommitMessage(["@namespace/package"], new Map());
expect(message).toMatchInlineSnapshot(`
"Update dependencies of "@namespace/package"
The following dependency versions have been updated:
| Dependency | From | To |
| ---------- | ---- | -- |"
`);
});
it("should return a commit message about multiple packages", ({ expect }) => {
const message = generateCommitMessage(
["@namespace/package", "another-package"],
new Map()
);
expect(message).toMatchInlineSnapshot(`
"Update dependencies of "@namespace/package", "another-package"
The following dependency versions have been updated:
| Dependency | From | To |
| ---------- | ---- | -- |"
`);
});
it("should return a commit message containing a row for each change", ({
expect,
}) => {
const message = generateCommitMessage(
["package-name"],
new Map([
["some-package", { from: "^0.0.1", to: "^0.0.2" }],
["@namespace/some-package", { from: "1.3.4", to: "1.4.5" }],
])
);
expect(message).toMatchInlineSnapshot(`
"Update dependencies of "package-name"
The following dependency versions have been updated:
| Dependency | From | To |
| ----------------------- | ------ | ------ |
| some-package | ^0.0.1 | ^0.0.2 |
| @namespace/some-package | 1.3.4 | 1.4.5 |"
`);
});
});
describe("writeChangeSet()", () => {
it("should call writeFileSync with appropriate changeset path and body", ({
expect,
}) => {
const header = dedent`
---
"package-name": patch
---`;
const body = dedent`
chore: update dependencies of "@namespace/package" package
The following dependency versions have been updated:
| Dependency | From | To |
| ----------------------- | ------ | ------ |
| some-package | ^0.0.1 | ^0.0.2 |
| @namespace/some-package | 1.3.4 | 1.4.5 |
`;
writeChangeSet("some-prefix", "1234", header, body);
expect(writeFileSync).toHaveBeenCalledOnce();
expect((writeFileSync as Mock).mock.lastCall?.[0]).toMatchInlineSnapshot(
`".changeset/some-prefix-1234.md"`
);
expect((writeFileSync as Mock).mock.lastCall?.[1]).toMatchInlineSnapshot(`
"---
"package-name": patch
---
chore: update dependencies of "@namespace/package" package
The following dependency versions have been updated:
| Dependency | From | To |
| ----------------------- | ------ | ------ |
| some-package | ^0.0.1 | ^0.0.2 |
| @namespace/some-package | 1.3.4 | 1.4.5 |
"
`);
});
});
describe("commitAndPush()", () => {
it("should call spawnSync with appropriate git commands", ({ expect }) => {
(spawnSync as Mock).mockClear();
(spawnSync as Mock).mockReturnValue({ output: [] });
const commitMessage = dedent`
chore: update dependencies of "@namespace/package" package
The following dependency versions have been updated:
| Dependency | From | To |
| ----------------------- | ------ | ------ |
| some-package | ^0.0.1 | ^0.0.2 |
| @namespace/some-package | 1.3.4 | 1.4.5 |`;
commitAndPush(commitMessage);
expect(spawnSync).toHaveBeenCalledTimes(3);
expect((spawnSync as Mock).mock.calls[0]).toMatchInlineSnapshot(`
[
"git",
[
"add",
".changeset",
],
{
"encoding": "utf-8",
},
]
`);
expect((spawnSync as Mock).mock.calls[1]).toMatchInlineSnapshot(`
[
"git",
[
"commit",
"-m",
"chore: update dependencies of "@namespace/package" package
The following dependency versions have been updated:
| Dependency | From | To |
| ----------------------- | ------ | ------ |
| some-package | ^0.0.1 | ^0.0.2 |
| @namespace/some-package | 1.3.4 | 1.4.5 |",
],
{
"encoding": "utf-8",
},
]
`);
expect((spawnSync as Mock).mock.calls[2]).toMatchInlineSnapshot(`
[
"git",
[
"push",
],
{
"encoding": "utf-8",
},
]
`);
});
it("should call error if any git command fails", ({ expect }) => {
(spawnSync as Mock).mockImplementation((git, args) => {
const error =
args[0] === "push" ? new Error("Failed to push") : undefined;
return { output: [], error };
});
expect(() =>
commitAndPush("commit message")
).toThrowErrorMatchingInlineSnapshot(`[Error: Failed to push]`);
});
});