Skip to content

Commit 1bf73ee

Browse files
fix(bff): improve error handling (images bff) (#381)
* fix(bff): improve error handling (images bff) * test(bff): update unit tests * test(bff): update unit test descriptions
1 parent fc132d4 commit 1bf73ee

4 files changed

Lines changed: 122 additions & 150 deletions

File tree

apps/aurora-portal/src/server/Compute/helpers/imageHelpers.test.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
buildNextPageUrl,
88
getLastImageMarker,
99
validateGlanceService,
10-
mapResponseToTRPCError,
10+
mapErrorResponseToTRPCError,
1111
ImageErrorHandlers,
1212
handleZodParsingError,
1313
wrapError,
@@ -309,106 +309,106 @@ describe("imageHelpers", () => {
309309
})
310310
})
311311

312-
describe("mapResponseToTRPCError", () => {
313-
it("should map 400 status to BAD_REQUEST", () => {
314-
const response = { status: 400, statusText: "Bad Request" }
312+
describe("mapErrorResponseToTRPCError", () => {
313+
it("should map 400 status BAD_REQUEST", () => {
314+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 400, message: "Bad Request" }
315315
const context = { operation: "create image", imageId: "image-123" }
316316

317-
const error = mapResponseToTRPCError(response, context)
317+
const error = mapErrorResponseToTRPCError(errorResponse, context)
318318

319319
expect(error.code).toBe("BAD_REQUEST")
320320
expect(error.message).toBe("Failed to create image image: image-123")
321321
})
322322

323-
it("should map 403 status to FORBIDDEN", () => {
324-
const response = { status: 403, statusText: "Forbidden" }
323+
it("should map 403 status FORBIDDEN", () => {
324+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 403, message: "Forbidden" }
325325
const context = { operation: "delete image", imageId: "image-123" }
326326

327-
const error = mapResponseToTRPCError(response, context)
327+
const error = mapErrorResponseToTRPCError(errorResponse, context)
328328

329329
expect(error.code).toBe("FORBIDDEN")
330330
expect(error.message).toBe("Access forbidden - cannot delete image image: image-123")
331331
})
332332

333-
it("should map 404 status to NOT_FOUND", () => {
334-
const response = { status: 404, statusText: "Not Found" }
333+
it("should map 404 status NOT_FOUND", () => {
334+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 404, message: "Not Found" }
335335
const context = { operation: "get image", imageId: "image-123" }
336336

337-
const error = mapResponseToTRPCError(response, context)
337+
const error = mapErrorResponseToTRPCError(errorResponse, context)
338338

339339
expect(error.code).toBe("NOT_FOUND")
340340
expect(error.message).toBe("Image not found image: image-123")
341341
})
342342

343343
it("should map 404 status with member info", () => {
344-
const response = { status: 404, statusText: "Not Found" }
344+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 404, message: "Not Found" }
345345
const context = { operation: "get member", imageId: "image-123", memberId: "member-456" }
346346

347-
const error = mapResponseToTRPCError(response, context)
347+
const error = mapErrorResponseToTRPCError(errorResponse, context)
348348

349349
expect(error.code).toBe("NOT_FOUND")
350350
expect(error.message).toBe("Image or member not found image: image-123, member: member-456")
351351
})
352352

353-
it("should map 409 status to CONFLICT", () => {
354-
const response = { status: 409, statusText: "Conflict" }
353+
it("should map 409 status CONFLICT to TRPC Error", () => {
354+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 409, message: "Conflict" }
355355
const context = { operation: "update image", imageId: "image-123" }
356356

357-
const error = mapResponseToTRPCError(response, context)
357+
const error = mapErrorResponseToTRPCError(errorResponse, context)
358358

359359
expect(error.code).toBe("CONFLICT")
360360
expect(error.message).toBe("Conflict - update image image: image-123")
361361
})
362362

363-
it("should map 413 status to PAYLOAD_TOO_LARGE", () => {
364-
const response = { status: 413, statusText: "Payload Too Large" }
363+
it("should map 413 status PAYLOAD_TOO_LARGE", () => {
364+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 413, message: "Payload Too Large" }
365365
const context = { operation: "upload image", imageId: "image-123" }
366366

367-
const error = mapResponseToTRPCError(response, context)
367+
const error = mapErrorResponseToTRPCError(errorResponse, context)
368368

369369
expect(error.code).toBe("PAYLOAD_TOO_LARGE")
370370
expect(error.message).toBe("Request entity too large image: image-123")
371371
})
372372

373-
it("should map 415 status to BAD_REQUEST", () => {
374-
const response = { status: 415, statusText: "Unsupported Media Type" }
373+
it("should map 415 status BAD_REQUEST", () => {
374+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 415, message: "Unsupported Media Type" }
375375
const context = { operation: "upload image", imageId: "image-123" }
376376

377-
const error = mapResponseToTRPCError(response, context)
377+
const error = mapErrorResponseToTRPCError(errorResponse, context)
378378

379379
expect(error.code).toBe("BAD_REQUEST")
380380
expect(error.message).toBe("Unsupported media type image: image-123")
381381
})
382382

383-
it("should map unknown status to INTERNAL_SERVER_ERROR", () => {
384-
const response = { status: 500, statusText: "Internal Server Error" }
383+
it("should map unknown status INTERNAL_SERVER_ERROR", () => {
384+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 500, message: "Internal Server Error" }
385385
const context = { operation: "create image" }
386386

387-
const error = mapResponseToTRPCError(response, context)
387+
const error = mapErrorResponseToTRPCError(errorResponse, context)
388388

389389
expect(error.code).toBe("INTERNAL_SERVER_ERROR")
390390
expect(error.message).toBe("Failed to create image: Internal Server Error")
391391
})
392392

393-
it("should handle missing statusText", () => {
394-
const response = { status: 500 }
393+
it("should handle missing status text", () => {
394+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 500, message: "" }
395395
const context = { operation: "create image" }
396396

397-
const error = mapResponseToTRPCError(response, context)
397+
const error = mapErrorResponseToTRPCError(errorResponse, context)
398398

399399
expect(error.code).toBe("INTERNAL_SERVER_ERROR")
400400
expect(error.message).toBe("Failed to create image: Unknown error")
401401
})
402402

403403
it("should include additional info when provided", () => {
404-
const response = { status: 400, statusText: "Bad Request" }
404+
const errorResponse = { name: "SignalOpenstackApiError", statusCode: 400, message: "Bad Request" }
405405
const context = {
406406
operation: "create image",
407407
imageId: "image-123",
408408
additionalInfo: "Invalid disk format",
409409
}
410410

411-
const error = mapResponseToTRPCError(response, context)
411+
const error = mapErrorResponseToTRPCError(errorResponse, context)
412412

413413
expect(error.message).toBe("Failed to create image image: image-123 - Invalid disk format")
414414
})

apps/aurora-portal/src/server/Compute/helpers/imageHelpers.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { TRPCError } from "@trpc/server"
22
import { ZodError } from "zod"
33
import { ListImagesInput } from "../types/image"
4+
import { SignalOpenstackApiError } from "@cobaltcore-dev/signal-openstack"
45

56
/**
67
* Applies sorting and filtering parameters to URLSearchParams for OpenStack Glance API calls
@@ -176,13 +177,13 @@ export function validateGlanceService(glance: unknown): asserts glance is NonNul
176177
}
177178

178179
/**
179-
* Maps OpenStack API HTTP response status codes to appropriate TRPCError instances
180-
* @param response - The HTTP response from OpenStack API
180+
* Maps Signal OpenStack API Error to appropriate TRPCError instances
181+
* @param error - The Signal OpenStack API Error
181182
* @param context - Additional context for error messages (e.g., imageId, operation type)
182183
* @returns TRPCError instance with appropriate code and message
183184
*/
184-
export function mapResponseToTRPCError(
185-
response: { status?: number; statusText?: string },
185+
export function mapErrorResponseToTRPCError(
186+
error: SignalOpenstackApiError,
186187
context: {
187188
operation: string
188189
imageId?: string
@@ -196,7 +197,7 @@ export function mapResponseToTRPCError(
196197
const memberInfo = memberId ? `, member: ${memberId}` : ""
197198
const extraInfo = additionalInfo ? ` - ${additionalInfo}` : ""
198199

199-
switch (response.status) {
200+
switch (error.statusCode) {
200201
case 400:
201202
return new TRPCError({
202203
code: "BAD_REQUEST",
@@ -236,7 +237,7 @@ export function mapResponseToTRPCError(
236237
default:
237238
return new TRPCError({
238239
code: "INTERNAL_SERVER_ERROR",
239-
message: `${baseMessage}: ${response.statusText || "Unknown error"}`,
240+
message: `${baseMessage}: ${error.message || "Unknown error"}`,
240241
})
241242
}
242243
}

apps/aurora-portal/src/server/Compute/routers/imageRouter.test.ts

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,29 @@ import { GlanceImage, ImageMember } from "../types/image"
77
import { createCallerFactory, auroraRouter } from "../../trpc"
88

99
// Mock the helpers
10-
vi.mock("../helpers/imageHelpers", () => ({
11-
applyImageQueryParams: vi.fn(),
12-
validateGlanceService: vi.fn(),
13-
mapResponseToTRPCError: vi.fn(),
14-
ImageErrorHandlers: {
15-
upload: vi.fn(),
16-
visibility: vi.fn(),
17-
delete: vi.fn(),
18-
member: {
19-
list: vi.fn(),
20-
get: vi.fn(),
21-
create: vi.fn(),
22-
update: vi.fn(),
10+
vi.mock("../helpers/imageHelpers", async (importOriginal) => {
11+
const actual: object = await importOriginal()
12+
13+
return {
14+
...actual,
15+
applyImageQueryParams: vi.fn(),
16+
validateGlanceService: vi.fn(),
17+
ImageErrorHandlers: {
18+
upload: vi.fn(),
19+
visibility: vi.fn(),
2320
delete: vi.fn(),
21+
member: {
22+
list: vi.fn(),
23+
get: vi.fn(),
24+
create: vi.fn(),
25+
update: vi.fn(),
26+
delete: vi.fn(),
27+
},
2428
},
25-
},
26-
handleZodParsingError: vi.fn(),
27-
withErrorHandling: vi.fn((fn) => fn()),
28-
}))
29+
handleZodParsingError: vi.fn(),
30+
withErrorHandling: vi.fn((fn) => fn()),
31+
}
32+
})
2933

3034
// Mock data
3135
const mockGlanceImage: GlanceImage = {
@@ -134,18 +138,11 @@ describe("imageRouter", () => {
134138
const caller = createCaller(mockCtx)
135139

136140
// Mock failed response
137-
mockCtx.mockGlance.get.mockResolvedValue({
138-
ok: false,
139-
status: 500,
140-
json: vi.fn(),
141-
})
142-
143-
const mockError = new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "API Error" })
144-
;(imageHelpers.mapResponseToTRPCError as Mock).mockReturnValue(mockError)
141+
mockCtx.mockGlance.get.mockRejectedValue({ statusCode: 500, message: "Internal Server Error" })
145142

146143
const input = {}
147144

148-
await expect(caller.image.listImages(input)).rejects.toThrow("API Error")
145+
await expect(caller.image.listImages(input)).rejects.toThrow("Failed to list images: Internal Server Error")
149146
})
150147
})
151148

@@ -170,18 +167,11 @@ describe("imageRouter", () => {
170167
const mockCtx = createMockContext()
171168
const caller = createCaller(mockCtx)
172169

173-
mockCtx.mockGlance.get.mockResolvedValue({
174-
ok: false,
175-
status: 404,
176-
json: vi.fn(),
177-
})
178-
179-
const mockError = new TRPCError({ code: "NOT_FOUND", message: "Image not found" })
180-
;(imageHelpers.mapResponseToTRPCError as Mock).mockReturnValue(mockError)
170+
mockCtx.mockGlance.get.mockRejectedValue({ statusCode: 404, message: "Not Found" })
181171

182172
const input = { imageId: "123e4567-e89b-12d3-a456-426614174111" }
183173

184-
await expect(caller.image.getImageById(input)).rejects.toThrow("Image not found")
174+
await expect(caller.image.getImageById(input)).rejects.toThrow(`Image not found image: ${input.imageId}`)
185175
})
186176
})
187177

@@ -223,22 +213,15 @@ describe("imageRouter", () => {
223213
const mockCtx = createMockContext()
224214
const caller = createCaller(mockCtx)
225215

226-
mockCtx.mockGlance.post.mockResolvedValue({
227-
ok: false,
228-
status: 400,
229-
json: vi.fn(),
230-
})
231-
232-
const mockError = new TRPCError({ code: "BAD_REQUEST", message: "Create failed" })
233-
;(imageHelpers.mapResponseToTRPCError as Mock).mockReturnValue(mockError)
216+
mockCtx.mockGlance.post.mockRejectedValue({ statusCode: 400, message: "Bad Request" })
234217

235218
const input = {
236219
name: "new-image",
237220
container_format: "bare" as const,
238221
disk_format: "qcow2" as const,
239222
}
240223

241-
await expect(caller.image.createImage(input)).rejects.toThrow("Create failed")
224+
await expect(caller.image.createImage(input)).rejects.toThrow("Failed to create image")
242225
})
243226
})
244227

@@ -271,10 +254,8 @@ describe("imageRouter", () => {
271254
const mockCtx = createMockContext()
272255
const caller = createCaller(mockCtx)
273256

274-
mockCtx.mockGlance.put.mockResolvedValue({
275-
ok: false,
276-
status: 400,
277-
json: vi.fn(),
257+
mockCtx.mockGlance.put.mockImplementation(() => {
258+
return Promise.reject(new Error("400 Bad Request"))
278259
})
279260

280261
const mockError = new TRPCError({ code: "BAD_REQUEST", message: "Upload failed" })

0 commit comments

Comments
 (0)