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:
parent
b976fee44b
commit
c4f3ada0eb
@ -7,6 +7,7 @@ import {
|
||||
IAddonMultiSelectorProps,
|
||||
AddonMultiSelector,
|
||||
} from './AddonMultiSelector';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
|
||||
const onChange = vi.fn();
|
||||
const onFocus = vi.fn();
|
||||
@ -24,10 +25,13 @@ const mockProps: IAddonMultiSelectorProps = {
|
||||
entityName: 'project',
|
||||
};
|
||||
|
||||
const server = testServerSetup();
|
||||
|
||||
describe('AddonMultiSelector', () => {
|
||||
beforeEach(() => {
|
||||
onChange.mockClear();
|
||||
onFocus.mockClear();
|
||||
testServerRoute(server, '/api/admin/ui-config', {});
|
||||
});
|
||||
|
||||
it('renders with default state', () => {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
ISelectProjectInputProps,
|
||||
SelectProjectInput,
|
||||
} from './SelectProjectInput';
|
||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||
|
||||
const onChange = vi.fn();
|
||||
const onFocus = vi.fn();
|
||||
@ -22,10 +23,13 @@ const mockProps: ISelectProjectInputProps = {
|
||||
onFocus,
|
||||
};
|
||||
|
||||
const server = testServerSetup();
|
||||
|
||||
describe('SelectProjectInput', () => {
|
||||
beforeEach(() => {
|
||||
onChange.mockClear();
|
||||
onFocus.mockClear();
|
||||
testServerRoute(server, '/api/admin/ui-config', {});
|
||||
});
|
||||
|
||||
it('renders with default state', () => {
|
||||
|
288
frontend/src/component/changeRequest/ChangeRequest.test.tsx
Normal file
288
frontend/src/component/changeRequest/ChangeRequest.test.tsx
Normal 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');
|
||||
});
|
@ -224,6 +224,7 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
||||
>
|
||||
{feature.changes.map((change, index) => (
|
||||
<Change
|
||||
key={index}
|
||||
onDiscard={onDiscard(change.id)}
|
||||
index={index}
|
||||
changeRequest={changeRequest}
|
||||
|
@ -11,7 +11,7 @@ export const UpdateEnabledMessage = ({
|
||||
featureName,
|
||||
environment,
|
||||
}: UpdateEnabledMsg) => (
|
||||
<Typography>
|
||||
<Typography data-testid="update-enabled-message">
|
||||
<strong>{enabled ? 'Enable' : 'Disable'}</strong> feature toggle{' '}
|
||||
<strong>{featureName}</strong> in <strong>{environment}</strong>
|
||||
</Typography>
|
||||
|
@ -5,6 +5,13 @@ import { screen } from '@testing-library/react';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import { INSTANCE_STATUS_BAR_ID } from 'utils/testIds';
|
||||
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 () => {
|
||||
render(<InstanceStatusBar instanceStatus={UNKNOWN_INSTANCE_STATUS} />);
|
||||
|
@ -44,6 +44,7 @@ const PermissionSwitch = React.forwardRef<
|
||||
<TooltipResolver title={formatAccessText(access, tooltip)} arrow>
|
||||
<span data-loading>
|
||||
<Switch
|
||||
data-testid="toggle-switch"
|
||||
onChange={onChange}
|
||||
disabled={disabled || !access}
|
||||
checked={checked}
|
||||
|
@ -70,7 +70,7 @@ const FeatureOverviewEnvSwitches = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledContainer data-testid="feature-toggle-status">
|
||||
<StyledHeader data-loading>
|
||||
Feature toggle status
|
||||
<HelpIcon
|
||||
|
@ -9,6 +9,7 @@ import { render } from 'utils/testRenderer';
|
||||
const server = testServerSetup();
|
||||
|
||||
test('should render password auth', async () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {});
|
||||
testServerRoute(server, '/api/admin/user', {});
|
||||
testServerRoute(server, '/auth/reset/validate', {
|
||||
name: INVALID_TOKEN_ERROR,
|
||||
|
@ -1,20 +1,21 @@
|
||||
import useSWR from 'swr';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IChangeRequestEnvironmentConfig } from 'component/changeRequest/changeRequest.types';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
|
||||
|
||||
export const useChangeRequestConfig = (projectId: string) => {
|
||||
const { isOss } = useUiConfig();
|
||||
const { data, error, mutate } = useSWR<IChangeRequestEnvironmentConfig[]>(
|
||||
const { data, error, mutate } = useEnterpriseSWR<
|
||||
IChangeRequestEnvironmentConfig[]
|
||||
>(
|
||||
formatApiPath(`api/admin/projects/${projectId}/change-requests/config`),
|
||||
(path: string) => (isOss() ? Promise.resolve([]) : fetcher(path))
|
||||
fetcher,
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
data: data || [],
|
||||
loading: !error && !data,
|
||||
refetchChangeRequestConfig: () => mutate(),
|
||||
refetchChangeRequestConfig: mutate,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
@ -1,8 +1,7 @@
|
||||
import useSWR from 'swr';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
@ -11,10 +10,10 @@ const fetcher = (path: string) => {
|
||||
};
|
||||
|
||||
export const usePendingChangeRequests = (project: string) => {
|
||||
const { isOss } = useUiConfig();
|
||||
const { data, error, mutate } = useSWR<IChangeRequest[]>(
|
||||
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
||||
formatApiPath(`api/admin/projects/${project}/change-requests/pending`),
|
||||
(path: string) => (isOss() ? Promise.resolve([]) : fetcher(path))
|
||||
fetcher,
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import useSWR from 'swr';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IChangeRequest } from 'component/changeRequest/changeRequest.types';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
@ -14,12 +13,12 @@ export const usePendingChangeRequestsForFeature = (
|
||||
project: string,
|
||||
featureName: string
|
||||
) => {
|
||||
const { isOss } = useUiConfig();
|
||||
const { data, error, mutate } = useSWR<IChangeRequest[]>(
|
||||
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
||||
formatApiPath(
|
||||
`api/admin/projects/${project}/change-requests/pending/${featureName}`
|
||||
),
|
||||
(path: string) => (isOss() ? Promise.resolve([]) : fetcher(path))
|
||||
fetcher,
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user