Skip to content

Commit a272740

Browse files
kristof-siketclaude
andcommitted
fix(app): paginate branch lookup and preserve API error detail
Address CodeRabbit review on #91: resolveReadBranch now pages through all branches (was first page only), includes the API error in the thrown message, and the tests cover the API-failure path (and the shared mock returns pagination). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e81e1b0 commit a272740

3 files changed

Lines changed: 44 additions & 9 deletions

File tree

packages/cli/src/lib/app/read-branch.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,31 @@ export async function resolveReadBranch(
1717
client: ManagementApiClient,
1818
options: { projectId: string; branchName: string; signal?: AbortSignal },
1919
): Promise<ReadBranch | null> {
20-
const result = await client.GET("/v1/projects/{projectId}/branches", {
21-
params: { path: { projectId: options.projectId }, query: {} },
22-
signal: options.signal,
23-
});
24-
if (result.error || !result.data) {
25-
throw new Error(`Failed to list branches for project ${options.projectId}`);
26-
}
20+
const branches: Array<{
21+
id: string;
22+
gitName: string;
23+
isDefault: boolean;
24+
role: BranchKind;
25+
}> = [];
26+
let cursor: string | undefined;
27+
28+
do {
29+
const result = await client.GET("/v1/projects/{projectId}/branches", {
30+
params: { path: { projectId: options.projectId }, query: { cursor } },
31+
signal: options.signal,
32+
});
33+
if (result.error || !result.data) {
34+
throw new Error(
35+
`Failed to list branches for project ${options.projectId}: ${JSON.stringify(result.error)}`,
36+
);
37+
}
38+
39+
branches.push(...result.data.data);
40+
cursor = result.data.pagination.hasMore
41+
? (result.data.pagination.nextCursor ?? undefined)
42+
: undefined;
43+
} while (cursor);
2744

28-
const branches = result.data.data;
2945
const chosen =
3046
branches.find((branch) => branch.gitName === options.branchName) ??
3147
branches.find((branch) => branch.isDefault) ??

packages/cli/tests/helpers/mock-factories.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function createProjectClient(
5555
options.branchExists === false
5656
? []
5757
: [branchRecord(branchName)],
58+
pagination: { hasMore: false, nextCursor: null },
5859
},
5960
};
6061
}

packages/cli/tests/read-branch.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ type RawBranch = {
1212

1313
function clientReturning(branches: RawBranch[]): ManagementApiClient {
1414
return {
15-
GET: vi.fn().mockResolvedValue({ data: { data: branches } }),
15+
GET: vi.fn().mockResolvedValue({
16+
data: {
17+
data: branches,
18+
pagination: { hasMore: false, nextCursor: null },
19+
},
20+
}),
1621
} as unknown as ManagementApiClient;
1722
}
1823

@@ -68,4 +73,17 @@ describe("resolveReadBranch", () => {
6873

6974
expect(result).toBeNull();
7075
});
76+
77+
it("throws when the branches request fails", async () => {
78+
const client = {
79+
GET: vi.fn().mockResolvedValue({
80+
error: { message: "Unauthorized" },
81+
response: { status: 401 },
82+
}),
83+
} as unknown as ManagementApiClient;
84+
85+
await expect(
86+
resolveReadBranch(client, { projectId: "proj_1", branchName: "main" }),
87+
).rejects.toThrow();
88+
});
7189
});

0 commit comments

Comments
 (0)