diff --git a/packages/manager/apps/vrack-services/mocks/vrack-services/get-vrack-services.json b/packages/manager/apps/vrack-services/mocks/vrack-services/get-vrack-services.json index 3b70e9593729..f5a784f220de 100644 --- a/packages/manager/apps/vrack-services/mocks/vrack-services/get-vrack-services.json +++ b/packages/manager/apps/vrack-services/mocks/vrack-services/get-vrack-services.json @@ -31,7 +31,7 @@ "productStatus": "DRAFT", "subnets": [ { - "cidr": "192.168.0.0/16", + "cidr": "10.0.0.0/24", "displayName": "My.Subnet", "serviceEndpoints": [ { @@ -54,7 +54,7 @@ } ], "serviceRange": { - "cidr": "192.168.0.0/29", + "cidr": "10.0.0.0/29", "remainingIps": 1, "reservedIps": 5, "usedIps": 2 diff --git a/packages/manager/apps/vrack-services/src/pages/create-subnet/subnetCreate.spec.tsx b/packages/manager/apps/vrack-services/src/pages/create-subnet/subnetCreate.spec.tsx new file mode 100644 index 000000000000..4ca555baf214 --- /dev/null +++ b/packages/manager/apps/vrack-services/src/pages/create-subnet/subnetCreate.spec.tsx @@ -0,0 +1,69 @@ +import { describe, it } from 'vitest'; +import '@testing-library/jest-dom'; +import { assertTextVisibility } from '@ovh-ux/manager-core-test-utils'; +import { waitFor, fireEvent } from '@testing-library/react'; +import { ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components'; +import { + assertOsdFormInputInError, + changeInputValueByLabelText, + clickOnRadioByName, + getButtonByVariant, + labels, + renderTest, +} from '../../test-utils'; +import vrackServicesList from '../../../mocks/vrack-services/get-vrack-services.json'; +import { urls } from '@/routes/routes.constants'; + +describe('Vrack Services subnets page test suite', () => { + it('should create a subnet', async () => { + const { container } = await renderTest({ + nbVs: 2, + initialRoute: urls.createSubnet.replace(':id', vrackServicesList[1].id), + }); + + await assertTextVisibility(labels.subnets.subnetNameLabel); + + await changeInputValueByLabelText({ + inputLabel: labels.subnets.cidrLabel, + value: '10.0.0.0/23', + }); + await assertOsdFormInputInError({ + inputLabel: labels.subnets.cidrLabel, + inError: true, + }); + + await changeInputValueByLabelText({ + inputLabel: labels.subnets.cidrLabel, + value: '10.0.0.0/24', + }); + await assertOsdFormInputInError({ + inputLabel: labels.subnets.cidrLabel, + inError: false, + }); + await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: false, + }); + + clickOnRadioByName({ container, name: 'hasVlan', value: 'vlanNumber' }); + await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: true, + }); + await changeInputValueByLabelText({ + inputLabel: labels.subnets.vlanNumberLabel, + value: '20', + }); + const submitButton = await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: false, + }); + + await waitFor(() => fireEvent.click(submitButton)); + + await assertTextVisibility(labels.subnets.subnetDatagridDisplayNameLabel); + }); +}); diff --git a/packages/manager/apps/vrack-services/src/pages/subnets/SubnetsTab.spec.tsx b/packages/manager/apps/vrack-services/src/pages/subnets/SubnetsTab.spec.tsx new file mode 100644 index 000000000000..7b91ae0c8bc0 --- /dev/null +++ b/packages/manager/apps/vrack-services/src/pages/subnets/SubnetsTab.spec.tsx @@ -0,0 +1,231 @@ +import { describe, it } from 'vitest'; +import '@testing-library/jest-dom'; +import { + assertModalVisibility, + assertTextVisibility, + getButtonByLabel, +} from '@ovh-ux/manager-core-test-utils'; +import { waitFor, fireEvent, screen } from '@testing-library/react'; +import { ODS_BUTTON_VARIANT, ODS_ICON_NAME } from '@ovhcloud/ods-components'; +import { + assertModalTitle, + assertOsdFormInputInError, + changeInputValueByLabelText, + getButtonByIcon, + getButtonByVariant, + labels, + renderTest, +} from '../../test-utils'; +import vrackServicesList from '../../../mocks/vrack-services/get-vrack-services.json'; +import { urls } from '@/routes/routes.constants'; + +describe('Vrack Services subnets page test suite', () => { + it('should display the subnets onboarding if no subnet exist', async () => { + await renderTest({ + nbVs: 1, + initialRoute: urls.overview.replace(':id', vrackServicesList[0].id), + }); + + const subnetTab = await waitFor(() => + screen.getByText(labels.dashboard.subnetsTabLabel), + ); + + await waitFor(() => fireEvent.click(subnetTab)); + + await assertTextVisibility(labels.subnets.subnetsOnboardingTitle); + }); + + it('should display the subnets listing if subnet exist', async () => { + await renderTest({ + nbVs: 2, + initialRoute: urls.overview.replace(':id', vrackServicesList[1].id), + }); + + const subnetTab = await waitFor(() => + screen.getByText(labels.dashboard.subnetsTabLabel), + ); + + await waitFor(() => fireEvent.click(subnetTab)); + + await assertTextVisibility(labels.subnets.subnetDatagridDisplayNameLabel); + await assertTextVisibility( + vrackServicesList[1].currentState.subnets[0].displayName, + ); + await assertTextVisibility( + vrackServicesList[1].currentState.subnets[0].serviceRange.cidr, + ); + }); + + it('should limit the subnets to 1', async () => { + const { container } = await renderTest({ + nbVs: 2, + initialRoute: urls.subnetsListing.replace(':id', vrackServicesList[1].id), + }); + + await assertTextVisibility(labels.subnets.betaSubnetLimitMessage); + await getButtonByLabel({ + container, + label: labels.subnets.createSubnetButtonLabel, + disabled: true, + }); + }); + + it('should edit a subnet', async () => { + const { container } = await renderTest({ + nbVs: 2, + initialRoute: urls.subnetsListing.replace(':id', vrackServicesList[1].id), + }); + + const actionMenuButton = await getButtonByIcon({ + container, + iconName: ODS_ICON_NAME.ELLIPSIS, + }); + + await waitFor(() => fireEvent.click(actionMenuButton)); + + const editLink = await getButtonByLabel({ + container, + label: labels.subnets['action-editSubnet'], + }); + + await waitFor(() => fireEvent.click(editLink)); + + await assertModalTitle({ + container, + title: labels.subnets.modalSubnetUpdateHeadline, + }); + + // not /24 + await changeInputValueByLabelText({ + inputLabel: labels.subnets.cidrLabel, + value: '10.0.0.0/23', + }); + await assertOsdFormInputInError({ + inputLabel: labels.subnets.cidrLabel, + inError: true, + }); + + // didn't change + await changeInputValueByLabelText({ + inputLabel: labels.subnets.cidrLabel, + value: '10.0.0.0/24', + }); + await assertOsdFormInputInError({ + inputLabel: labels.subnets.cidrLabel, + inError: false, + }); + await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: true, + }); + + // new value is correct + await changeInputValueByLabelText({ + inputLabel: labels.subnets.cidrLabel, + value: '11.0.0.0/24', + }); + await assertOsdFormInputInError({ + inputLabel: labels.subnets.cidrLabel, + inError: false, + }); + const submitButton = await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: false, + }); + + await waitFor(() => fireEvent.click(submitButton)); + + await assertModalVisibility({ container, isVisible: false }); + }); + + it('should display an error if api fail to edit a subnet', async () => { + const { container } = await renderTest({ + nbVs: 2, + initialRoute: urls.subnetsListing.replace(':id', vrackServicesList[1].id), + updateKo: true, + }); + + const actionMenuButton = await getButtonByIcon({ + container, + iconName: ODS_ICON_NAME.ELLIPSIS, + }); + + await waitFor(() => fireEvent.click(actionMenuButton)); + + const editLink = await getButtonByLabel({ + container, + label: labels.subnets['action-editSubnet'], + }); + + await waitFor(() => fireEvent.click(editLink)); + + // new value is correct + await changeInputValueByLabelText({ + inputLabel: labels.subnets.cidrLabel, + value: '11.0.0.0/24', + }); + await assertOsdFormInputInError({ + inputLabel: labels.subnets.cidrLabel, + inError: false, + }); + const submitButton = await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: false, + }); + + await waitFor(() => fireEvent.click(submitButton)); + + await assertModalVisibility({ container, isVisible: true }); + await assertTextVisibility( + labels.subnets.subnetUpdateError.replace('{{error}}', 'Update vs error'), + ); + }); + + it('should delete a subnet', async () => { + const { container } = await renderTest({ + nbVs: 2, + initialRoute: urls.subnetsListing.replace(':id', vrackServicesList[1].id), + }); + + const actionMenuButton = await getButtonByIcon({ + container, + iconName: ODS_ICON_NAME.ELLIPSIS, + }); + + await waitFor(() => fireEvent.click(actionMenuButton)); + + const editLink = await getButtonByLabel({ + container, + label: labels.subnets['action-deleteSubnet'], + }); + + await waitFor(() => fireEvent.click(editLink)); + + await assertModalTitle({ + container, + title: labels.subnets.modalDeleteSubnetHeadline, + }); + await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: true, + }); + await changeInputValueByLabelText({ + inputLabel: labels.subnets.modalDeleteSubnetInputLabel, + value: 'TERMINATE', + }); + + const submitButton = await getButtonByVariant({ + container, + variant: ODS_BUTTON_VARIANT.flat, + disabled: false, + }); + + await waitFor(() => fireEvent.click(submitButton)); + + await assertModalVisibility({ container, isVisible: false }); + }); +}); diff --git a/packages/manager/apps/vrack-services/src/test-utils/uiTestHelpers.ts b/packages/manager/apps/vrack-services/src/test-utils/uiTestHelpers.ts index ad84f0c6c22f..7890f7b9e2f5 100644 --- a/packages/manager/apps/vrack-services/src/test-utils/uiTestHelpers.ts +++ b/packages/manager/apps/vrack-services/src/test-utils/uiTestHelpers.ts @@ -45,8 +45,8 @@ export const getButtonByVariant = async ({ [variant].includes(btn.getAttribute('variant') as ODS_BUTTON_VARIANT), )[nth]; return disabled - ? expect(button).toHaveAttribute('disabled') - : expect(button).not.toHaveAttribute('disabled'); + ? expect(button).toBeDisabled() + : expect(button).not.toBeDisabled(); }, WAIT_FOR_DEFAULT_OPTIONS); return button; }; @@ -100,3 +100,68 @@ export const getButtonByIcon = async ({ }, WAIT_FOR_DEFAULT_OPTIONS); return button; }; + +export const changeInputValueByLabelText = async ({ + inputLabel, + value, +}: { + inputLabel: string; + value: string; +}) => { + const odsForm: HTMLElement = screen + .getByText(inputLabel) + ?.closest('osds-form-field'); + const odsInput = odsForm.querySelector('osds-input'); + const event = new CustomEvent('odsValueChange', { + detail: { value }, + }); + return waitFor(() => fireEvent(odsInput, event)); +}; + +export const assertOsdFormInputInError = async ({ + inputLabel, + inError = false, +}: { + inputLabel: string; + inError?: boolean; +}) => + waitFor(() => { + const odsForm: HTMLElement = screen + .getByText(inputLabel) + ?.closest('osds-form-field'); + const odsInput: HTMLElement = odsForm.querySelector('osds-input'); + if (inError) { + expect(odsInput).toHaveAttribute('error'); + } else { + expect(odsInput).toHaveAttribute('error', 'false'); + } + return odsInput; + }, WAIT_FOR_DEFAULT_OPTIONS); + +export const clickOnRadioByName = async ({ + container, + name, + value, +}: { + container: HTMLElement; + name: string; + value: string; +}) => { + let odsRadio: HTMLElement; + await waitFor(() => { + const odsRadios: HTMLElement[] = Array.from( + container.querySelectorAll('osds-radio'), + ); + odsRadio = odsRadios.find( + (item) => + item.getAttribute('name') === name && + item.getAttribute('value') === value, + ); + }); + + if (!odsRadio) return null; + + await waitFor(() => fireEvent.click(odsRadio)); + + return odsRadio; +};