adds test for new webhook component

This commit is contained in:
Alex Corey 2020-04-13 15:08:06 -04:00
parent 6e648cf72f
commit 222fecc5f6
5 changed files with 260 additions and 92 deletions

View File

@ -45,7 +45,7 @@ function Template({ i18n, me, setBreadcrumb }) {
role_level: 'notification_admin_role',
}),
]);
if (data?.related?.webhook_key) {
if (data.webhook_service && data?.related?.webhook_key) {
const {
data: { webhook_key },
} = await JobTemplatesAPI.readWebhookKey(templateId);

View File

@ -40,7 +40,7 @@ import {
import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect';
import WebhookSubForm from './WebhooksSubForm';
import WebhookSubForm from './WebhookSubForm';
const { origin } = document.location;
@ -94,10 +94,6 @@ function JobTemplateForm({
);
const [jobTagsField, , jobTagsHelpers] = useField('job_tags');
const [skipTagsField, , skipTagsHelpers] = useField('skip_tags');
const webhookService = useField('webhook_service');
const webhookUrl = useField('webhook_url');
const webhookKey = useField('webhook_key');
const webhookCredential = useField('webhook_credential');
const {
request: fetchProject,
@ -189,19 +185,11 @@ function JobTemplateForm({
callbackUrl = `${origin}${path}`;
}
if (
instanceGroupLoading ||
hasProjectLoading
// credentialContentLoading
) {
if (instanceGroupLoading || hasProjectLoading) {
return <ContentLoading />;
}
if (
instanceGroupError ||
projectContentError
// credentialContentError
) {
if (instanceGroupError || projectContentError) {
return <ContentError error={contentError} />;
}
@ -530,23 +518,9 @@ function JobTemplateForm({
</span>
}
id="wfjt-enabled-webhooks"
isChecked={
Boolean(webhookService[0].value) || enableWebhooks
}
isChecked={enableWebhooks}
onChange={checked => {
setEnableWebhooks(checked);
webhookService[2].setValue(
!checked ? '' : webhookService[1].initialValue
);
webhookUrl[2].setValue(
!checked ? '' : webhookUrl[1].initialValue
);
webhookKey[2].setValue(
!checked ? '' : webhookKey[1].initialValue
);
webhookCredential[2].setValue(
!checked ? null : webhookCredential[1].initialValue
);
}}
/>
<CheckboxField
@ -624,7 +598,7 @@ JobTemplateForm.defaultProps = {
};
const FormikApp = withFormik({
mapPropsToValues({ template = {} }) {
mapPropsToValues({ template = {}, i18n }) {
const {
summary_fields = {
labels: { results: [] },
@ -671,8 +645,10 @@ const FormikApp = withFormik({
webhook_service: template.webhook_service || '',
webhook_url: template?.related?.webhook_receiver
? `${origin}${template.related.webhook_receiver}`
: '',
webhook_key: template.webhook_key || '',
: i18n._(t`a new webhook url will be generated on save.`).toUpperCase(),
webhook_key:
template.webhook_key ||
i18n._(t`a new webhook key will be generated on save.`).toUpperCase(),
webhook_credential: template?.summary_fields?.webhook_credential || null,
};
},

View File

@ -292,6 +292,49 @@ describe('<JobTemplateForm />', () => {
);
});
test('webhooks should render properly, without data', async () => {
let wrapper;
const history = createMemoryHistory({
initialEntries: ['/templates/job_template/1/edit'],
});
await act(async () => {
wrapper = mountWithContexts(
<Route
path="/templates/job_template/:id/edit"
component={() => (
<JobTemplateForm
template={{
...mockData,
webhook_credential: null,
webhook_key: '',
webhook_service: 'github',
related: { webhook_receiver: '' },
}}
handleSubmit={jest.fn()}
handleCancel={jest.fn()}
/>
)}
/>,
{
context: {
router: {
history,
route: {
location: history.location,
match: { params: { id: 1 } },
},
},
},
}
);
});
expect(
wrapper.find('TextInputBase#template-webhook_key').prop('value')
).toBe('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.');
expect(
wrapper.find('Button[aria-label="Update webhook key"]').prop('isDisabled')
).toBe(true);
});
test('should call handleSubmit when Submit button is clicked', async () => {
const handleSubmit = jest.fn();
let wrapper;

View File

@ -1,6 +1,6 @@
import React, { useEffect, useCallback, useState } from 'react';
import React, { useEffect, useCallback } from 'react';
import { SyncAltIcon } from '@patternfly/react-icons';
import { useParams, useRouteMatch } from 'react-router-dom';
import { useParams, useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import {
@ -20,9 +20,8 @@ import { FieldTooltip } from '@components/FormField';
import { JobTemplatesAPI, CredentialTypesAPI } from '@api';
function WebhookSubForm({ i18n, enableWebhooks }) {
const [contentError, setContentError] = useState(null);
const jtAddMatch = useRouteMatch('/templates/job_template/add');
const { id } = useParams();
const { id, templateType } = useParams();
const { pathname } = useLocation();
const { origin } = document.location;
@ -32,7 +31,9 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
webhookServiceHelpers,
] = useField('webhook_service');
const [webhookUrlField, , webhookUrlHelpers] = useField('webhook_url');
const [webhookUrlField, webhookUrlMeta, webhookUrlHelpers] = useField(
'webhook_url'
);
const [webhookKeyField, webhookKeyMeta, webhookKeyHelpers] = useField(
'webhook_key'
);
@ -65,17 +66,37 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
loadCredentialType();
}, [loadCredentialType]);
const changeWebhookKey = async () => {
try {
useEffect(() => {
if (enableWebhooks) {
webhookServiceHelpers.setValue(webhookServiceMeta.initialValue);
webhookUrlHelpers.setValue(webhookUrlMeta.initialValue);
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
webhookCredentialHelpers.setValue(webhookCredentialMeta.initialValue);
} else {
webhookServiceHelpers.setValue('');
webhookUrlHelpers.setValue('');
webhookKeyHelpers.setValue('');
webhookCredentialHelpers.setValue(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enableWebhooks]);
const { request: fetchWebhookKey, error: webhookKeyError } = useRequest(
useCallback(async () => {
const {
data: { webhook_key: key },
} = await JobTemplatesAPI.updateWebhookKey(id);
webhookKeyHelpers.setValue(key);
} catch (err) {
setContentError(err);
}
};
}, [webhookKeyHelpers, id])
);
const changeWebhookKey = async () => {
await fetchWebhookKey();
};
const isUpdateKeyDisabled =
pathname.endsWith('/add') ||
webhookKeyMeta.initialValue ===
'A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.';
const webhookServiceOptions = [
{
value: '',
@ -97,7 +118,7 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
},
];
if (error || contentError) {
if (error || webhookKeyError) {
return <ContentError error={error} />;
}
if (isLoading) {
@ -120,7 +141,11 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
onChange={(event, val) => {
webhookServiceHelpers.setValue(val);
webhookUrlHelpers.setValue(
`${origin}/api/v2/job_templates/${id}/${val}/`
pathname.endsWith('/add')
? i18n
._(t`a new webhook url will be generated on save.`)
.toUpperCase()
: `${origin}/api/v2/${templateType}s/${id}/${val}/`
);
if (val === webhookServiceMeta.initialValue || val === '') {
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
@ -138,53 +163,53 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
}}
/>
</FormGroup>
{!jtAddMatch && (
<>
<FormGroup
type="text"
fieldId="jt-webhookURL"
label={i18n._(t`Webhook URL`)}
name="webhook_url"
>
<FieldTooltip
content={i18n._(
t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`
)}
/>
<>
<FormGroup
type="text"
fieldId="jt-webhookURL"
label={i18n._(t`Webhook URL`)}
name="webhook_url"
>
<FieldTooltip
content={i18n._(
t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`
)}
/>
<TextInput
id="t-webhookURL"
aria-label={i18n._(t`Webhook URL`)}
value={webhookUrlField.value}
isReadOnly
/>
</FormGroup>
<FormGroup
label={i18n._(t`Webhook Key`)}
fieldId="template-webhook_key"
>
<FieldTooltip
content={i18n._(
t`Webhook services can use this as a shared secret.`
)}
/>
<InputGroup>
<TextInput
id="t-webhookURL"
aria-label={i18n._(t`Webhook URL`)}
value={webhookUrlField.value}
id="template-webhook_key"
isReadOnly
aria-label="wfjt-webhook-key"
value={webhookKeyField.value}
/>
</FormGroup>
<FormGroup
label={i18n._(t`Webhook Key`)}
fieldId="template-webhook_key"
>
<FieldTooltip
content={i18n._(
t`Webhook services can use this as a shared secret.`
)}
/>
<InputGroup>
<TextInput
id="template-webhook_key"
isReadOnly
aria-label="wfjt-webhook-key"
value={webhookKeyField.value}
/>
<Button
variant="tertiary"
aria-label={i18n._(t`Update webhook key`)}
onClick={changeWebhookKey}
>
<SyncAltIcon />
</Button>
</InputGroup>
</FormGroup>
</>
)}
<Button
isDisabled={isUpdateKeyDisabled}
variant="tertiary"
aria-label={i18n._(t`Update webhook key`)}
onClick={changeWebhookKey}
>
<SyncAltIcon />
</Button>
</InputGroup>
</FormGroup>
</>
{credTypeId && (
<CredentialLookup
label={i18n._(t`Webhook Credential`)}

View File

@ -0,0 +1,124 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { Route } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { CredentialsAPI } from '@api';
import { Formik } from 'formik';
import WebhookSubForm from './WebhookSubForm';
jest.mock('@api');
describe('<WebhooksSubForm />', () => {
let wrapper;
let history;
const initialValues = {
webhook_url: '/api/v2/job_templates/51/github/',
webhook_credential: { id: 1, name: 'Github credential' },
webhook_service: 'github',
webhook_key: 'webhook key',
};
beforeEach(async () => {
history = createMemoryHistory({
initialEntries: ['templates/job_template/51/edit'],
});
CredentialsAPI.read.mockResolvedValue({
data: { results: [{ id: 12, name: 'Github credential' }] },
});
await act(async () => {
wrapper = mountWithContexts(
<Route path="templates/:templateType/:id/edit">
<Formik initialValues={initialValues}>
<WebhookSubForm enableWebhooks />
</Formik>
</Route>,
{
context: {
router: {
history,
route: {
location: { pathname: 'templates/job_template/51/edit' },
match: { params: { id: 51, templateType: 'job_template' } },
},
},
},
}
);
});
});
afterEach(() => {
jest.clearAllMocks();
});
test('mounts properly', () => {
expect(wrapper.length).toBe(1);
});
test('should render initial values properly', () => {
waitForElement(wrapper, 'Lookup__ChipHolder', el => el.lenth > 0);
expect(wrapper.find('AnsibleSelect').prop('value')).toBe('github');
expect(
wrapper.find('TextInputBase[aria-label="Webhook URL"]').prop('value')
).toContain('/api/v2/job_templates/51/github/');
expect(
wrapper.find('TextInputBase[aria-label="wfjt-webhook-key"]').prop('value')
).toBe('webhook key');
expect(
wrapper
.find('Chip')
.find('span')
.text()
).toBe('Github credential');
});
test('should make other credential type available', async () => {
CredentialsAPI.read.mockResolvedValue({
data: { results: [{ id: 13, name: 'GitLab credential' }] },
});
await act(async () =>
wrapper.find('AnsibleSelect').prop('onChange')({}, 'gitlab')
);
expect(CredentialsAPI.read).toHaveBeenCalledWith({
namespace: 'gitlab_token',
});
wrapper.update();
expect(
wrapper.find('TextInputBase[aria-label="Webhook URL"]').prop('value')
).toContain('/api/v2/job_templates/51/gitlab/');
expect(
wrapper.find('TextInputBase[aria-label="wfjt-webhook-key"]').prop('value')
).toBe('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.');
});
test('should have disabled button to update webhook key', async () => {
let newWrapper;
await act(async () => {
newWrapper = mountWithContexts(
<Route path="templates/:templateType/:id/edit">
<Formik
initialValues={{
...initialValues,
webhook_key: 'A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.',
}}
>
<WebhookSubForm enableWebhooks />
</Formik>
</Route>,
{
context: {
router: {
history,
route: {
location: { pathname: 'templates/job_template/51/edit' },
match: { params: { id: 51, templateType: 'job_template' } },
},
},
},
}
);
});
expect(
newWrapper
.find("Button[aria-label='Update webhook key']")
.prop('isDisabled')
).toBe(true);
});
});