Skip to content

Commit

Permalink
allow Veeam capacity to have decimal:
Browse files Browse the repository at this point in the history
Change validation schema
change useCapacity hook to accept float instead of int
Tests for validation in veeam configuration and in edit modal
Change Input : step to 0.01 and maximum capacity to 1024 (to take into account the binary base of capacity)
  • Loading branch information
JeanMarcMilletScality committed Oct 3, 2024
1 parent 97cd46c commit b572b07
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/react/ui-elements/Veeam/VeeamCapacityFormSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export const VeeamCapacityFormSection = ({
type="number"
size="1/3"
min={1}
max={999}
step={1}
max={1024}
step={0.01}
autoFocus={autoFocusEnabled}
{...register('capacity')}
/>
Expand Down
48 changes: 48 additions & 0 deletions src/react/ui-elements/Veeam/VeeamCapacityModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ describe('VeeamCapacityModal', () => {
);
});
});
it('should validate capacity value correctly : number less than 1', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), { target: { value: '0' } });

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must be larger than or equal to 1/i),
).toBeInTheDocument();
});
});
it('should validate capacity value correctly : number greater than 1024', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), { target: { value: '1025' } });

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must be less than or equal to 1024/i),
).toBeInTheDocument();
});
});
it('should validate capacity value correctly : number with more than 2 decimals', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), {
target: { value: '12.345' },
});

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must have at most 2 decimals/i),
).toBeInTheDocument();
});
});
it('should validate capacity value correctly : number is required', async () => {
fireEvent.click(selectors.editBtn());
fireEvent.change(selectors.capacityInput(), {
target: { value: '' },
});

await waitFor(async () => {
expect(selectors.editModalBtn()).toBeDisabled();
expect(
screen.getByText(/"capacity" must be a number/i),
).toBeInTheDocument();
});
});

it('should display error toast if mutation failed', async () => {
server.use(
Expand Down
13 changes: 12 additions & 1 deletion src/react/ui-elements/Veeam/VeeamCapacityModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ import {
import { getCapacityBytes, useCapacityUnit } from './useCapacityUnit';

const schema = Joi.object({
capacity: Joi.number().required().min(1).max(999).integer(),
capacity: Joi.number()
.required()
.min(1)
.max(1024)
.custom((value, helpers) => {
if (!Number.isInteger(value * 100)) {
return helpers.message({
custom: '"capacity" must have at most 2 decimals',
});
}
return value;
}),
capacityUnit: Joi.string().required(),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('VeeamCapacityOverviewRow', () => {
expect(screen.getByText('Max repository Capacity')).toBeInTheDocument();
});

expect(screen.getByText('100 GiB')).toBeInTheDocument();
expect(screen.getByText('100.00 GiB')).toBeInTheDocument();
});

it('should not render the row if SOSAPI is not enabled', () => {
Expand Down
5 changes: 2 additions & 3 deletions src/react/ui-elements/Veeam/VeeamCapacityOverviewRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@ export const VeeamCapacityOverviewRow = ({
const xml = veeamObject?.Body?.toString();
const regex = /<Capacity>([\s\S]*?)<\/Capacity>/;
const matches = xml?.match(regex);
const capacity = parseInt(
const capacity = parseFloat(
new DOMParser()
?.parseFromString(xml || '', 'application/xml')
?.querySelector('Capacity')?.textContent ||
matches?.[1] ||
'0',
10,
);

if (isSOSAPIEnabled) {
Expand All @@ -60,7 +59,7 @@ export const VeeamCapacityOverviewRow = ({
) : veeamObjectStatus === 'error' ? (
'Error'
) : (
<PrettyBytes bytes={capacity} decimals={0} />
<PrettyBytes bytes={capacity} decimals={2} />
)}
</>
{veeamObjectStatus === 'success' && (
Expand Down
83 changes: 76 additions & 7 deletions src/react/ui-elements/Veeam/VeeamConfiguration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ describe('Veeam Configuration UI', () => {
).toBeInTheDocument();
//expect the immutable backup toogle to be active
expect(screen.getByLabelText('enableImmutableBackup')).toBeEnabled();
// verify the max capacity input is prefilled with 4 GiB
expect(selectors.maxCapacityInput()).toHaveValue(4);
// verify the max capacity input is prefilled with 80% of binary value of clusterCapacity: jestSetupAfterEnv.tsx
expect(selectors.maxCapacityInput()).toHaveValue(3.73);
expect(screen.getByText(/GiB/i)).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeEnabled();
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('Veeam Configuration UI', () => {
accountName: 'Veeam',
application: 'Veeam Backup for Microsoft 365',
bucketName: 'veeam-bucket',
capacityBytes: '4294967296',
capacityBytes: '4005057004',
enableImmutableBackup: false,
});
});
Expand Down Expand Up @@ -190,17 +190,86 @@ describe('Veeam Configuration UI', () => {
expect(selectors.accountNameInput()).toHaveValue('Veeam');
});
});

it('should throw validation error if the max capacity is not integer', async () => {
it('should show validation error if the max capacity is less than 1', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '0');
//V
expect(
screen.getByText(/"capacity" must be larger than or equal to 1/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error and disable continue button if the max capacity is more than 1024', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '1025');
//V
expect(
screen.getByText(/"capacity" must be less than or equal to 1024/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error and disable continue button if the max capacity is not a number', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '4.666');
await userEvent.type(selectors.maxCapacityInput(), 'abc');
//V
expect(
screen.getByText(/"capacity" must be an integer/i),
screen.getByText(/"capacity" must be a number/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error and disable continue button if the max capacity is empty', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
//V
expect(
screen.getByText(/"capacity" must be a number/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
it('should show validation error if max capacity as more than 2 decimal points', async () => {
//S
mockUseAccountsImplementation();
renderVeeamConfigurationForm();
await userEvent.type(selectors.accountNameInput(), 'Veeam');
await userEvent.type(selectors.repositoryInput(), 'veeam-bucket');
//E
await userEvent.clear(selectors.maxCapacityInput());
await userEvent.type(selectors.maxCapacityInput(), '1.123');
//V
expect(
screen.getByText(/"capacity" must have at most 2 decimals/i),
).toBeInTheDocument();
await waitFor(() => {
expect(selectors.continueButton()).toBeDisabled();
});
});
});
13 changes: 12 additions & 1 deletion src/react/ui-elements/Veeam/VeeamConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,18 @@ const schema = Joi.object({
application: Joi.string().required(),
capacity: Joi.when('application', {
is: Joi.equal(VEEAM_BACKUP_REPLICATION_XML_VALUE),
then: Joi.number().required().min(1).max(999).integer(),
then: Joi.number()
.required()
.min(1)
.max(1024)
.custom((value, helpers) => {
if (!Number.isInteger(value * 100)) {
return helpers.message({
custom: '"capacity" must have at most 2 decimals',
});
}
return value;
}),
otherwise: Joi.valid(),
}),
capacityUnit: Joi.when('application', {
Expand Down
8 changes: 5 additions & 3 deletions src/react/ui-elements/Veeam/useCapacityUnit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const useCapacityUnit = (
const pBytesCapacity = prettyBytes(capacity, {
locale: 'en',
binary: true,
maximumFractionDigits: 0,
maximumFractionDigits: 2,
});
const capacityValue = pBytesCapacity.split(' ')[0];
const capacityValue = pBytesCapacity.split(' ')[0].replace(',', '');
const capacityUnit = `${unitChoices[pBytesCapacity.split(' ')[1] as Units]}`;
return { capacityValue, capacityUnit };
};
Expand All @@ -23,5 +23,7 @@ export const getCapacityBytes = (
capacityValue: string,
capacityUnit: string,
) => {
return (parseInt(capacityValue, 10) * parseInt(capacityUnit, 10)).toString();
return Math.round(
parseFloat(capacityValue) * parseFloat(capacityUnit),
).toString();
};

0 comments on commit b572b07

Please sign in to comment.