diff --git a/frontend/src/__mocks__/mockAcceleratorProfile.ts b/frontend/src/__mocks__/mockAcceleratorProfile.ts index b57e3b7202..0155a2c429 100644 --- a/frontend/src/__mocks__/mockAcceleratorProfile.ts +++ b/frontend/src/__mocks__/mockAcceleratorProfile.ts @@ -5,6 +5,7 @@ import { genUID } from './mockUtils'; type MockResourceConfigType = { name?: string; namespace?: string; + uid?: string; displayName?: string; identifier?: string; description?: string; @@ -15,6 +16,7 @@ type MockResourceConfigType = { export const mockAcceleratorProfile = ({ name = 'migrated-gpu', namespace = 'test-project', + uid = genUID('service'), displayName = 'Nvidia GPU', identifier = 'nvidia.com/gpu', description = '', @@ -35,7 +37,7 @@ export const mockAcceleratorProfile = ({ name, namespace, resourceVersion: '1309350', - uid: genUID('service'), + uid, }, spec: { identifier, diff --git a/frontend/src/pages/notebookController/screens/server/__tests__/useAcceleratorProfiles.spec.ts b/frontend/src/pages/notebookController/screens/server/__tests__/useAcceleratorProfiles.spec.ts new file mode 100644 index 0000000000..504587c8c7 --- /dev/null +++ b/frontend/src/pages/notebookController/screens/server/__tests__/useAcceleratorProfiles.spec.ts @@ -0,0 +1,99 @@ +import { act } from '@testing-library/react'; +import { k8sListResource } from '@openshift/dynamic-plugin-sdk-utils'; +import { standardUseFetchState, testHook } from '~/__tests__/unit/testUtils/hooks'; +import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList'; +import { mockAcceleratorProfile } from '~/__mocks__/mockAcceleratorProfile'; +import useAcceleratorProfiles from '~/pages/notebookController/screens/server/useAcceleratorProfiles'; + +jest.mock('@openshift/dynamic-plugin-sdk-utils', () => ({ + k8sListResource: jest.fn(), +})); + +const k8sListResourceMock = k8sListResource as jest.Mock; + +describe('useGroups', () => { + it('should return successful list of accelerators profiles', async () => { + k8sListResourceMock.mockReturnValue( + Promise.resolve(mockK8sResourceList([mockAcceleratorProfile({ uid: 'test-project-12m' })])), + ); + + const renderResult = testHook(useAcceleratorProfiles)('test-project'); + expect(k8sListResourceMock).toHaveBeenCalledTimes(1); + expect(renderResult).hookToStrictEqual(standardUseFetchState([])); + expect(renderResult).hookToHaveUpdateCount(1); + + // wait for update + await renderResult.waitForNextUpdate(); + expect(k8sListResourceMock).toHaveBeenCalledTimes(1); + expect(renderResult).hookToStrictEqual( + standardUseFetchState([mockAcceleratorProfile({ uid: 'test-project-12m' })], true), + ); + expect(renderResult).hookToHaveUpdateCount(2); + expect(renderResult).hookToBeStable([false, false, true, true]); + + // refresh + k8sListResourceMock.mockReturnValue( + Promise.resolve(mockK8sResourceList([mockAcceleratorProfile({})])), + ); + await act(() => renderResult.result.current[3]()); + expect(k8sListResourceMock).toHaveBeenCalledTimes(2); + expect(renderResult).hookToHaveUpdateCount(3); + expect(renderResult).hookToBeStable([false, true, true, true]); + }); +}); + +it('should handle when accelerator profiles are not found', async () => { + const error = { + statusObject: { + code: 404, + }, + }; + k8sListResourceMock.mockReturnValue(Promise.reject(error)); + + const renderResult = testHook(useAcceleratorProfiles)('test-project'); + expect(k8sListResourceMock).toHaveBeenCalledTimes(1); + expect(renderResult).hookToStrictEqual(standardUseFetchState([])); + expect(renderResult).hookToHaveUpdateCount(1); + + await renderResult.waitForNextUpdate(); + expect(k8sListResourceMock).toHaveBeenCalledTimes(1); + expect(renderResult).hookToStrictEqual( + standardUseFetchState([], false, new Error('No accelerators profiles found.')), + ); + expect(renderResult).hookToHaveUpdateCount(2); + expect(renderResult).hookToBeStable([true, true, false, true]); + + //refresh + await act(() => renderResult.result.current[3]()); + // error 404 should cache error and prevent subsequent attempts to fetch + expect(k8sListResourceMock).toHaveBeenCalledTimes(2); + expect(renderResult).hookToStrictEqual( + standardUseFetchState([], false, new Error('No accelerators profiles found.')), + ); + expect(renderResult).hookToHaveUpdateCount(3); + expect(renderResult).hookToBeStable([true, true, false, true]); +}); + +it('should handle other errors and rethrow', async () => { + k8sListResourceMock.mockReturnValue(Promise.reject(new Error('error1'))); + + const renderResult = testHook(useAcceleratorProfiles)('test-project'); + expect(k8sListResourceMock).toHaveBeenCalledTimes(1); + expect(renderResult).hookToStrictEqual(standardUseFetchState([])); + expect(renderResult).hookToHaveUpdateCount(1); + + // wait for update + await renderResult.waitForNextUpdate(); + expect(k8sListResourceMock).toHaveBeenCalledTimes(1); + expect(renderResult).hookToStrictEqual(standardUseFetchState([], false, new Error('error1'))); + expect(renderResult).hookToHaveUpdateCount(2); + expect(renderResult).hookToBeStable([true, true, false, true]); + + // refresh + k8sListResourceMock.mockReturnValue(Promise.reject(new Error('error2'))); + await act(() => renderResult.result.current[3]()); + expect(k8sListResourceMock).toHaveBeenCalledTimes(2); + expect(renderResult).hookToStrictEqual(standardUseFetchState([], false, new Error('error2'))); + expect(renderResult).hookToHaveUpdateCount(3); + expect(renderResult).hookToBeStable([true, true, false, true]); +}); diff --git a/frontend/src/pages/notebookController/screens/server/useAcceleratorProfiles.ts b/frontend/src/pages/notebookController/screens/server/useAcceleratorProfiles.ts index 975b979b60..232956ab7c 100644 --- a/frontend/src/pages/notebookController/screens/server/useAcceleratorProfiles.ts +++ b/frontend/src/pages/notebookController/screens/server/useAcceleratorProfiles.ts @@ -5,7 +5,13 @@ import { listAcceleratorProfiles } from '~/api'; const useAcceleratorProfiles = (namespace: string): FetchState => { const getAcceleratorProfiles = React.useCallback( - () => listAcceleratorProfiles(namespace), + () => + listAcceleratorProfiles(namespace).catch((e) => { + if (e.statusObject?.code === 404) { + throw new Error('No accelerators profiles found.'); + } + throw e; + }), [namespace], ); return useFetchState(getAcceleratorProfiles, []);