Skip to content

Commit ffbd6b0

Browse files
🚸 Improve error handling so toasts are more descriptive (#145)
1 parent 5a6cac8 commit ffbd6b0

File tree

2 files changed

+87
-6
lines changed

2 files changed

+87
-6
lines changed

‎src/cloud/api.ts‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,19 @@ export class ApiService {
6262
})
6363

6464
if (!response.ok) {
65+
let detail = ""
66+
try {
67+
const body = await response.json()
68+
detail = typeof body.detail === "string" ? body.detail : ""
69+
} catch {}
70+
6571
throw new Error(
66-
`API request failed: ${options.method || "GET"} ${endpoint} returned ${response.status}`,
72+
detail ||
73+
`API request failed: ${options.method || "GET"} ${endpoint} returned ${response.status}`,
6774
)
6875
}
6976

70-
return response.json() as Promise<T>
77+
return (await response.json()) as T
7178
}
7279

7380
static async getUser(token: string): Promise<User | null> {
@@ -174,8 +181,15 @@ export class ApiService {
174181
)
175182

176183
if (!response.ok || !response.body) {
184+
let detail = ""
185+
try {
186+
const body = await response.json()
187+
detail = typeof body.detail === "string" ? body.detail : ""
188+
} catch {}
189+
177190
throw new Error(
178-
`Failed to stream logs: ${response.status} ${response.statusText}`,
191+
detail ||
192+
`Failed to stream logs: ${response.status} ${response.statusText}`,
179193
)
180194
}
181195

‎src/test/cloud/api.test.ts‎

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,49 @@ suite("cloud/api", () => {
242242
await assert.rejects(() => api.getTeams(), /Not authenticated/)
243243
})
244244

245-
test("throws on non-ok response", async () => {
246-
sinon.stub(globalThis, "fetch").resolves(mockResponse({}, false, 403))
245+
test("throws with detail message from API error response", async () => {
246+
sinon
247+
.stub(globalThis, "fetch")
248+
.resolves(
249+
mockResponse(
250+
{
251+
detail:
252+
"App limit reached (3). Upgrade your plan to create more apps.",
253+
},
254+
false,
255+
403,
256+
),
257+
)
258+
259+
await assert.rejects(
260+
() => api.createApp("team-id", "New App"),
261+
/App limit reached \(3\). Upgrade your plan to create more apps\./,
262+
)
263+
})
264+
265+
test("falls back to generic message when no detail in error response", async () => {
266+
sinon.stub(globalThis, "fetch").resolves(mockResponse({}, false, 500))
247267

248-
await assert.rejects(() => api.getTeams(), /API request failed/)
268+
await assert.rejects(
269+
() => api.createApp("team-id", "New App"),
270+
/API request failed: POST \/apps\/ returned 500/,
271+
)
272+
})
273+
274+
test("falls back to generic message when response body is not JSON", async () => {
275+
sinon.stub(globalThis, "fetch").resolves({
276+
ok: false,
277+
status: 502,
278+
statusText: "Bad Gateway",
279+
json: async () => {
280+
throw new SyntaxError("Unexpected token")
281+
},
282+
} as unknown as Response)
283+
284+
await assert.rejects(
285+
() => api.createApp("team-id", "New App"),
286+
/API request failed: POST \/apps\/ returned 502/,
287+
)
249288
})
250289

251290
test("getApps returns app data", async () => {
@@ -272,6 +311,34 @@ suite("cloud/api", () => {
272311
assert.strictEqual(options?.method, "POST")
273312
})
274313

314+
test("streamAppLogs throws with detail message from API error response", async () => {
315+
sinon
316+
.stub(globalThis, "fetch")
317+
.resolves(mockResponse({ detail: "App not found" }, false, 404))
318+
319+
const stream = api.streamAppLogs({
320+
appId: "app-id",
321+
tail: 100,
322+
since: "2024-01-01T00:00:00Z",
323+
follow: false,
324+
})
325+
326+
await assert.rejects(() => stream.next(), /App not found/)
327+
})
328+
329+
test("streamAppLogs falls back to generic message when no detail", async () => {
330+
sinon.stub(globalThis, "fetch").resolves(mockResponse({}, false, 502))
331+
332+
const stream = api.streamAppLogs({
333+
appId: "app-id",
334+
tail: 100,
335+
since: "2024-01-01T00:00:00Z",
336+
follow: false,
337+
})
338+
339+
await assert.rejects(() => stream.next(), /Failed to stream logs: 502/)
340+
})
341+
275342
test("request includes auth header and user-agent", async () => {
276343
mockSession("my_token")
277344

0 commit comments

Comments
 (0)