this commit add validation to name field in Users step

This commit is contained in:
regexowl 2025-01-20 12:35:48 +01:00 committed by Sanne Raymaekers
parent 5a7ce1efee
commit 30d77faeb5
5 changed files with 78 additions and 5 deletions

View file

@ -41,6 +41,7 @@ import {
useRegistrationValidation,
useHostnameValidation,
useKernelValidation,
useUsersValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
@ -230,6 +231,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const firstBootValidation = useFirstBootValidation();
// Details
const detailsValidation = useDetailsValidation();
// Users
const usersValidation = useUsersValidation();
let startIndex = 1; // default index
if (isEdit) {
@ -462,7 +465,10 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-users"
isHidden={!isUsersEnabled}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={usersValidation.disabledNext}
optional={true}
/>
}
>
<UsersStep />

View file

@ -13,6 +13,7 @@ import {
setUserPasswordByIndex,
setUserSshKeyByIndex,
} from '../../../../../store/wizardSlice';
import { useUsersValidation } from '../../../utilities/useValidation';
import { HookValidatedInput } from '../../../ValidatedTextInput';
const UserInfo = () => {
const dispatch = useAppDispatch();
@ -45,10 +46,7 @@ const UserInfo = () => {
dispatch(setUserSshKeyByIndex({ index: index, sshKey: value }));
};
const stepValidation = {
errors: {},
disabledNext: false,
};
const stepValidation = useUsersValidation();
return (
<>

View file

@ -20,6 +20,10 @@ import {
selectRegistrationType,
selectHostname,
selectKernel,
selectUserNameByIndex,
selectUsers,
selectUserPasswordByIndex,
selectUserSshKeyByIndex,
} from '../../../store/wizardSlice';
import {
getDuplicateMountPoints,
@ -29,6 +33,7 @@ import {
isSnapshotValid,
isHostnameValid,
isKernelNameValid,
isUserNameValid,
} from '../validators';
export type StepValidation = {
@ -173,6 +178,31 @@ export function useKernelValidation(): StepValidation {
return { errors: {}, disabledNext: false };
}
export function useUsersValidation(): StepValidation {
const index = 0;
const userNameSelector = selectUserNameByIndex(index);
const userName = useAppSelector(userNameSelector);
const userPasswordSelector = selectUserPasswordByIndex(index);
const userPassword = useAppSelector(userPasswordSelector);
const userSshKeySelector = selectUserSshKeyByIndex(index);
const userSshKey = useAppSelector(userSshKeySelector);
const users = useAppSelector(selectUsers);
const canProceed =
// Case 1: there is no users
users.length === 0 ||
// Case 2: All fields are empty
(userName === '' && userPassword === '' && userSshKey === '') ||
// Case 3: userName is valid
(userName && isUserNameValid(userName));
return {
errors: {
userName: !isUserNameValid(userName) ? 'Invalid user name' : '',
},
disabledNext: !canProceed,
};
}
export function useDetailsValidation(): StepValidation {
const name = useAppSelector(selectBlueprintName);
const description = useAppSelector(selectBlueprintDescription);

View file

@ -57,6 +57,17 @@ export const isFileSystemConfigValid = (partitions: Partition[]) => {
return duplicates.length === 0;
};
export const isUserNameValid = (userName: string) => {
if (userName === undefined) return false;
const isLengthValid = userName.length <= 32;
const isNotNumericOnly = !/^\d+$/.test(userName);
const isPatternValid = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9_$]$/.test(
userName
);
return isLengthValid && isNotNumericOnly && isPatternValid;
};
export const getDuplicateMountPoints = (partitions: Partition[]): string[] => {
const mountPointSet: Set<string> = new Set();
const duplicates: string[] = [];

View file

@ -72,6 +72,24 @@ const addValidUser = async () => {
await waitFor(() => expect(nextButton).toBeEnabled());
};
const addInvalidUser = async () => {
const user = userEvent.setup();
const addUser = await screen.findByRole('button', { name: /add a user/i });
expect(addUser).toBeEnabled();
await waitFor(() => user.click(addUser));
const enterUserName = screen.getByRole('textbox', {
name: /blueprint user name/i,
});
const nextButton = await getNextButton();
await waitFor(() => user.type(enterUserName, '..'));
await waitFor(() => expect(enterUserName).toHaveValue('..'));
const enterSshKey = await screen.findByRole('textbox', {
name: /public SSH key/i,
});
await waitFor(() => user.type(enterSshKey, 'ssh-rsa d'));
await waitFor(() => expect(nextButton).toBeDisabled());
};
describe('Step Users', () => {
beforeEach(async () => {
vi.clearAllMocks();
@ -145,6 +163,16 @@ describe('Step Users', () => {
expect(receivedRequest).toEqual(expectedRequest);
});
});
test('with invalid name', async () => {
await renderCreateMode();
await goToRegistrationStep();
await clickRegisterLater();
await goToUsersStep();
await addInvalidUser();
const invalidUserMessage = screen.getByText(/invalid user name/i);
await waitFor(() => expect(invalidUserMessage));
});
});
describe('Users edit mode', () => {