Files
kandev/apps/web/lib/api/domains/repository-sets-api.test.ts
T
2026-09-11 15:18:10 +00:00

137 lines
4.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
createRepositorySet,
deleteRepositorySet,
listRepositorySets,
updateRepositorySet,
} from "./workspace-api";
type RecordedCall = { url: string; init: RequestInit | undefined };
const SET_ITEM_PATH = "/api/v1/repository-sets/set-1";
const SET_ID = "set-1";
const REPO_WEB = "repo-web";
const REPO_GATEWAY = "repo-gateway";
const WORKSPACE_COLLECTION_PATH = "/api/v1/workspaces/ws-1/repository-sets";
let calls: RecordedCall[];
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
beforeEach(() => {
calls = [];
vi.stubGlobal(
"fetch",
vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
calls.push({ url: String(input), init });
if (init?.method === "DELETE") {
return Promise.resolve(new Response(null, { status: 204 }));
}
return Promise.resolve(jsonResponse({ repository_sets: [], total: 0 }));
}),
);
});
afterEach(() => {
vi.unstubAllGlobals();
});
function bodyOf(call: RecordedCall): Record<string, unknown> {
return JSON.parse(String(call.init?.body ?? "{}"));
}
describe("repository set API client", () => {
it("lists a workspace's sets from the workspace-scoped collection route", async () => {
await listRepositorySets("ws-1");
expect(calls).toHaveLength(1);
expect(calls[0].url).toContain(WORKSPACE_COLLECTION_PATH);
});
it("encodes the workspace id into the path", async () => {
await listRepositorySets("ws/1");
expect(calls[0].url).toContain("/api/v1/workspaces/ws%2F1/repository-sets");
});
it("posts name, description, and ordered repository ids", async () => {
await createRepositorySet("ws-1", {
name: "Full-stack",
description: "web + gateway",
repositoryIds: [REPO_WEB, REPO_GATEWAY],
});
expect(calls[0].url).toContain(WORKSPACE_COLLECTION_PATH);
expect(calls[0].init?.method).toBe("POST");
expect(bodyOf(calls[0])).toEqual({
name: "Full-stack",
description: "web + gateway",
repository_ids: [REPO_WEB, REPO_GATEWAY],
});
});
it("posts ordered repository members with their saved bases", async () => {
await createRepositorySet("ws-1", {
name: "Release",
repositories: [
{ repositoryId: REPO_GATEWAY, baseBranch: "develop" },
{ repositoryId: REPO_WEB },
],
});
expect(bodyOf(calls[0])).toEqual({
name: "Release",
description: "",
repositories: [
{ repository_id: REPO_GATEWAY, base_branch: "develop" },
{ repository_id: REPO_WEB, base_branch: "" },
],
});
});
it("patches the flat item route and omits fields the caller left out", async () => {
await updateRepositorySet(SET_ID, { name: "Renamed" });
expect(calls[0].url).toContain(SET_ITEM_PATH);
expect(calls[0].init?.method).toBe("PATCH");
// An absent repository_ids must stay absent: the backend reads a present
// empty list as a rejected request, not as "leave membership alone".
expect(bodyOf(calls[0])).toEqual({ name: "Renamed" });
});
it("sends an explicit repository_ids list when reordering or replacing members", async () => {
await updateRepositorySet(SET_ID, { repositoryIds: [REPO_GATEWAY, REPO_WEB] });
expect(bodyOf(calls[0])).toEqual({ repository_ids: [REPO_GATEWAY, REPO_WEB] });
});
it("sends an empty description when the caller clears it", async () => {
await updateRepositorySet(SET_ID, { description: "" });
expect(bodyOf(calls[0])).toEqual({ description: "" });
});
it("patches ordered repository members with their saved bases", async () => {
await updateRepositorySet(SET_ID, {
repositories: [{ repositoryId: REPO_WEB, baseBranch: "develop" }],
});
expect(bodyOf(calls[0])).toEqual({
repositories: [{ repository_id: REPO_WEB, base_branch: "develop" }],
});
});
it("deletes the flat item route", async () => {
await deleteRepositorySet(SET_ID);
expect(calls[0].url).toContain(SET_ITEM_PATH);
expect(calls[0].init?.method).toBe("DELETE");
});
});