Skip to content

Commit

Permalink
feat(vrack-services): add iam checks on dashboard and listing
Browse files Browse the repository at this point in the history
ref: MANAGER-16523

Signed-off-by: Quentin Pavy <[email protected]>
  • Loading branch information
Quentin Pavy committed Jan 23, 2025
1 parent c8ff2f5 commit caa7c12
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 12 deletions.
21 changes: 21 additions & 0 deletions packages/manager/apps/vrack-services/mocks/iam/iam.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IAM_ACTION } from '../../src/utils/iamActions.constants';

const defaultUrn = 'urn:v1:eu:resource:vrackServices:vrs-ar7-72w-poh-3qe';

export const configureIamResponse = ({
unauthorizedActions = [],
urn,
}: {
unauthorizedActions?: string[];
urn?: string;
}) => {
const allActions = Object.values(IAM_ACTION);
const authorizedActions = allActions.filter(
(action) => !unauthorizedActions.includes(action),
);
return {
urn: urn || defaultUrn,
authorizedActions,
unauthorizedActions,
};
};
16 changes: 15 additions & 1 deletion packages/manager/apps/vrack-services/mocks/iam/iam.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Handler } from '@ovh-ux/manager-core-test-utils';
import { configureIamResponse } from './iam.mock';

export const iamResources = [
{
Expand Down Expand Up @@ -44,9 +45,22 @@ export const iamResources = [

export type GetIamMocksParams = {
iamKo?: boolean;
unauthorizedActions?: string[];
urn?: string;
};

export const getIamMocks = ({ iamKo }: GetIamMocksParams): Handler[] => [
export const getIamMocks = ({
iamKo,
unauthorizedActions,
urn,
}: GetIamMocksParams): Handler[] => [
{
url: '/iam/resource/:urn/authorization/check',
response: () => configureIamResponse({ urn, unauthorizedActions }),
api: 'v2',
method: 'post',
status: 200,
},
{
url: '/iam/resource',
response: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { EditButton } from './EditButton.component';
import { VrackServicesWithIAM, getDisplayName, isEditable } from '@/data';
import { urls } from '@/routes/routes.constants';
import { InfoIcon } from './InfoIcon.component';
import { IAM_ACTION } from '@/utils/iamActions.constants';

export type DisplayNameProps = {
isListing?: boolean;
Expand Down Expand Up @@ -57,6 +58,8 @@ export const DisplayName: React.FC<DisplayNameProps> = ({
navigate(urls.overviewEdit.replace(':id', vs.id));
}}
data-testid="display-name-edit-button"
iamActions={[IAM_ACTION.VRACK_SERVICES_RESOURCE_EDIT]}
urn={vs.iam?.urn}
>
{name}
</EditButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,47 @@
import '@/test-utils/setupTests';
// -----
import React from 'react';
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render } from '@testing-library/react';
import { DisplayName } from '@/components/display-name/DisplayName.component';
import vrackServicesList from '../../../mocks/vrack-services/get-vrack-services.json';
import { VrackServicesWithIAM } from '@/data';
import '@testing-library/jest-dom';
import { configureIamResponse } from '../../../mocks/iam/iam.mock';
import { IAM_ACTION } from '@/utils/iamActions.constants';

const defaultVs = vrackServicesList[0] as VrackServicesWithIAM;

const queryClient = new QueryClient();

const iamActionsMock = vi.fn();

vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => {
const original: typeof import('@ovh-ux/manager-react-components') = await importOriginal();
return {
...original,
useAuthorizationIam: iamActionsMock,
};
});

const renderComponent = ({
isListing,
vs = defaultVs,
}: {
isListing?: boolean;
vs?: VrackServicesWithIAM;
}) => {
return render(<DisplayName isListing={isListing} {...vs} />);
return render(
<QueryClientProvider client={queryClient}>
<DisplayName isListing={isListing} {...vs} />
</QueryClientProvider>,
);
};

describe('DisplayName Component', () => {
it('In listing, should display the display name with link', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { getByText, queryByTestId } = renderComponent({ isListing: true });

expect(queryByTestId('display-name-link')).toBeDefined();
Expand All @@ -31,6 +51,7 @@ describe('DisplayName Component', () => {
});

it('In listing, should display the display name with info icon', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { queryByTestId } = renderComponent({
isListing: true,
vs: vrackServicesList[2] as VrackServicesWithIAM,
Expand All @@ -39,6 +60,7 @@ describe('DisplayName Component', () => {
});

it('In listing, should display the display name with loader', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { queryByTestId } = renderComponent({
isListing: true,
vs: vrackServicesList[3] as VrackServicesWithIAM,
Expand All @@ -47,6 +69,7 @@ describe('DisplayName Component', () => {
});

it('In Dashboard, should display the display name with edit action', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { getByText, queryByTestId } = renderComponent({});

expect(getByText(defaultVs.iam.displayName)).toBeDefined();
Expand All @@ -55,6 +78,19 @@ describe('DisplayName Component', () => {
});

it('In Dashboard, should display the display name with disabled edit action', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { queryByTestId } = renderComponent({
vs: vrackServicesList[2] as VrackServicesWithIAM,
});
expect(queryByTestId('edit-button')).toHaveProperty('disabled');
});

it('In Dashboard, should display the display name with disabled edit action when user have no iam right', async () => {
iamActionsMock.mockReturnValue(
configureIamResponse({
unauthorizedActions: [IAM_ACTION.VRACK_SERVICES_RESOURCE_EDIT],
}),
);
const { queryByTestId } = renderComponent({
vs: vrackServicesList[2] as VrackServicesWithIAM,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { OsdsButton, OsdsIcon, OsdsText } from '@ovhcloud/ods-components/react';
import { OsdsIcon, OsdsText } from '@ovhcloud/ods-components/react';
import {
ODS_BUTTON_SIZE,
ODS_BUTTON_TYPE,
Expand All @@ -10,17 +10,21 @@ import {
ODS_TEXT_LEVEL,
} from '@ovhcloud/ods-components';
import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import { handleClick } from '@ovh-ux/manager-react-components';
import { handleClick, ManagerButton } from '@ovh-ux/manager-react-components';

export type EditButtonProps = React.PropsWithChildren<{
disabled?: boolean;
onClick: () => void;
iamActions?: string[];
urn?: string;
}>;

export const EditButton: React.FC<EditButtonProps> = ({
children,
disabled,
onClick,
iamActions,
urn,
}) => (
<div className="flex items-center">
<div className="grow">
Expand All @@ -33,7 +37,7 @@ export const EditButton: React.FC<EditButtonProps> = ({
</OsdsText>
</div>
<div className="flex-none">
<OsdsButton
<ManagerButton
className="ml-2"
inline
circle
Expand All @@ -44,13 +48,15 @@ export const EditButton: React.FC<EditButtonProps> = ({
{...handleClick(onClick)}
disabled={disabled || undefined}
data-testid="edit-button"
iamActions={iamActions}
urn={urn}
>
<OsdsIcon
color={ODS_THEME_COLOR_INTENT.primary}
name={ODS_ICON_NAME.PEN}
size={ODS_ICON_SIZE.xs}
/>
</OsdsButton>
</ManagerButton>
</div>
</div>
);
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,29 @@ import {
ShellContext,
ShellContextType,
} from '@ovh-ux/manager-react-shell-client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { getButtonByLabel } from '@ovh-ux/manager-core-test-utils';
import { VrackId } from './VrackId.component';
import { VrackServicesWithIAM } from '@/data';
import vrackServicesList from '../../../mocks/vrack-services/get-vrack-services.json';
import { configureIamResponse } from '../../../mocks/iam/iam.mock';
import { IAM_ACTION } from '@/utils/iamActions.constants';

const defaultVs = vrackServicesList[5] as VrackServicesWithIAM;
const vsWithoutVrack = vrackServicesList[0] as VrackServicesWithIAM;

const queryClient = new QueryClient();

const iamActionsMock = vi.fn();

vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => {
const original: typeof import('@ovh-ux/manager-react-components') = await importOriginal();
return {
...original,
useAuthorizationIam: iamActionsMock,
};
});

/** Render */

const shellContext = {
Expand All @@ -41,18 +57,22 @@ const renderComponent = ({
vs: VrackServicesWithIAM;
}) => {
return render(
<ShellContext.Provider
value={(shellContext as unknown) as ShellContextType}
>
<VrackId isListing={isListing} {...vs} />
</ShellContext.Provider>,
<QueryClientProvider client={queryClient}>
<ShellContext.Provider
value={(shellContext as unknown) as ShellContextType}
>
<VrackId isListing={isListing} {...vs} />
</ShellContext.Provider>
,
</QueryClientProvider>,
);
};

/** END RENDER */

describe('VrackId Component', () => {
it('should display link to vrack if associated', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { getByText, queryByText } = renderComponent({ vs: defaultVs });

await waitFor(() => {
Expand All @@ -67,7 +87,23 @@ describe('VrackId Component', () => {
});
});

it('should display disable link to vrack if user has no iam right', async () => {
iamActionsMock.mockReturnValue(
configureIamResponse({
unauthorizedActions: [IAM_ACTION.VRACK_SERVICES_VRACK_ATTACH],
}),
);
const { container } = renderComponent({ vs: defaultVs });

await getButtonByLabel({
container,
label: 'vrackActionAssociateToAnother',
disabled: true,
});
});

it('should display action to link vrack if not associated', async () => {
iamActionsMock.mockReturnValue(configureIamResponse({}));
const { getByText, queryByText } = renderComponent({ vs: vsWithoutVrack });

await waitFor(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { useNavigate } from 'react-router-dom';
import { VrackServicesWithIAM, isEditable } from '@/data';
import { urls } from '@/routes/routes.constants';
import { IAM_ACTION } from '@/utils/iamActions.constants';

export type UseVrackMenuItemsParams = {
vs: VrackServicesWithIAM;
Expand Down Expand Up @@ -44,6 +45,8 @@ export const useVrackMenuItems = ({
),
);
},
iamActions: [IAM_ACTION.VRACK_SERVICES_VRACK_ATTACH],
urn: vs.iam?.urn,
},
vrackId && {
id: 5,
Expand All @@ -65,6 +68,8 @@ export const useVrackMenuItems = ({
.replace(':vrackId', vs.currentState.vrackId),
);
},
iamActions: [IAM_ACTION.VRACK_SERVICES_VRACK_ATTACH],
urn: vs.iam?.urn,
},
vrackId && {
id: 6,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import vrackServicesList from '../../../mocks/vrack-services/get-vrack-services.json';
import { urls } from '@/routes/routes.constants';
import { vrackList } from '../../../mocks/vrack/vrack';
import { IAM_ACTION } from '@/utils/iamActions.constants';

describe('Vrack Services associate vrack test suite', () => {
it('from dashboard should associate vrack using vrack modal', async () => {
Expand Down Expand Up @@ -66,6 +67,27 @@ describe('Vrack Services associate vrack test suite', () => {
await assertModalVisibility({ container, isVisible: false });
});

it('from dashboard should disable vrack association if user have not the iam right to do it', async () => {
const { container } = await renderTest({
initialRoute: urls.overview.replace(':id', vrackServicesList[0].id),
nbVs: 1,
unauthorizedActions: [IAM_ACTION.VRACK_SERVICES_VRACK_ATTACH],
});

const actionMenuButton = await getButtonByIcon({
container,
iconName: ODS_ICON_NAME.ELLIPSIS,
});

await waitFor(() => fireEvent.click(actionMenuButton));

await getButtonByLabel({
container,
label: labels.common.associateVrackButtonLabel,
disabled: true,
});
});

it('from dashboard, should propose user to create vrack if no eligible vrack', async () => {
const { container } = await renderTest({
initialRoute: urls.overviewAssociate.replace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import { isEditable, VrackServicesWithIAM } from '@/data';
import { urls } from '@/routes/routes.constants';
import { useVrackMenuItems } from '@/components/vrack-id/useVrackMenuItems.hook';
import { IAM_ACTION } from '@/utils/iamActions.constants';

export const ActionCell: React.FC<VrackServicesWithIAM> = (vs) => {
const navigate = useNavigate();
Expand Down Expand Up @@ -58,6 +59,8 @@ export const ActionCell: React.FC<VrackServicesWithIAM> = (vs) => {
});
navigate(urls.listingEdit.replace(':id', vs.id));
},
iamActions: [IAM_ACTION.VRACK_SERVICES_RESOURCE_EDIT],
urn: vs.iam?.urn,
},
...vrackActionsMenuItems,
{
Expand Down
Loading

0 comments on commit caa7c12

Please sign in to comment.