Skip to content

Commit 045f554

Browse files
betegonclaude
andcommitted
test: add coverage for toNumericId, fetchProjectId, and partial-failure project field
Improve patch coverage for PR #312 by adding tests for new functions and enhancing existing assertions on error output format. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 153d21e commit 045f554

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

test/commands/issue/list.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ describe("issue list: partial failure handling", () => {
297297
expect(output.data.length).toBe(1);
298298
expect(output.errors.length).toBe(1);
299299
expect(output.errors[0].status).toBe(400);
300+
expect(output.errors[0].project).toBe("org-two/myproj");
300301
});
301302

302303
test("stderr warning on partial failures in human output", async () => {

test/lib/resolve-target.test.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import { array, constantFrom, assert as fcAssert, property } from "fast-check";
1111
import { DEFAULT_SENTRY_URL } from "../../src/lib/constants.js";
1212
import { setAuthToken } from "../../src/lib/db/auth.js";
1313
import { setOrgRegion } from "../../src/lib/db/regions.js";
14+
import { AuthError, ResolutionError } from "../../src/lib/errors.js";
1415
import {
16+
fetchProjectId,
1517
isValidDirNameForInference,
1618
resolveAllTargets,
1719
resolveOrg,
1820
resolveOrgAndProject,
1921
resolveOrgsForListing,
22+
toNumericId,
2023
} from "../../src/lib/resolve-target.js";
2124
import { mockFetch, useTestConfigDir } from "../helpers.js";
2225

@@ -104,6 +107,44 @@ describe("isValidDirNameForInference edge cases", () => {
104107
});
105108
});
106109

110+
// ============================================================================
111+
// toNumericId — pure function for ID coercion
112+
// ============================================================================
113+
114+
describe("toNumericId", () => {
115+
test("returns undefined for undefined", () => {
116+
expect(toNumericId(undefined)).toBeUndefined();
117+
});
118+
119+
test("returns undefined for null (cast)", () => {
120+
expect(toNumericId(null as unknown as undefined)).toBeUndefined();
121+
});
122+
123+
test("converts string number to number", () => {
124+
expect(toNumericId("123")).toBe(123);
125+
});
126+
127+
test("returns number as-is", () => {
128+
expect(toNumericId(123)).toBe(123);
129+
});
130+
131+
test("returns undefined for string '0' (falsy)", () => {
132+
expect(toNumericId("0")).toBeUndefined();
133+
});
134+
135+
test("returns undefined for numeric 0 (falsy)", () => {
136+
expect(toNumericId(0)).toBeUndefined();
137+
});
138+
139+
test("returns undefined for empty string", () => {
140+
expect(toNumericId("")).toBeUndefined();
141+
});
142+
143+
test("returns undefined for non-numeric string", () => {
144+
expect(toNumericId("abc")).toBeUndefined();
145+
});
146+
});
147+
107148
// ============================================================================
108149
// Environment Variable Resolution (SENTRY_ORG / SENTRY_PROJECT)
109150
//
@@ -295,3 +336,74 @@ describe("Environment variable resolution (SENTRY_ORG / SENTRY_PROJECT)", () =>
295336
expect(result.orgs).toContain("env-org");
296337
});
297338
});
339+
340+
// ============================================================================
341+
// fetchProjectId — async project ID lookup with error handling
342+
// ============================================================================
343+
344+
describe("fetchProjectId", () => {
345+
useTestConfigDir("test-fetchProjectId-");
346+
347+
let originalFetch: typeof globalThis.fetch;
348+
349+
beforeEach(() => {
350+
originalFetch = globalThis.fetch;
351+
});
352+
353+
afterEach(() => {
354+
globalThis.fetch = originalFetch;
355+
});
356+
357+
test("returns numeric project ID on success", async () => {
358+
await setAuthToken("test-token");
359+
await setOrgRegion("test-org", DEFAULT_SENTRY_URL);
360+
globalThis.fetch = mockFetch(async (input, init) => {
361+
const req = new Request(input, init);
362+
if (req.url.includes("/api/0/projects/test-org/test-project/")) {
363+
return Response.json({ id: "456", slug: "test-project" });
364+
}
365+
return new Response("Not found", { status: 404 });
366+
});
367+
368+
const result = await fetchProjectId("test-org", "test-project");
369+
expect(result).toBe(456);
370+
});
371+
372+
test("throws ResolutionError on 404", async () => {
373+
await setAuthToken("test-token");
374+
await setOrgRegion("test-org", DEFAULT_SENTRY_URL);
375+
globalThis.fetch = mockFetch(
376+
async () =>
377+
new Response(JSON.stringify({ detail: "Not found" }), {
378+
status: 404,
379+
})
380+
);
381+
382+
expect(fetchProjectId("test-org", "test-project")).rejects.toThrow(
383+
ResolutionError
384+
);
385+
});
386+
387+
test("rethrows AuthError when not authenticated", async () => {
388+
// No auth token set — refreshToken() will throw AuthError
389+
await setOrgRegion("test-org", DEFAULT_SENTRY_URL);
390+
391+
expect(fetchProjectId("test-org", "test-project")).rejects.toThrow(
392+
AuthError
393+
);
394+
});
395+
396+
test("returns undefined on transient server error", async () => {
397+
await setAuthToken("test-token");
398+
await setOrgRegion("test-org", DEFAULT_SENTRY_URL);
399+
globalThis.fetch = mockFetch(
400+
async () =>
401+
new Response(JSON.stringify({ detail: "Internal error" }), {
402+
status: 500,
403+
})
404+
);
405+
406+
const result = await fetchProjectId("test-org", "test-project");
407+
expect(result).toBeUndefined();
408+
});
409+
});

0 commit comments

Comments
 (0)