diff --git a/app/test/e2e/instance-create.e2e.ts b/app/test/e2e/instance-create.e2e.ts index 5cc626c2b8..cdfb27b889 100644 --- a/app/test/e2e/instance-create.e2e.ts +++ b/app/test/e2e/instance-create.e2e.ts @@ -217,3 +217,11 @@ test('add ssh key from instance create form', async ({ page }) => { await page.getByRole('link', { name: 'SSH Keys' }).click() await expectRowVisible(page.getByRole('table'), { Name: newKey, Description: 'hi' }) }) + +test('shows object not found error on no default pool', async ({ page }) => { + await page.goto('/projects/mock-project/instances-new') + await page.getByRole('textbox', { name: 'Name', exact: true }).fill('no-default-pool') + await page.getByRole('button', { name: 'Create instance' }).click() + + await expect(page.getByText('Not found: default IP pool')).toBeVisible() +}) diff --git a/libs/api-mocks/msw/db.ts b/libs/api-mocks/msw/db.ts index ff9b73b75f..a5d9d27c67 100644 --- a/libs/api-mocks/msw/db.ts +++ b/libs/api-mocks/msw/db.ts @@ -19,8 +19,10 @@ import { internalError, json } from './util' const notFoundBody = { error_code: 'ObjectNotFound' } as const export type NotFound = typeof notFoundBody -export const notFoundErr = () => - json({ error_code: 'ObjectNotFound' } as const, { status: 404 }) +export const notFoundErr = (msg?: string) => { + const message = msg ? `not found: ${msg}` : 'not found' + return json({ error_code: 'ObjectNotFound', message } as const, { status: 404 }) +} export const lookupById = (table: T[], id: string) => { const item = table.find((i) => i.id === id) diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index 237a280104..537d0dcf96 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -292,6 +292,10 @@ export const handlers = makeHandlers({ instanceCreate({ body, query }) { const project = lookup.project(query) + if (body.name === 'no-default-pool') { + throw notFoundErr('default IP pool for current silo') + } + errIfExists(db.instances, { name: body.name, project_id: project.id }, 'instance') const instanceId = uuid() diff --git a/libs/api/__tests__/errors.spec.ts b/libs/api/__tests__/errors.spec.ts index 16c8e14772..81c0b367b5 100644 --- a/libs/api/__tests__/errors.spec.ts +++ b/libs/api/__tests__/errors.spec.ts @@ -83,6 +83,21 @@ describe('processServerError', () => { }) }) + describe('ObjectNotFound', () => { + it('passes through the API error', () => { + const error = makeError({ + errorCode: 'ObjectNotFound', + message: 'not found: whatever', + statusCode: 404, + }) + expect(processServerError('fakeThingCreate', error)).toEqual({ + errorCode: 'ObjectNotFound', + message: 'Not found: whatever', + statusCode: 404, + }) + }) + }) + it('falls back to server error message if code not found', () => { const error = makeError({ errorCode: 'WeirdError', message: 'whatever' }) expect(processServerError('womp', error)).toEqual({ @@ -100,6 +115,6 @@ it.each([ ['instanceNetworkInterfaceCreate', '', 'interface'], ['instanceNetworkInterfaceCreate', 'already exists: something else', 'something else'], ['doesNotContainC-reate', '', null], -])('getResourceName', (method, message, resource) => { +])('getResourceName: %s', (method, message, resource) => { expect(getResourceName(method, message)).toEqual(resource) }) diff --git a/libs/api/__tests__/hooks.spec.tsx b/libs/api/__tests__/hooks.spec.tsx index 1aa9e2ddec..e8d21b5ab7 100644 --- a/libs/api/__tests__/hooks.spec.tsx +++ b/libs/api/__tests__/hooks.spec.tsx @@ -130,7 +130,7 @@ describe('useApiQuery', () => { const error = onError.mock.lastCall?.[0] expect(error).toEqual({ errorCode: 'ObjectNotFound', - message: 'Object not found', + message: 'Not found', statusCode: 404, }) }) @@ -208,7 +208,7 @@ describe('useApiMutation', () => { expect(result.current.error).toEqual({ errorCode: 'ObjectNotFound', - message: 'Object not found', + message: 'Not found', statusCode: 404, }) }) diff --git a/libs/api/errors.ts b/libs/api/errors.ts index 5733777681..a4cd12f5b8 100644 --- a/libs/api/errors.ts +++ b/libs/api/errors.ts @@ -7,7 +7,7 @@ */ import { camelCaseToWords, capitalize } from '@oxide/util' -import type { ErrorResult } from '.' +import type { ErrorResult } from './__generated__/Api' /** * Processed API error ready for display in the console. Note that it's possible @@ -56,7 +56,7 @@ export function processServerError(method: string, resp: ErrorResult): ApiError if (code === 'Forbidden') { message = 'Action not authorized' } else if (code === 'ObjectNotFound') { - message = 'Object not found' + message = capitalize(resp.data.message) } else if (code === 'ObjectAlreadyExists') { const resource = getResourceName(method, resp.data.message) if (resource) {