Upload files to "tests/mutator/branch"

This commit is contained in:
2026-07-03 02:52:50 +00:00
parent 59699625eb
commit 0d5e3a2255
3 changed files with 283 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import { describe, expect, test } from "bun:test";
import { BranchService } from "../../../src/mutator/branch/branches";
import type { GraphQLClient } from "../../../src/mutator/branch/client";
const fakeClient = { execute: async () => ({}) } as unknown as GraphQLClient;
describe("BranchService.filterBranches", () => {
const service = new BranchService(fakeClient, "owner", "repo");
test("returns all branches when none match exclude patterns", () => {
const branches = [
{ name: "main", headOid: "abc" },
{ name: "develop", headOid: "def" },
{ name: "feature/test", headOid: "ghi" },
];
expect(service.filterBranches(branches)).toEqual(branches);
});
test("filters out dependabot branches", () => {
const branches = [
{ name: "main", headOid: "abc" },
{ name: "dependabot/npm/express-4.18.0", headOid: "def" },
{ name: "dependabot/pip/requests", headOid: "ghi" },
];
expect(service.filterBranches(branches)).toEqual([{ name: "main", headOid: "abc" }]);
});
test("filters out copilot branches", () => {
const branches = [
{ name: "main", headOid: "abc" },
{ name: "copilot/github-copilot-auto", headOid: "def" },
{ name: "copilot/initialize-github-copilot", headOid: "ghi" },
];
expect(service.filterBranches(branches)).toEqual([{ name: "main", headOid: "abc" }]);
});
test("does not filter plain string containing 'dependabot' outside prefix", () => {
const branches = [{ name: "my-dependabot-wrapper", headOid: "abc" }];
expect(service.filterBranches(branches)).toEqual(branches);
});
test("filters using extra exclude prefixes", () => {
const branches = [
{ name: "main", headOid: "abc" },
{ name: "release/v1.0", headOid: "def" },
];
expect(service.filterBranches(branches, ["release/"])).toEqual([{ name: "main", headOid: "abc" }]);
});
test("supports combined default and extra patterns", () => {
const branches = [
{ name: "main", headOid: "abc" },
{ name: "dependabot/npm/express", headOid: "def" },
{ name: "release/v1.0", headOid: "ghi" },
];
expect(service.filterBranches(branches, ["release/"])).toEqual([{ name: "main", headOid: "abc" }]);
});
test("empty branches returns empty array", () => {
expect(service.filterBranches([])).toEqual([]);
});
});
+116
View File
@@ -0,0 +1,116 @@
import { afterAll, describe, expect, mock, test } from "bun:test";
import type { GraphQLResponse } from "../../../src/mutator/branch/client";
import { GraphQLClient } from "../../../src/mutator/branch/client";
const originalFetch = globalThis.fetch;
function mockFetch(response: Partial<Response> & { jsonData?: unknown }) {
globalThis.fetch = mock(() =>
Promise.resolve({
ok: response.ok ?? true,
status: response.status ?? 200,
statusText: response.statusText ?? "OK",
json: mock(() => Promise.resolve(response.jsonData ?? { data: {} })),
text: mock(() =>
Promise.resolve(JSON.stringify(response.jsonData ?? { data: {} })),
),
}),
) as any;
}
afterAll(() => {
globalThis.fetch = originalFetch;
});
describe("GraphQLClient", () => {
test("throws when constructed without token", () => {
expect(() => new GraphQLClient("")).toThrow(
"A GitHub token is required to construct a GraphQLClient.",
);
});
test("throws on non-2xx response", async () => {
mockFetch({ ok: false, status: 401, statusText: "Unauthorized" });
const client = new GraphQLClient("fake-token");
await expect(client.execute("query { viewer { login } }")).rejects.toThrow(
"GitHub GraphQL request failed (status=401)",
);
});
test("throws on GraphQL errors in execute()", async () => {
mockFetch({
ok: true,
jsonData: { errors: [{ message: "Something went wrong" }] },
});
const client = new GraphQLClient("fake-token");
await expect(client.execute("query { x }")).rejects.toThrow(
"GraphQL errors: Something went wrong",
);
});
test("throws when no data returned in execute()", async () => {
mockFetch({
ok: true,
jsonData: { errors: [] },
});
const client = new GraphQLClient("fake-token");
await expect(client.execute("query { x }")).rejects.toThrow(
"No data returned from GitHub API",
);
});
test("returns data on successful query", async () => {
mockFetch({
ok: true,
jsonData: { data: { viewer: { login: "testuser" } } },
});
const client = new GraphQLClient("fake-token");
const result = await client.execute("query { viewer { login } }");
expect(result).toEqual({ viewer: { login: "testuser" } });
});
test("executeWithPartial returns data + errors without throwing", async () => {
mockFetch({
ok: true,
jsonData: {
data: { b0: { commit: { oid: "abc" } }, b1: null },
errors: [{ message: "Branch protected", path: ["b1"] }],
},
});
const client = new GraphQLClient("fake-token");
const result = await client.executeWithPartial("mutation { ... }");
expect(result.data).toBeDefined();
expect(result.errors).toHaveLength(1);
expect(result.errors![0].message).toBe("Branch protected");
});
test("executeWithPartial throws on non-2xx", async () => {
mockFetch({ ok: false, status: 500, statusText: "Internal Server Error" });
const client = new GraphQLClient("fake-token");
await expect(client.executeWithPartial("query { x }")).rejects.toThrow(
"GitHub GraphQL request failed (status=500)",
);
});
test("sends correct headers and body", async () => {
mockFetch({ ok: true, jsonData: { data: {} } });
const client = new GraphQLClient("ghp_token123");
await client.execute("query Test($x: String!) { node(id: $x) }", {
x: "abc",
});
const fetchMock = globalThis.fetch as any;
const call = fetchMock.mock.calls[0];
const [url, options] = call;
expect(url).toBe("https://api.github.com/graphql");
expect(options.method).toBe("POST");
expect(options.headers.Authorization).toBe("Bearer ghp_token123");
expect(options.headers["Content-Type"]).toBe("application/json");
const body = JSON.parse(options.body);
expect(body.query).toBe("query Test($x: String!) { node(id: $x) }");
expect(body.variables).toEqual({ x: "abc" });
});
});
+104
View File
@@ -0,0 +1,104 @@
import { describe, expect, test } from "bun:test";
import { CommitService } from "../../../src/mutator/branch/commits";
function fakeClient(result: unknown) {
return { executeWithPartial: () => Promise.resolve(result) } as any;
}
function failingClient(error: unknown) {
return { executeWithPartial: () => Promise.reject(error) } as any;
}
describe("CommitService", () => {
describe("pushBatched", () => {
test("returns empty array for empty commits", async () => {
const svc = new CommitService(fakeClient({}), "owner", "repo");
const result = await svc.pushBatched([]);
expect(result).toEqual([]);
});
test("returns failures for commits with no files", async () => {
const svc = new CommitService(fakeClient({}), "owner", "repo");
const result = await svc.pushBatched([
{ branchName: "main", expectedHeadOid: "abc", files: [], commitHeadline: "test" },
]);
expect(result[0].success).toBe(false);
expect(result[0].error).toBe("No file changes provided.");
});
test("returns success for batched commits", async () => {
const client = fakeClient({
data: {
b0: { commit: { oid: "abc123", url: "url" } },
b1: { commit: { oid: "def456", url: "url" } },
},
});
const svc = new CommitService(client, "owner", "repo");
const result = await svc.pushBatched([
{ branchName: "main", expectedHeadOid: "eee", files: [{ path: "README.md", content: "hello" }], commitHeadline: "update main" },
{ branchName: "develop", expectedHeadOid: "fff", files: [{ path: "README.md", content: "world" }], commitHeadline: "update develop" },
]);
expect(result).toHaveLength(2);
expect(result[0]).toEqual({ branch: "main", success: true, commitOid: "abc123" });
expect(result[1]).toEqual({ branch: "develop", success: true, commitOid: "def456" });
});
test("handles partial failures in batched results", async () => {
const client = fakeClient({
data: { b0: { commit: { oid: "abc123", url: "url" } }, b1: null },
errors: [{ message: "Branch is protected", path: ["b1"] }],
});
const svc = new CommitService(client, "owner", "repo");
const result = await svc.pushBatched([
{ branchName: "branch-a", expectedHeadOid: "a", files: [{ path: "x.txt", content: "ok" }], commitHeadline: "ok" },
{ branchName: "branch-b", expectedHeadOid: "b", files: [{ path: "y.txt", content: "fail" }], commitHeadline: "fail" },
]);
expect(result[0].success).toBe(true);
expect(result[1].success).toBe(false);
expect(result[1].error).toBe("Branch is protected");
});
test("handles transport failure — all commits fail", async () => {
const svc = new CommitService(failingClient(new Error("Network down")), "owner", "repo");
const result = await svc.pushBatched([
{ branchName: "main", expectedHeadOid: "eee", files: [{ path: "README.md", content: "hello" }], commitHeadline: "test" },
]);
expect(result[0].success).toBe(false);
expect(result[0].error).toBe("Network down");
});
});
describe("pushChunked", () => {
test("throws for invalid chunkSize", async () => {
const svc = new CommitService(fakeClient({}), "owner", "repo");
await expect(svc.pushChunked([], 0)).rejects.toThrow("pushChunked requires chunkSize >= 1");
});
test("splits commits into chunks", async () => {
const client = fakeClient({ data: { b0: { commit: { oid: "ok", url: "url" } } } });
const svc = new CommitService(client, "owner", "repo");
const commits = Array.from({ length: 5 }, (_, i) => ({
branchName: `branch-${i}`,
expectedHeadOid: "abc",
files: [{ path: "README.md", content: "content" }],
commitHeadline: `commit ${i}`,
}));
expect(await svc.pushChunked(commits, 2)).toHaveLength(5);
});
test("calls onChunk callback for each chunk", async () => {
const client = fakeClient({ data: { b0: { commit: { oid: "ok", url: "url" } } } });
const svc = new CommitService(client, "owner", "repo");
const chunks: any[] = [];
const commits = Array.from({ length: 3 }, (_, i) => ({
branchName: `branch-${i}`,
expectedHeadOid: "abc",
files: [{ path: "README.md", content: "content" }],
commitHeadline: `commit ${i}`,
}));
await svc.pushChunked(commits, 1, (r: any) => chunks.push(r));
expect(chunks).toHaveLength(3);
});
});
});