mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 18:40:01 -03:30
Adds User TokenAdd Functionality
This commit is contained in:
parent
fb3271da3c
commit
bbc4522063
@ -24,6 +24,7 @@ import Roles from './models/Roles';
|
||||
import Schedules from './models/Schedules';
|
||||
import SystemJobs from './models/SystemJobs';
|
||||
import Teams from './models/Teams';
|
||||
import Tokens from './models/Tokens';
|
||||
import UnifiedJobTemplates from './models/UnifiedJobTemplates';
|
||||
import UnifiedJobs from './models/UnifiedJobs';
|
||||
import Users from './models/Users';
|
||||
@ -58,6 +59,7 @@ const RolesAPI = new Roles();
|
||||
const SchedulesAPI = new Schedules();
|
||||
const SystemJobsAPI = new SystemJobs();
|
||||
const TeamsAPI = new Teams();
|
||||
const TokensAPI = new Tokens();
|
||||
const UnifiedJobTemplatesAPI = new UnifiedJobTemplates();
|
||||
const UnifiedJobsAPI = new UnifiedJobs();
|
||||
const UsersAPI = new Users();
|
||||
@ -93,6 +95,7 @@ export {
|
||||
SchedulesAPI,
|
||||
SystemJobsAPI,
|
||||
TeamsAPI,
|
||||
TokensAPI,
|
||||
UnifiedJobTemplatesAPI,
|
||||
UnifiedJobsAPI,
|
||||
UsersAPI,
|
||||
|
||||
10
awx/ui_next/src/api/models/Tokens.js
Normal file
10
awx/ui_next/src/api/models/Tokens.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Base from '../Base';
|
||||
|
||||
class Tokens extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/tokens/';
|
||||
}
|
||||
}
|
||||
|
||||
export default Tokens;
|
||||
@ -12,6 +12,10 @@ class Users extends Base {
|
||||
});
|
||||
}
|
||||
|
||||
createToken(userId, data) {
|
||||
return this.http.post(`${this.baseUrl}${userId}/authorized_tokens/`, data);
|
||||
}
|
||||
|
||||
disassociateRole(userId, roleId) {
|
||||
return this.http.post(`${this.baseUrl}${userId}/roles/`, {
|
||||
id: roleId,
|
||||
|
||||
106
awx/ui_next/src/components/Lookup/ApplicationLookup.jsx
Normal file
106
awx/ui_next/src/components/Lookup/ApplicationLookup.jsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { func, node } from 'prop-types';
|
||||
import { withRouter, useLocation } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { FormGroup } from '@patternfly/react-core';
|
||||
import { ApplicationsAPI } from '../../api';
|
||||
import { Application } from '../../types';
|
||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
import Lookup from './Lookup';
|
||||
import OptionsList from '../OptionsList';
|
||||
import useRequest from '../../util/useRequest';
|
||||
import LookupErrorMessage from './shared/LookupErrorMessage';
|
||||
|
||||
const QS_CONFIG = getQSConfig('applications', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
});
|
||||
|
||||
function ApplicationLookup({ i18n, onChange, value, label }) {
|
||||
const location = useLocation();
|
||||
const {
|
||||
error,
|
||||
result: { applications, itemCount },
|
||||
request: fetchApplications,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const params = parseQueryString(QS_CONFIG, location.search);
|
||||
|
||||
const {
|
||||
data: { results, count },
|
||||
} = await ApplicationsAPI.read(params);
|
||||
return { applications: results, itemCount: count };
|
||||
}, [location]),
|
||||
{ applications: [], itemCount: 0 }
|
||||
);
|
||||
useEffect(() => {
|
||||
fetchApplications();
|
||||
}, [fetchApplications]);
|
||||
return (
|
||||
<FormGroup fieldId="application" label={label}>
|
||||
<Lookup
|
||||
id="application"
|
||||
header={i18n._(t`Application`)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
qsConfig={QS_CONFIG}
|
||||
renderOptionsList={({ state, dispatch, canDelete }) => (
|
||||
<OptionsList
|
||||
value={state.selectedItems}
|
||||
options={applications}
|
||||
optionCount={itemCount}
|
||||
header={i18n._(t`Applications`)}
|
||||
qsConfig={QS_CONFIG}
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Description`),
|
||||
key: 'description',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created`),
|
||||
key: 'created',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Organization`),
|
||||
key: 'organization',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Description`),
|
||||
key: 'description',
|
||||
},
|
||||
]}
|
||||
readOnly={!canDelete}
|
||||
name="application"
|
||||
selectItem={item => dispatch({ type: 'SELECT_ITEM', item })}
|
||||
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<LookupErrorMessage error={error} />
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
ApplicationLookup.propTypes = {
|
||||
label: node.isRequired,
|
||||
onChange: func.isRequired,
|
||||
value: Application,
|
||||
};
|
||||
|
||||
ApplicationLookup.defaultProps = {
|
||||
value: null,
|
||||
};
|
||||
|
||||
export default withI18n()(withRouter(ApplicationLookup));
|
||||
80
awx/ui_next/src/components/Lookup/ApplicationLookup.test.jsx
Normal file
80
awx/ui_next/src/components/Lookup/ApplicationLookup.test.jsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
import ApplicationLookup from './ApplicationLookup';
|
||||
import { ApplicationsAPI } from '../../api';
|
||||
|
||||
jest.mock('../../api');
|
||||
const application = {
|
||||
id: 1,
|
||||
name: 'app',
|
||||
description: '',
|
||||
};
|
||||
|
||||
const fetchedApplications = {
|
||||
count: 2,
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'app',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'application that should not crach',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
describe('ApplicationLookup', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
ApplicationsAPI.read.mockResolvedValueOnce(fetchedApplications);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('should render successfully', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ApplicationLookup
|
||||
label="Application"
|
||||
value={application}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(wrapper.find('ApplicationLookup')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should fetch applications', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ApplicationLookup
|
||||
label="Application"
|
||||
value={application}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(ApplicationsAPI.read).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should display label', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ApplicationLookup
|
||||
label="Application"
|
||||
value={application}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const title = wrapper.find('FormGroup .pf-c-form__label-text');
|
||||
expect(title.text()).toEqual('Application');
|
||||
});
|
||||
});
|
||||
@ -4,3 +4,4 @@ export { default as InventoryLookup } from './InventoryLookup';
|
||||
export { default as ProjectLookup } from './ProjectLookup';
|
||||
export { default as MultiCredentialsLookup } from './MultiCredentialsLookup';
|
||||
export { default as CredentialLookup } from './CredentialLookup';
|
||||
export { default as ApplicationLookup } from './ApplicationLookup';
|
||||
|
||||
@ -20,7 +20,7 @@ import UserDetail from './UserDetail';
|
||||
import UserEdit from './UserEdit';
|
||||
import UserOrganizations from './UserOrganizations';
|
||||
import UserTeams from './UserTeams';
|
||||
import UserTokenList from './UserTokenList';
|
||||
import UserTokens from './UserTokens';
|
||||
import UserAccessList from './UserAccess/UserAccessList';
|
||||
|
||||
function User({ i18n, setBreadcrumb, me }) {
|
||||
@ -80,7 +80,7 @@ function User({ i18n, setBreadcrumb, me }) {
|
||||
}
|
||||
|
||||
let showCardHeader = true;
|
||||
if (['edit'].some(name => location.pathname.includes(name))) {
|
||||
if (['edit', 'add'].some(name => location.pathname.includes(name))) {
|
||||
showCardHeader = false;
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ function User({ i18n, setBreadcrumb, me }) {
|
||||
</Route>
|
||||
)}
|
||||
<Route path="/users/:id/tokens">
|
||||
<UserTokenList id={Number(match.params.id)} />
|
||||
<UserTokens id={Number(match.params.id)} />
|
||||
</Route>
|
||||
<Route key="not-found" path="*">
|
||||
<ContentError isNotFound>
|
||||
|
||||
42
awx/ui_next/src/screens/User/UserTokenAdd/UserTokenAdd.jsx
Normal file
42
awx/ui_next/src/screens/User/UserTokenAdd/UserTokenAdd.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
|
||||
import { CardBody } from '../../../components/Card';
|
||||
import { TokensAPI, UsersAPI } from '../../../api';
|
||||
import useRequest from '../../../util/useRequest';
|
||||
import UserTokenFrom from '../shared/UserTokenForm';
|
||||
|
||||
function UserTokenAdd() {
|
||||
const history = useHistory();
|
||||
const { id: userId } = useParams();
|
||||
const { error: submitError, request: handleSubmit } = useRequest(
|
||||
useCallback(
|
||||
async formData => {
|
||||
if (formData.application) {
|
||||
formData.application = formData.application?.id || null;
|
||||
await UsersAPI.createToken(userId, formData);
|
||||
} else {
|
||||
await TokensAPI.create(formData);
|
||||
}
|
||||
|
||||
history.push(`/users/${userId}/tokens`);
|
||||
},
|
||||
[history, userId]
|
||||
)
|
||||
);
|
||||
|
||||
const handleCancel = () => {
|
||||
history.push(`/users/${userId}/tokens`);
|
||||
};
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
<UserTokenFrom
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
submitError={submitError}
|
||||
/>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
export default UserTokenAdd;
|
||||
@ -0,0 +1,98 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
} from '../../../../testUtils/enzymeHelpers';
|
||||
import UserTokenAdd from './UserTokenAdd';
|
||||
import { UsersAPI, TokensAPI } from '../../../api';
|
||||
|
||||
jest.mock('../../../api');
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
history: () => ({
|
||||
location: '/user',
|
||||
}),
|
||||
useParams: () => ({ id: 1 }),
|
||||
}));
|
||||
let wrapper;
|
||||
|
||||
describe('<UserTokenAdd />', () => {
|
||||
test('handleSubmit should post to api', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<UserTokenAdd />);
|
||||
});
|
||||
UsersAPI.createToken.mockResolvedValueOnce({ data: { id: 1 } });
|
||||
const tokenData = {
|
||||
application: 1,
|
||||
description: 'foo',
|
||||
scope: 'read',
|
||||
};
|
||||
await act(async () => {
|
||||
wrapper.find('UserTokenForm').prop('handleSubmit')(tokenData);
|
||||
});
|
||||
expect(UsersAPI.createToken).toHaveBeenCalledWith(1, tokenData);
|
||||
});
|
||||
|
||||
test('should navigate to tokens list when cancel is clicked', async () => {
|
||||
const history = createMemoryHistory({});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<UserTokenAdd />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||
});
|
||||
expect(history.location.pathname).toEqual('/users/1/tokens');
|
||||
});
|
||||
|
||||
test('successful form submission should trigger redirect', async () => {
|
||||
const history = createMemoryHistory({});
|
||||
const tokenData = {
|
||||
application: 1,
|
||||
description: 'foo',
|
||||
scope: 'read',
|
||||
};
|
||||
UsersAPI.createToken.mockResolvedValueOnce({
|
||||
data: {
|
||||
id: 2,
|
||||
...tokenData,
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<UserTokenAdd />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'button[aria-label="Save"]');
|
||||
await act(async () => {
|
||||
wrapper.find('UserTokenForm').prop('handleSubmit')(tokenData);
|
||||
});
|
||||
expect(history.location.pathname).toEqual('/users/1/tokens');
|
||||
});
|
||||
|
||||
test('should successful submit form with application', async () => {
|
||||
const history = createMemoryHistory({});
|
||||
const tokenData = {
|
||||
scope: 'read',
|
||||
};
|
||||
TokensAPI.create.mockResolvedValueOnce({
|
||||
data: {
|
||||
id: 2,
|
||||
...tokenData,
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<UserTokenAdd />, {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'button[aria-label="Save"]');
|
||||
await act(async () => {
|
||||
wrapper.find('UserTokenForm').prop('handleSubmit')(tokenData);
|
||||
});
|
||||
expect(history.location.pathname).toEqual('/users/1/tokens');
|
||||
});
|
||||
});
|
||||
1
awx/ui_next/src/screens/User/UserTokenAdd/index.js
Normal file
1
awx/ui_next/src/screens/User/UserTokenAdd/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './UserTokenAdd';
|
||||
@ -40,7 +40,7 @@ function UserTokenListItem({ i18n, token, isSelected, onSelect }) {
|
||||
>
|
||||
{token.summary_fields?.application?.name ? (
|
||||
<span>
|
||||
<NameLabel>{i18n._(t`Application:`)}</NameLabel>
|
||||
<NameLabel>{i18n._(t`Application`)}</NameLabel>
|
||||
{token.summary_fields.application.name}
|
||||
</span>
|
||||
) : (
|
||||
|
||||
@ -53,7 +53,7 @@ describe('<UserTokenListItem />', () => {
|
||||
expect(wrapper.find('DataListCheck').prop('checked')).toBe(false);
|
||||
expect(
|
||||
wrapper.find('PFDataListCell[aria-label="application name"]').text()
|
||||
).toBe('Application:app');
|
||||
).toBe('Applicationapp');
|
||||
expect(wrapper.find('PFDataListCell[aria-label="scope"]').text()).toBe(
|
||||
'ScopeRead'
|
||||
);
|
||||
|
||||
22
awx/ui_next/src/screens/User/UserTokens/UserTokens.jsx
Normal file
22
awx/ui_next/src/screens/User/UserTokens/UserTokens.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { Switch, Route, useParams } from 'react-router-dom';
|
||||
import UserTokenAdd from '../UserTokenAdd';
|
||||
import UserTokenList from '../UserTokenList';
|
||||
|
||||
function UserTokens() {
|
||||
const { id: userId } = useParams();
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route key="add" path="/users/:id/tokens/add">
|
||||
<UserTokenAdd id={Number(userId)} />
|
||||
</Route>
|
||||
<Route key="list" path="/users/:id/tokens">
|
||||
<UserTokenList id={Number(userId)} />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n()(UserTokens);
|
||||
1
awx/ui_next/src/screens/User/UserTokens/index.js
Normal file
1
awx/ui_next/src/screens/User/UserTokens/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './UserTokens';
|
||||
@ -33,6 +33,7 @@ function Users({ i18n }) {
|
||||
[`/users/${user.id}/teams`]: i18n._(t`Teams`),
|
||||
[`/users/${user.id}/organizations`]: i18n._(t`Organizations`),
|
||||
[`/users/${user.id}/tokens`]: i18n._(t`Tokens`),
|
||||
[`/users/${user.id}/tokens/add`]: i18n._(t`Create user token`),
|
||||
});
|
||||
},
|
||||
[i18n]
|
||||
|
||||
127
awx/ui_next/src/screens/User/shared/UserTokenForm.jsx
Normal file
127
awx/ui_next/src/screens/User/shared/UserTokenForm.jsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup';
|
||||
import FormField, {
|
||||
FormSubmitError,
|
||||
FieldTooltip,
|
||||
} from '../../../components/FormField';
|
||||
import ApplicationLookup from '../../../components/Lookup/ApplicationLookup';
|
||||
import { required } from '../../../util/validators';
|
||||
|
||||
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||
|
||||
function UserTokenFormFields({ i18n }) {
|
||||
const [applicationField, applicationMeta, applicationHelpers] = useField(
|
||||
'application'
|
||||
);
|
||||
|
||||
const [scopeField, scopeMeta, scopeHelpers] = useField({
|
||||
name: 'scope',
|
||||
validate: required(i18n._(t`Please enter a value.`), i18n),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup
|
||||
fieldId="application-lookup"
|
||||
name="application"
|
||||
validated={
|
||||
!applicationMeta.touched || !applicationMeta.error
|
||||
? 'default'
|
||||
: 'error'
|
||||
}
|
||||
helperTextInvalid={applicationMeta.error}
|
||||
>
|
||||
<ApplicationLookup
|
||||
value={applicationField.value}
|
||||
onChange={value => {
|
||||
applicationHelpers.setValue(value);
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
{i18n._(t`Application`)}
|
||||
<FieldTooltip
|
||||
content={i18n._(
|
||||
t`Select the application that this token will belong to.`
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
touched={applicationMeta.touched}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormField
|
||||
id="token-description"
|
||||
name="description"
|
||||
type="text"
|
||||
label={i18n._(t`Description`)}
|
||||
/>
|
||||
|
||||
<FormGroup
|
||||
name="scope"
|
||||
fieldId="token-scope"
|
||||
helperTextInvalid={scopeMeta.error}
|
||||
isRequired
|
||||
validated={!scopeMeta.touched || !scopeMeta.error ? 'default' : 'error'}
|
||||
label={i18n._(t`Scope`)}
|
||||
labelIcon={
|
||||
<FieldTooltip
|
||||
content={i18n._(t`Specify a scope for the token's access`)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AnsibleSelect
|
||||
{...scopeField}
|
||||
id="token-scope"
|
||||
data={[
|
||||
{ key: 'default', label: '', value: '' },
|
||||
{ key: 'read', value: 'read', label: i18n._(t`Read`) },
|
||||
{ key: 'write', value: 'write', label: i18n._(t`Write`) },
|
||||
]}
|
||||
onChange={(event, value) => {
|
||||
scopeHelpers.setValue(value);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function UserTokenForm({
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
submitError,
|
||||
i18n,
|
||||
token = {},
|
||||
}) {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
description: token.description || '',
|
||||
application: token.application || null,
|
||||
scope: token.scope || '',
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{formik => (
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<UserTokenFormFields i18n={i18n} />
|
||||
{submitError && <FormSubmitError error={submitError} />}
|
||||
<FormActionGroup
|
||||
onCancel={handleCancel}
|
||||
onSubmit={() => {
|
||||
formik.handleSubmit();
|
||||
}}
|
||||
/>
|
||||
</FormColumnLayout>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
export default withI18n()(UserTokenForm);
|
||||
144
awx/ui_next/src/screens/User/shared/UserTokenForm.test.jsx
Normal file
144
awx/ui_next/src/screens/User/shared/UserTokenForm.test.jsx
Normal file
@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
} from '../../../../testUtils/enzymeHelpers';
|
||||
import UserTokenForm from './UserTokenForm';
|
||||
import { sleep } from '../../../../testUtils/testUtils';
|
||||
import { ApplicationsAPI } from '../../../api';
|
||||
|
||||
jest.mock('../../../api');
|
||||
const applications = {
|
||||
data: {
|
||||
count: 2,
|
||||
results: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'app',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'application that should not crach',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
describe('<UserTokenForm />', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {});
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('initially renders successfully', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<UserTokenForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
||||
);
|
||||
});
|
||||
|
||||
expect(wrapper.find('UserTokenForm').length).toBe(1);
|
||||
});
|
||||
|
||||
test('add form displays all form fields', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<UserTokenForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(wrapper.find('FormGroup[name="application"]').length).toBe(1);
|
||||
expect(wrapper.find('FormField[name="description"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[name="scope"]').length).toBe(1);
|
||||
});
|
||||
|
||||
test('inputs should update form value on change', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<UserTokenForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('ApplicationLookup').invoke('onChange')({
|
||||
id: 1,
|
||||
name: 'application',
|
||||
});
|
||||
wrapper.find('input[name="description"]').simulate('change', {
|
||||
target: { value: 'new Bar', name: 'description' },
|
||||
});
|
||||
wrapper.find('AnsibleSelect[name="scope"]').prop('onChange')({}, 'read');
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ApplicationLookup').prop('value')).toEqual({
|
||||
id: 1,
|
||||
name: 'application',
|
||||
});
|
||||
expect(wrapper.find('input[name="description"]').prop('value')).toBe(
|
||||
'new Bar'
|
||||
);
|
||||
expect(wrapper.find('AnsibleSelect#token-scope').prop('value')).toBe(
|
||||
'read'
|
||||
);
|
||||
});
|
||||
|
||||
test('should call handleSubmit when Submit button is clicked', async () => {
|
||||
ApplicationsAPI.read.mockResolvedValue(applications);
|
||||
const handleSubmit = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<UserTokenForm handleSubmit={handleSubmit} handleCancel={jest.fn()} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
|
||||
await act(async () => {
|
||||
wrapper.find('AnsibleSelect[name="scope"]').prop('onChange')({}, 'read');
|
||||
});
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Save"]').prop('onClick')();
|
||||
});
|
||||
await sleep(1);
|
||||
|
||||
expect(handleSubmit).toBeCalled();
|
||||
});
|
||||
|
||||
test('should call handleCancel when Cancel button is clicked', async () => {
|
||||
const handleCancel = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<UserTokenForm handleSubmit={jest.fn()} handleCancel={handleCancel} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
expect(handleCancel).not.toHaveBeenCalled();
|
||||
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||
expect(handleCancel).toBeCalled();
|
||||
});
|
||||
test('should throw error on submit without scope value', async () => {
|
||||
ApplicationsAPI.read.mockResolvedValue(applications);
|
||||
const handleSubmit = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<UserTokenForm handleSubmit={handleSubmit} handleCancel={jest.fn()} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Save"]').prop('onClick')();
|
||||
});
|
||||
await sleep(1);
|
||||
wrapper.update();
|
||||
expect(
|
||||
wrapper.find('FormGroup[name="scope"]').prop('helperTextInvalid')
|
||||
).toBe('Please enter a value.');
|
||||
expect(handleSubmit).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@ -1,2 +1,3 @@
|
||||
/* eslint-disable-next-line import/prefer-default-export */
|
||||
export { default as UserForm } from './UserForm';
|
||||
export { default as UserTokenForm } from './UserTokenForm';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user