Upload files to "tests/mutator/branch"
This commit is contained in:
@@ -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([]);
|
||||
});
|
||||
});
|
||||
@@ -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" });
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user