Skip to content

Commit

Permalink
V2Wizard/Registration: Add request assertion tests
Browse files Browse the repository at this point in the history
We need to start using `undefined` as the default state for when a value
has not been defined. Previously we had used things like `’’` for string
typed values. But this causes problems later when generating the request
we send to image-builder. Using `undefined` is explicit and will make
generating the requests much easier (as we don’t need to check for `’’`,
determine the intent, and convert it to undefined if necessary).
Explicit is better than implicit.

With that in mind, tests have been added to ensure that the correct
request is sent to the API for every option on the Registration step.
This is facilitated using a new `spyOnRequest()` function. In the
future, we will have similar tests for the rest of the steps.

A few other minor things:

1. We need to get the `store` using `useStore()`
for when we later call `store.getState()` because the tests use a different
store that is configured in the renderer than the one we were importing.

2. In the wizardSlice, a new type RegistrationType is added that provides
additional type safety instead of using `string`.
  • Loading branch information
lucasgarfield committed Feb 14, 2024
1 parent 841edd1 commit 8c31e6f
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 16 deletions.
5 changes: 3 additions & 2 deletions src/Components/CreateImageWizardV2/steps/Review/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@patternfly/react-core';
import { SpinnerIcon } from '@patternfly/react-icons';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import { useStore } from 'react-redux';
import { useNavigate, useParams } from 'react-router-dom';

import {
Expand All @@ -33,7 +34,7 @@ const ReviewWizardFooter = () => {
const navigate = useNavigate();
const { composeId } = useParams();
const [isOpen, setIsOpen] = useState(false);

const store = useStore();
const onToggleClick = () => {
setIsOpen(!isOpen);
};
Expand All @@ -47,7 +48,7 @@ const ReviewWizardFooter = () => {
const getBlueprintPayload = async () => {
const userData = await auth?.getUser();
const orgId = userData?.identity?.internal?.org_id;
const requestBody = orgId && mapRequestFromState(orgId);
const requestBody = orgId && mapRequestFromState(store, orgId);
return requestBody;
};

Expand Down
43 changes: 32 additions & 11 deletions src/Components/CreateImageWizardV2/utilities/requestMapper.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { RootState, store } from '../../../store';
import { Store } from 'redux';

import { RootState } from '../../../store';
import {
AwsUploadRequestOptions,
CreateBlueprintRequest,
Expand All @@ -18,7 +20,6 @@ import {
selectBaseUrl,
selectBlueprintDescription,
selectBlueprintName,
selectCustomRepositories,
selectDistribution,
selectGcpAccountType,
selectGcpEmail,
Expand All @@ -33,7 +34,10 @@ import {
* @param {string} orgID organization ID
* @returns {CreateBlueprintRequest} blueprint creation request payload
*/
export const mapRequestFromState = (orgID: string): CreateBlueprintRequest => {
export const mapRequestFromState = (
store: Store,
orgID: string
): CreateBlueprintRequest => {
const state = store.getState();
const imageRequests = getImageRequests(state);
const customizations = getCustomizations(state, orgID);
Expand Down Expand Up @@ -127,7 +131,7 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => {
subscription: getSubscription(state, orgID),
packages: undefined,
payload_repositories: undefined,
custom_repositories: selectCustomRepositories(state),
custom_repositories: undefined,
openscap: undefined,
filesystem: undefined,
users: undefined,
Expand All @@ -146,19 +150,36 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => {
};
};

const getSubscription = (state: RootState, orgID: string): Subscription => {
const getSubscription = (
state: RootState,
orgID: string
): Subscription | undefined => {
const registrationType = selectRegistrationType(state);
const activationKey = selectActivationKey(state);

if (registrationType === 'register-later') {
return undefined;
}

if (activationKey === undefined) {
throw new Error(
'Activation key unexpectedly undefined while generating subscription customization'
);
}

const initialSubscription = {
'activation-key': selectActivationKey(state) || '',
'activation-key': activationKey,
organization: Number(orgID),
'server-url': selectServerUrl(state),
'base-url': selectBaseUrl(state),
};
switch (selectRegistrationType(state)) {
case 'register-now-insights':
return { ...initialSubscription, insights: true };

switch (registrationType) {
case 'register-now-rhc':
return { ...initialSubscription, insights: true, rhc: true };
default:
return { ...initialSubscription, insights: false };
case 'register-now-insights':
return { ...initialSubscription, insights: true, rhc: false };
case 'register-now':
return { ...initialSubscription, insights: false, rhc: false };
}
};
2 changes: 2 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const CENTOS_9 = 'centos-9';
export const X86_64 = 'x86_64';
export const AARCH64 = 'aarch64';

export const CREATE_BLUEPRINT = `${IMAGE_BUILDER_API}/experimental/blueprints`;

export const UNIT_KIB = 1024 ** 1;
export const UNIT_MIB = 1024 ** 2;
export const UNIT_GIB = 1024 ** 3;
Expand Down
15 changes: 12 additions & 3 deletions src/store/wizardSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import { RHEL_9, X86_64 } from '../constants';

import { RootState } from '.';

export type RegistrationType =
| 'register-later'
| 'register-now'
| 'register-now-insights'
| 'register-now-rhc';

type wizardState = {
env: {
serverUrl: string;
Expand All @@ -40,7 +46,7 @@ type wizardState = {
email: string;
};
registration: {
registrationType: string;
registrationType: RegistrationType;
activationKey: ActivationKeys['name'];
};
openScap: {
Expand Down Expand Up @@ -83,7 +89,7 @@ const initialState: wizardState = {
},
registration: {
registrationType: 'register-now-rhc',
activationKey: '',
activationKey: undefined,
},
openScap: {
profile: undefined,
Expand Down Expand Up @@ -250,7 +256,10 @@ export const wizardSlice = createSlice({
changeGcpEmail: (state, action: PayloadAction<string>) => {
state.gcp.email = action.payload;
},
changeRegistrationType: (state, action: PayloadAction<string>) => {
changeRegistrationType: (
state,
action: PayloadAction<RegistrationType>
) => {
state.registration.registrationType = action.payload;
},
changeActivationKey: (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';

import { CREATE_BLUEPRINT } from '../../../../../constants';
import {
CreateBlueprintRequest,
ImageRequest,
} from '../../../../../store/imageBuilderApi';
import { clickNext } from '../../../../testUtils';
import {
enterBlueprintName,
render,
saveBlueprint,
spyOnRequest,
} from '../../wizardTestUtils';

jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
useChrome: () => ({
auth: {
getUser: () => {
return {
identity: {
internal: {
org_id: 5,
},
},
};
},
},
isBeta: () => false,
isProd: () => true,
getEnvironment: () => 'prod',
}),
}));

const goToRegistrationStep = async () => {
const bareMetalCheckBox = await screen.findByRole('checkbox', {
name: /bare metal installer checkbox/i,
});
await userEvent.click(bareMetalCheckBox);
await clickNext();
};

const selectActivationKey = async () => {
const activationKeyDropdown = await screen.findByRole('textbox', {
name: 'Select activation key',
});
await userEvent.click(activationKeyDropdown);
const activationKey = await screen.findByRole('option', {
name: 'name0',
});
await userEvent.click(activationKey);
};

const clickShowAdditionalConnectionOptions = async () => {
const link = await screen.findByText('Show additional connection options');
await userEvent.click(link);
};

const deselectEnableRemoteRemediations = async () => {
const checkBox = await screen.findByRole('checkbox', {
name: 'Enable remote remediations and system management with automation',
});
await userEvent.click(checkBox);
};

const deselectPredictiveAnalytics = async () => {
const checkBox = await screen.findByRole('checkbox', {
name: 'Enable predictive analytics and management capabilities',
});
await userEvent.click(checkBox);
};

const clickRegisterLater = async () => {
const radioButton = await screen.findByRole('radio', {
name: 'Register later',
});
await userEvent.click(radioButton);
};

const goToReviewStep = async () => {
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
};

describe('registration request generated correctly', () => {
const imageRequest: ImageRequest = {
architecture: 'x86_64',
image_type: 'image-installer',
upload_request: {
options: {},
type: 'aws.s3',
},
};

const blueprintRequest: CreateBlueprintRequest = {
name: 'Red Velvet',
description: '',
distribution: 'rhel-93',
image_requests: [imageRequest],
customizations: {},
};

test('register + insights + rhc', async () => {
await render();
await goToRegistrationStep();
await selectActivationKey();
await goToReviewStep();

const receivedRequestPromise = spyOnRequest(CREATE_BLUEPRINT);
await saveBlueprint();
const receivedRequest = await receivedRequestPromise;

const expectedSubscription = {
'activation-key': 'name0',
insights: true,
rhc: true,
organization: 5,
'server-url': 'subscription.rhsm.redhat.com',
'base-url': 'https://cdn.redhat.com/',
};
const expectedRequest = {
...blueprintRequest,
customizations: { subscription: expectedSubscription },
};

expect(receivedRequest).toEqual(expectedRequest);
});

test('register + insights', async () => {
await render();
await goToRegistrationStep();
await clickShowAdditionalConnectionOptions();
await deselectEnableRemoteRemediations();
await selectActivationKey();
await goToReviewStep();

const receivedRequestPromise = spyOnRequest(CREATE_BLUEPRINT);
await saveBlueprint();
const receivedRequest = await receivedRequestPromise;

const expectedSubscription = {
'activation-key': 'name0',
insights: true,
rhc: false,
organization: 5,
'server-url': 'subscription.rhsm.redhat.com',
'base-url': 'https://cdn.redhat.com/',
};
const expectedRequest = {
...blueprintRequest,
customizations: { subscription: expectedSubscription },
};

expect(receivedRequest).toEqual(expectedRequest);
});

test('register', async () => {
await render();
await goToRegistrationStep();
await clickShowAdditionalConnectionOptions();
await deselectPredictiveAnalytics();
await selectActivationKey();
await goToReviewStep();

const receivedRequestPromise = spyOnRequest(CREATE_BLUEPRINT);
await saveBlueprint();
const receivedRequest = await receivedRequestPromise;

const expectedSubscription = {
'activation-key': 'name0',
insights: false,
rhc: false,
organization: 5,
'server-url': 'subscription.rhsm.redhat.com',
'base-url': 'https://cdn.redhat.com/',
};
const expectedRequest = {
...blueprintRequest,
customizations: { subscription: expectedSubscription },
};

expect(receivedRequest).toEqual(expectedRequest);
});

test('register Later', async () => {
await render();
await goToRegistrationStep();
await clickShowAdditionalConnectionOptions();
await clickRegisterLater();
await goToReviewStep();

const receivedRequestPromise = spyOnRequest(CREATE_BLUEPRINT);
await saveBlueprint();
const receivedRequest = await receivedRequestPromise;

const expectedRequest = {
...blueprintRequest,
customizations: {},
};

expect(receivedRequest).toEqual(expectedRequest);
});
});
Loading

0 comments on commit 8c31e6f

Please sign in to comment.