1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-04 00:18:40 +01:00

POC: integration tests (#2422)

This commit is contained in:
Mateusz Kwasniewski 2022-12-06 15:28:33 +01:00 committed by GitHub
parent b976fee44b
commit c4f3ada0eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 357 additions and 18 deletions

View File

@ -7,6 +7,7 @@ import {
IAddonMultiSelectorProps, IAddonMultiSelectorProps,
AddonMultiSelector, AddonMultiSelector,
} from './AddonMultiSelector'; } from './AddonMultiSelector';
import { testServerRoute, testServerSetup } from 'utils/testServer';
const onChange = vi.fn(); const onChange = vi.fn();
const onFocus = vi.fn(); const onFocus = vi.fn();
@ -24,10 +25,13 @@ const mockProps: IAddonMultiSelectorProps = {
entityName: 'project', entityName: 'project',
}; };
const server = testServerSetup();
describe('AddonMultiSelector', () => { describe('AddonMultiSelector', () => {
beforeEach(() => { beforeEach(() => {
onChange.mockClear(); onChange.mockClear();
onFocus.mockClear(); onFocus.mockClear();
testServerRoute(server, '/api/admin/ui-config', {});
}); });
it('renders with default state', () => { it('renders with default state', () => {

View File

@ -7,6 +7,7 @@ import {
ISelectProjectInputProps, ISelectProjectInputProps,
SelectProjectInput, SelectProjectInput,
} from './SelectProjectInput'; } from './SelectProjectInput';
import { testServerRoute, testServerSetup } from 'utils/testServer';
const onChange = vi.fn(); const onChange = vi.fn();
const onFocus = vi.fn(); const onFocus = vi.fn();
@ -22,10 +23,13 @@ const mockProps: ISelectProjectInputProps = {
onFocus, onFocus,
}; };
const server = testServerSetup();
describe('SelectProjectInput', () => { describe('SelectProjectInput', () => {
beforeEach(() => { beforeEach(() => {
onChange.mockClear(); onChange.mockClear();
onFocus.mockClear(); onFocus.mockClear();
testServerRoute(server, '/api/admin/ui-config', {});
}); });
it('renders with default state', () => { it('renders with default state', () => {

View File

@ -0,0 +1,288 @@
import {
render,
screen,
waitFor,
within,
getAllByRole,
fireEvent,
} from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import { FeatureView } from '../feature/FeatureView/FeatureView';
import { ThemeProvider } from 'themes/ThemeProvider';
import { AccessProvider } from '../providers/AccessProvider/AccessProvider';
import { AnnouncerProvider } from '../common/Announcer/AnnouncerProvider/AnnouncerProvider';
import { testServerRoute, testServerSetup } from '../../utils/testServer';
import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer';
import { FC } from 'react';
const server = testServerSetup();
const pendingChangeRequest = (featureName: string) =>
testServerRoute(
server,
'api/admin/projects/default/change-requests/pending',
[
{
id: 156,
environment: 'production',
state: 'Draft',
minApprovals: 1,
project: 'default',
createdBy: {
id: 1,
username: 'admin',
imageUrl:
'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
},
createdAt: '2022-12-02T09:19:12.242Z',
features: [
{
name: featureName,
changes: [
{
id: 292,
action: 'addStrategy',
payload: {
name: 'default',
segments: [],
parameters: {},
constraints: [],
},
createdAt: '2022-12-02T09:19:12.245Z',
createdBy: {
id: 1,
username: 'admin',
imageUrl:
'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
},
},
],
},
],
approvals: [],
comments: [],
},
]
);
const changeRequestsEnabledIn = (env: string) =>
testServerRoute(
server,
'/api/admin/projects/default/change-requests/config',
[
{
environment: 'development',
type: 'development',
changeRequestEnabled: env === 'development',
},
{
environment: 'production',
type: 'production',
changeRequestEnabled: env === 'production',
},
]
);
const uiConfigForEnterprise = () =>
testServerRoute(server, '/api/admin/ui-config', {
environment: 'Open Source',
flags: {
changeRequests: true,
},
slogan: 'getunleash.io - All rights reserved',
name: 'Unleash enterprise',
links: [
{
value: 'Documentation',
icon: 'library_books',
href: 'https://docs.getunleash.io/docs',
title: 'User documentation',
},
{
value: 'GitHub',
icon: 'c_github',
href: 'https://github.com/Unleash/unleash',
title: 'Source code on GitHub',
},
],
version: '4.18.0-beta.5',
emailEnabled: false,
unleashUrl: 'http://localhost:4242',
baseUriPath: '',
authenticationType: 'enterprise',
segmentValuesLimit: 100,
strategySegmentsLimit: 5,
frontendApiOrigins: ['*'],
versionInfo: {
current: { oss: '4.18.0-beta.5', enterprise: '4.17.0-beta.1' },
latest: {},
isLatest: true,
instanceId: 'c7566052-15d7-4e09-9625-9c988e1f2be7',
},
disablePasswordAuth: false,
});
const featureList = (featureName: string) =>
testServerRoute(server, '/api/admin/projects/default', {
name: 'Default',
description: 'Default project',
health: 100,
updatedAt: '2022-11-14T10:15:59.228Z',
environments: ['development', 'production'],
features: [
{
type: 'release',
name: featureName,
createdAt: '2022-11-14T08:16:33.338Z',
lastSeenAt: null,
stale: false,
environments: [
{
name: 'development',
enabled: false,
type: 'development',
sortOrder: 100,
},
{
name: 'production',
enabled: false,
type: 'production',
sortOrder: 200,
},
],
},
],
members: 0,
version: 1,
});
const feature = ({ name, enabled }: { name: string; enabled: boolean }) =>
testServerRoute(server, `/api/admin/projects/default/features/${name}`, {
environments: [
{
name: 'development',
enabled: false,
type: 'development',
sortOrder: 100,
strategies: [],
},
{
name: 'production',
enabled,
type: 'production',
sortOrder: 200,
strategies: [],
},
],
name,
impressionData: false,
description: '',
project: 'default',
stale: false,
variants: [],
createdAt: '2022-11-14T08:16:33.338Z',
lastSeenAt: null,
type: 'release',
archived: false,
});
const otherRequests = (feature: string) => {
testServerRoute(server, `api/admin/client-metrics/features/${feature}`, {
version: 1,
maturity: 'stable',
featureName: feature,
lastHourUsage: [],
seenApplications: [],
});
testServerRoute(server, `api/admin/features/${feature}/tags`, {
version: 1,
tags: [],
});
testServerRoute(server, 'api/admin/user', {
user: {
isAPI: false,
id: 17,
name: 'Some User',
email: 'user@example.com',
imageUrl:
'https://gravatar.com/avatar/8aa1132e102345f8c79322340e15340?size=42&default=retro',
seenAt: '2022-11-28T14:55:18.982Z',
loginAttempts: 0,
createdAt: '2022-11-23T13:31:17.061Z',
},
permissions: [{ permission: 'ADMIN' }],
feedback: [],
splash: {},
});
};
const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({
children,
path,
pathTemplate,
}) => (
<UIProviderContainer>
<AccessProvider>
<MemoryRouter initialEntries={[path]}>
<ThemeProvider>
<AnnouncerProvider>
<Routes>
<Route path={pathTemplate} element={children} />
</Routes>
</AnnouncerProvider>
</ThemeProvider>
</MemoryRouter>
</AccessProvider>
</UIProviderContainer>
);
const setupHttpRoutes = ({
featureName,
enabled,
}: {
featureName: string;
enabled: boolean;
}) => {
pendingChangeRequest(featureName);
changeRequestsEnabledIn('production');
uiConfigForEnterprise();
featureList(featureName);
feature({ name: featureName, enabled });
otherRequests(featureName);
};
const verifyBannerForPendingChangeRequest = async () => {
return screen.findByText('Change request mode', {}, { timeout: 5000 });
};
const changeToggle = async (environment: string) => {
const featureToggleStatusBox = screen.getByTestId('feature-toggle-status');
await within(featureToggleStatusBox).findByText(environment);
const toggle = screen.getAllByRole('checkbox')[1];
fireEvent.click(toggle);
};
const verifyChangeRequestDialog = async (bannerMainText: string) => {
await screen.findByText('Your suggestion:');
const message = screen.getByTestId('update-enabled-message').textContent;
expect(message).toBe(bannerMainText);
};
test('add toggle change to pending change request', async () => {
setupHttpRoutes({ featureName: 'test', enabled: false });
render(
<UnleashUiSetup
pathTemplate="/projects/:projectId/features/:featureId/*"
path="/projects/default/features/test"
>
<FeatureView />
</UnleashUiSetup>
);
await verifyBannerForPendingChangeRequest();
await changeToggle('production');
await verifyChangeRequestDialog('Enable feature toggle test in production');
});

View File

@ -224,6 +224,7 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
> >
{feature.changes.map((change, index) => ( {feature.changes.map((change, index) => (
<Change <Change
key={index}
onDiscard={onDiscard(change.id)} onDiscard={onDiscard(change.id)}
index={index} index={index}
changeRequest={changeRequest} changeRequest={changeRequest}

View File

@ -11,7 +11,7 @@ export const UpdateEnabledMessage = ({
featureName, featureName,
environment, environment,
}: UpdateEnabledMsg) => ( }: UpdateEnabledMsg) => (
<Typography> <Typography data-testid="update-enabled-message">
<strong>{enabled ? 'Enable' : 'Disable'}</strong> feature toggle{' '} <strong>{enabled ? 'Enable' : 'Disable'}</strong> feature toggle{' '}
<strong>{featureName}</strong> in <strong>{environment}</strong> <strong>{featureName}</strong> in <strong>{environment}</strong>
</Typography> </Typography>

View File

@ -5,6 +5,13 @@ import { screen } from '@testing-library/react';
import { addDays, subDays } from 'date-fns'; import { addDays, subDays } from 'date-fns';
import { INSTANCE_STATUS_BAR_ID } from 'utils/testIds'; import { INSTANCE_STATUS_BAR_ID } from 'utils/testIds';
import { UNKNOWN_INSTANCE_STATUS } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; import { UNKNOWN_INSTANCE_STATUS } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
import { testServerRoute, testServerSetup } from 'utils/testServer';
const server = testServerSetup();
beforeEach(() => {
testServerRoute(server, '/api/admin/ui-config', {});
});
test('InstanceStatusBar should be hidden by default', async () => { test('InstanceStatusBar should be hidden by default', async () => {
render(<InstanceStatusBar instanceStatus={UNKNOWN_INSTANCE_STATUS} />); render(<InstanceStatusBar instanceStatus={UNKNOWN_INSTANCE_STATUS} />);

View File

@ -44,6 +44,7 @@ const PermissionSwitch = React.forwardRef<
<TooltipResolver title={formatAccessText(access, tooltip)} arrow> <TooltipResolver title={formatAccessText(access, tooltip)} arrow>
<span data-loading> <span data-loading>
<Switch <Switch
data-testid="toggle-switch"
onChange={onChange} onChange={onChange}
disabled={disabled || !access} disabled={disabled || !access}
checked={checked} checked={checked}

View File

@ -70,7 +70,7 @@ const FeatureOverviewEnvSwitches = () => {
}; };
return ( return (
<StyledContainer> <StyledContainer data-testid="feature-toggle-status">
<StyledHeader data-loading> <StyledHeader data-loading>
Feature toggle status Feature toggle status
<HelpIcon <HelpIcon

View File

@ -9,6 +9,7 @@ import { render } from 'utils/testRenderer';
const server = testServerSetup(); const server = testServerSetup();
test('should render password auth', async () => { test('should render password auth', async () => {
testServerRoute(server, '/api/admin/ui-config', {});
testServerRoute(server, '/api/admin/user', {}); testServerRoute(server, '/api/admin/user', {});
testServerRoute(server, '/auth/reset/validate', { testServerRoute(server, '/auth/reset/validate', {
name: INVALID_TOKEN_ERROR, name: INVALID_TOKEN_ERROR,

View File

@ -1,20 +1,21 @@
import useSWR from 'swr';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { IChangeRequestEnvironmentConfig } from 'component/changeRequest/changeRequest.types'; import { IChangeRequestEnvironmentConfig } from 'component/changeRequest/changeRequest.types';
import useUiConfig from '../useUiConfig/useUiConfig'; import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
export const useChangeRequestConfig = (projectId: string) => { export const useChangeRequestConfig = (projectId: string) => {
const { isOss } = useUiConfig(); const { data, error, mutate } = useEnterpriseSWR<
const { data, error, mutate } = useSWR<IChangeRequestEnvironmentConfig[]>( IChangeRequestEnvironmentConfig[]
>(
formatApiPath(`api/admin/projects/${projectId}/change-requests/config`), formatApiPath(`api/admin/projects/${projectId}/change-requests/config`),
(path: string) => (isOss() ? Promise.resolve([]) : fetcher(path)) fetcher,
[]
); );
return { return {
data: data || [], data: data || [],
loading: !error && !data, loading: !error && !data,
refetchChangeRequestConfig: () => mutate(), refetchChangeRequestConfig: mutate,
error, error,
}; };
}; };

View File

@ -0,0 +1,34 @@
import useSWR, { BareFetcher, Key, SWRResponse } from 'swr';
import { useEffect } from 'react';
import useUiConfig from '../useUiConfig/useUiConfig';
export const useConditionalSWR = <Data = any, Error = any, T = boolean>(
key: Key,
fetcher: BareFetcher<Data>,
condition: T
): SWRResponse<Data, Error> => {
const result = useSWR(key, fetcher);
useEffect(() => {
result.mutate();
}, [condition]);
return result;
};
export const useEnterpriseSWR = <Data = any, Error = any>(
key: Key,
fetcher: BareFetcher<Data>,
fallback: Data
) => {
const { isEnterprise } = useUiConfig();
const result = useConditionalSWR(
key,
(path: string) =>
isEnterprise() ? fetcher(path) : Promise.resolve(fallback),
isEnterprise()
);
return result;
};

View File

@ -1,8 +1,7 @@
import useSWR from 'swr';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { IChangeRequest } from 'component/changeRequest/changeRequest.types'; import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
import useUiConfig from '../useUiConfig/useUiConfig'; import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
const fetcher = (path: string) => { const fetcher = (path: string) => {
return fetch(path) return fetch(path)
@ -11,10 +10,10 @@ const fetcher = (path: string) => {
}; };
export const usePendingChangeRequests = (project: string) => { export const usePendingChangeRequests = (project: string) => {
const { isOss } = useUiConfig(); const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
const { data, error, mutate } = useSWR<IChangeRequest[]>(
formatApiPath(`api/admin/projects/${project}/change-requests/pending`), formatApiPath(`api/admin/projects/${project}/change-requests/pending`),
(path: string) => (isOss() ? Promise.resolve([]) : fetcher(path)) fetcher,
[]
); );
return { return {

View File

@ -1,8 +1,7 @@
import useSWR from 'swr';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { IChangeRequest } from 'component/changeRequest/changeRequest.types'; import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
import useUiConfig from '../useUiConfig/useUiConfig'; import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
const fetcher = (path: string) => { const fetcher = (path: string) => {
return fetch(path) return fetch(path)
@ -14,12 +13,12 @@ export const usePendingChangeRequestsForFeature = (
project: string, project: string,
featureName: string featureName: string
) => { ) => {
const { isOss } = useUiConfig(); const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
const { data, error, mutate } = useSWR<IChangeRequest[]>(
formatApiPath( formatApiPath(
`api/admin/projects/${project}/change-requests/pending/${featureName}` `api/admin/projects/${project}/change-requests/pending/${featureName}`
), ),
(path: string) => (isOss() ? Promise.resolve([]) : fetcher(path)) fetcher,
[]
); );
return { return {