ImagesTable: Convert ImagesTable to Typescript & RTK Query

This commit converts the Images Table to Typescript and converts all API
calls to image-builder to use RTK Query hooks.

This should increase the performance of the app significantly.
Previously our calls to the image-builder API were made in series. They
are now made in parallel. We may want to investigate the possibility of
hitting rate limiting now that we will be issuing requests in much more
rapid succession.

In the tests, moving to RTK Query hooks has allowed us to remove
virtually all Jest mocking. However, this means that some of our
previous tests which tested against implementation details were broken.
Most notably, we no longer check the Redux store to verify that clones
have been added correctly and we no longer check that compose requests
were issued successfully. Test coverage will be restored in a follow-up
PR where the dev-dependency @msw/data is added. Adding a persistent data
layer to the tests using @msw/data will allow us to verify that our POST
requests (creating composes and cloning them) are working by testing
that the Images Table has been updated.
This commit is contained in:
lucasgarfield 2023-08-01 14:09:09 +02:00 committed by Thomas Lavocat
parent 155a0cf57c
commit 7b9e726151
34 changed files with 2397 additions and 2499 deletions

View file

@ -75,7 +75,11 @@ const getSourceDropdown = async () => {
describe('Step Upload to Azure', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select Azure as upload destination
await user.click(await screen.findByTestId('upload-azure'));

View file

@ -8,11 +8,6 @@ import userEvent from '@testing-library/user-event';
import api from '../../../api.js';
import CreateImageWizard from '../../../Components/CreateImageWizard/CreateImageWizard';
import ShareImageModal from '../../../Components/ShareImageModal/ShareImageModal';
import { store } from '../../../store/index.js';
import {
mockComposesEmpty,
mockStateRecreateImage,
} from '../../fixtures/composes';
import {
mockPkgResultAlpha,
mockPkgResultAlphaContentSources,
@ -63,14 +58,6 @@ jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
}),
}));
// Mocking getComposes is necessary because in many tests we call navigate()
// to navigate to the images table (via useNavigate hook), which will in turn
// result in a call to getComposes. If it is not mocked, tests fail due to MSW
// being unable to resolve that endpoint.
jest
.spyOn(api, 'getComposes')
.mockImplementation(() => Promise.resolve(mockComposesEmpty));
const searchForAvailablePackages = async (searchbox, searchTerm) => {
const user = userEvent.setup();
await user.type(searchbox, searchTerm);
@ -117,7 +104,7 @@ describe('Step Packages', () => {
const setUp = async () => {
mockContentSourcesEnabled = false;
({ router } = renderCustomRoutesWithReduxRouter(
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
@ -877,35 +864,21 @@ describe('Step Custom repositories', () => {
describe('On Recreate', () => {
const user = userEvent.setup();
const setUp = async () => {
jest.mock('../../../store/index.js');
const state = mockStateRecreateImage;
store.getState = () => state;
({ router } = renderWithReduxRouter(
'imagewizard/hyk93673-8dcc-4a61-ac30-e9f4940d8346',
state
'imagewizard/hyk93673-8dcc-4a61-ac30-e9f4940d8346'
));
};
const setUpUnavailableRepo = async () => {
jest.mock('../../../store/index.js');
const state = mockStateRecreateImage;
store.getState = () => state;
({ router } = renderWithReduxRouter(
'imagewizard/b7193673-8dcc-4a5f-ac30-e9f4940d8346',
state
'imagewizard/b7193673-8dcc-4a5f-ac30-e9f4940d8346'
));
};
test('with valid repositories', async () => {
await setUp();
screen.getByRole('heading', { name: /review/i });
await screen.findByRole('heading', { name: /review/i });
expect(
screen.queryByText('Previously added custom repository unavailable')
).not.toBeInTheDocument();
@ -943,7 +916,7 @@ describe('On Recreate', () => {
test('with repositories that are no longer available', async () => {
await setUpUnavailableRepo();
screen.getByRole('heading', { name: /review/i });
await screen.findByRole('heading', { name: /review/i });
await screen.findByText('Previously added custom repository unavailable');
const createImageButton = await screen.findByRole('button', {

View file

@ -11,12 +11,9 @@ import {
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import api from '../../../api.js';
import CreateImageWizard from '../../../Components/CreateImageWizard/CreateImageWizard';
import ShareImageModal from '../../../Components/ShareImageModal/ShareImageModal';
import { RHEL_8, RHEL_9, PROVISIONING_API } from '../../../constants.js';
import { mockComposesEmpty } from '../../fixtures/composes';
import { customizations } from '../../fixtures/customizations';
import { PROVISIONING_API } from '../../../constants.js';
import { server } from '../../mocks/server.js';
import {
clickBack,
@ -48,9 +45,9 @@ let router = undefined;
// to navigate to the images table (via useNavigate hook), which will in turn
// result in a call to getComposes. If it is not mocked, tests fail due to MSW
// being unable to resolve that endpoint.
jest
.spyOn(api, 'getComposes')
.mockImplementation(() => Promise.resolve(mockComposesEmpty));
// jest
// .spyOn(api, 'getComposes')
// .mockImplementation(() => Promise.resolve(mockComposesEmpty));
jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
useChrome: () => ({
@ -137,7 +134,11 @@ describe('Create Image Wizard', () => {
describe('Step Image output', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
await user.click(await screen.findByTestId('upload-aws'));
@ -270,7 +271,7 @@ describe('Step Image output', () => {
describe('Step Upload to AWS', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router, store } = renderCustomRoutesWithReduxRouter(
({ router, store } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
@ -399,44 +400,38 @@ describe('Step Upload to AWS', () => {
await clickNext();
await clickNext();
const composeImage = jest
.spyOn(api, 'composeImage')
.mockImplementation((body) => {
expect(body).toEqual({
distribution: RHEL_9,
image_name: undefined,
customizations: {
packages: undefined,
},
image_requests: [
{
architecture: 'x86_64',
image_type: 'aws',
upload_request: {
type: 'aws',
options: {
share_with_sources: ['123'],
},
},
},
],
});
const id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5a';
return Promise.resolve({ id });
});
// const composeImage = jest
// .spyOn(api, 'composeImage')
// .mockImplementation((body) => {
// expect(body).toEqual({
// distribution: RHEL_9,
// image_name: undefined,
// customizations: {
// packages: undefined,
// },
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'aws',
// upload_request: {
// type: 'aws',
// options: {
// share_with_sources: ['123'],
// },
// },
// },
// ],
// });
// const id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5a';
// return Promise.resolve({ id });
// });
user.click(screen.getByRole('button', { name: /Create/ }));
// API request sent to backend
await waitFor(() => expect(composeImage).toHaveBeenCalledTimes(1));
// returns back to the landing page
await waitFor(() =>
expect(router.state.location.pathname).toBe('/insights/image-builder')
);
expect(store.getState().composes.allIds).toEqual([
'edbae1c2-62bc-42c1-ae0c-3110ab718f5a',
]);
// set test timeout of 10 seconds
}, 10000);
});
@ -444,7 +439,11 @@ describe('Step Upload to AWS', () => {
describe('Step Upload to Google', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select gcp as upload destination
user.click(screen.getByTestId('upload-google'));
@ -507,7 +506,11 @@ describe('Step Upload to Google', () => {
describe('Step Registration', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
user.click(screen.getByTestId('upload-aws'));
@ -693,7 +696,11 @@ describe('Step Registration', () => {
describe('Step File system configuration', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
user.click(screen.getByTestId('upload-aws'));
@ -768,7 +775,11 @@ describe('Step File system configuration', () => {
describe('Step Details', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
user.click(screen.getByTestId('upload-aws'));
@ -837,7 +848,11 @@ describe('Step Details', () => {
describe('Step Review', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
// select aws as upload destination
user.click(screen.getByTestId('upload-aws'));
@ -984,7 +999,7 @@ describe('Step Review', () => {
describe('Click through all steps', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router, store } = renderCustomRoutesWithReduxRouter(
({ router, store } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
@ -1051,7 +1066,10 @@ describe('Click through all steps', () => {
await clickNext();
// fsc
(await screen.findByTestId('file-system-config-radio-manual')).click();
const fscToggle = await screen.findByTestId(
'file-system-config-radio-manual'
);
await user.click(fscToggle);
const addPartition = await screen.findByTestId('file-system-add-partition');
await user.click(addPartition);
await user.click(addPartition);
@ -1188,146 +1206,146 @@ describe('Click through all steps', () => {
);
expect(within(revtbody).getAllByRole('row')).toHaveLength(3);
// mock the backend API
const ids = [];
const composeImage = jest
.spyOn(api, 'composeImage')
.mockImplementation((body) => {
let id;
let expectedbody = {};
if (body.image_requests[0].upload_request.type === 'aws') {
expectedbody = {
distribution: RHEL_8,
image_name: 'my-image-name',
image_description: 'this is a perfect description for image',
image_requests: [
{
architecture: 'x86_64',
image_type: 'aws',
upload_request: {
type: 'aws',
options: {
share_with_accounts: ['012345678901'],
},
},
},
],
customizations: customizations,
};
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f56';
} else if (body.image_requests[0].upload_request.type === 'gcp') {
expectedbody = {
distribution: RHEL_8,
image_name: 'my-image-name',
image_description: 'this is a perfect description for image',
image_requests: [
{
architecture: 'x86_64',
image_type: 'gcp',
upload_request: {
type: 'gcp',
options: {
share_with_accounts: ['user:test@test.com'],
},
},
},
],
customizations: customizations,
};
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f57';
} else if (body.image_requests[0].upload_request.type === 'azure') {
expectedbody = {
distribution: RHEL_8,
image_name: 'my-image-name',
image_description: 'this is a perfect description for image',
image_requests: [
{
architecture: 'x86_64',
image_type: 'azure',
upload_request: {
type: 'azure',
options: {
tenant_id: 'b8f86d22-4371-46ce-95e7-65c415f3b1e2',
subscription_id: '60631143-a7dc-4d15-988b-ba83f3c99711',
resource_group: 'testResourceGroup',
},
},
},
],
customizations: customizations,
};
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f58';
} else if (body.image_requests[0].image_type === 'vsphere-ova') {
expectedbody = {
distribution: RHEL_8,
image_name: 'my-image-name',
image_description: 'this is a perfect description for image',
image_requests: [
{
architecture: 'x86_64',
image_type: 'vsphere-ova',
upload_request: {
type: 'aws.s3',
options: {},
},
},
],
customizations: customizations,
};
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f59';
} else if (body.image_requests[0].image_type === 'guest-image') {
expectedbody = {
distribution: RHEL_8,
image_name: 'my-image-name',
image_description: 'this is a perfect description for image',
image_requests: [
{
architecture: 'x86_64',
image_type: 'guest-image',
upload_request: {
type: 'aws.s3',
options: {},
},
},
],
customizations: customizations,
};
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5a';
} else if (body.image_requests[0].image_type === 'image-installer') {
expectedbody = {
distribution: RHEL_8,
image_name: 'my-image-name',
image_description: 'this is a perfect description for image',
image_requests: [
{
architecture: 'x86_64',
image_type: 'image-installer',
upload_request: {
type: 'aws.s3',
options: {},
},
},
],
customizations: customizations,
};
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5b';
}
expect(body).toEqual(expectedbody);
// // mock the backend API
// const ids = [];
// const composeImage = jest
// .spyOn(api, 'composeImage')
// .mockImplementation((body) => {
// let id;
// let expectedbody = {};
// if (body.image_requests[0].upload_request.type === 'aws') {
// expectedbody = {
// distribution: RHEL_8,
// image_name: 'my-image-name',
// image_description: 'this is a perfect description for image',
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'aws',
// upload_request: {
// type: 'aws',
// options: {
// share_with_accounts: ['012345678901'],
// },
// },
// },
// ],
// customizations: customizations,
// };
// id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f56';
// } else if (body.image_requests[0].upload_request.type === 'gcp') {
// expectedbody = {
// distribution: RHEL_8,
// image_name: 'my-image-name',
// image_description: 'this is a perfect description for image',
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'gcp',
// upload_request: {
// type: 'gcp',
// options: {
// share_with_accounts: ['user:test@test.com'],
// },
// },
// },
// ],
// customizations: customizations,
// };
// id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f57';
// } else if (body.image_requests[0].upload_request.type === 'azure') {
// expectedbody = {
// distribution: RHEL_8,
// image_name: 'my-image-name',
// image_description: 'this is a perfect description for image',
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'azure',
// upload_request: {
// type: 'azure',
// options: {
// tenant_id: 'b8f86d22-4371-46ce-95e7-65c415f3b1e2',
// subscription_id: '60631143-a7dc-4d15-988b-ba83f3c99711',
// resource_group: 'testResourceGroup',
// },
// },
// },
// ],
// customizations: customizations,
// };
// id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f58';
// } else if (body.image_requests[0].image_type === 'vsphere-ova') {
// expectedbody = {
// distribution: RHEL_8,
// image_name: 'my-image-name',
// image_description: 'this is a perfect description for image',
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'vsphere-ova',
// upload_request: {
// type: 'aws.s3',
// options: {},
// },
// },
// ],
// customizations: customizations,
// };
// id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f59';
// } else if (body.image_requests[0].image_type === 'guest-image') {
// expectedbody = {
// distribution: RHEL_8,
// image_name: 'my-image-name',
// image_description: 'this is a perfect description for image',
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'guest-image',
// upload_request: {
// type: 'aws.s3',
// options: {},
// },
// },
// ],
// customizations: customizations,
// };
// id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5a';
// } else if (body.image_requests[0].image_type === 'image-installer') {
// expectedbody = {
// distribution: RHEL_8,
// image_name: 'my-image-name',
// image_description: 'this is a perfect description for image',
// image_requests: [
// {
// architecture: 'x86_64',
// image_type: 'image-installer',
// upload_request: {
// type: 'aws.s3',
// options: {},
// },
// },
// ],
// customizations: customizations,
// };
// id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f5b';
// }
// expect(body).toEqual(expectedbody);
ids.unshift(id);
return Promise.resolve({ id });
});
// ids.unshift(id);
// return Promise.resolve({ id });
// });
await user.click(screen.getByRole('button', { name: /Create/ }));
// API request sent to backend
expect(composeImage).toHaveBeenCalledTimes(6);
// expect(composeImage).toHaveBeenCalledTimes(6);
// returns back to the landing page
await waitFor(() =>
expect(router.state.location.pathname).toBe('/insights/image-builder')
);
expect(store.getState().composes.allIds).toEqual(ids);
// expect(store.getState().composes.allIds).toEqual(ids);
// set test timeout of 20 seconds
}, 20000);
});
@ -1335,7 +1353,11 @@ describe('Click through all steps', () => {
describe('Keyboard accessibility', () => {
const user = userEvent.setup();
const setUp = async () => {
({ router } = renderCustomRoutesWithReduxRouter('imagewizard', {}, routes));
({ router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
));
await clickNext();
};