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', role_level: 'notification_admin_role',
}), }),
]); ]);
if (data?.related?.webhook_key) { if (data.webhook_service && data?.related?.webhook_key) {
const { const {
data: { webhook_key }, data: { webhook_key },
} = await JobTemplatesAPI.readWebhookKey(templateId); } = await JobTemplatesAPI.readWebhookKey(templateId);

View File

@@ -40,7 +40,7 @@ import {
import { JobTemplatesAPI, ProjectsAPI } from '@api'; import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect'; import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect'; import PlaybookSelect from './PlaybookSelect';
import WebhookSubForm from './WebhooksSubForm'; import WebhookSubForm from './WebhookSubForm';
const { origin } = document.location; const { origin } = document.location;
@@ -94,10 +94,6 @@ function JobTemplateForm({
); );
const [jobTagsField, , jobTagsHelpers] = useField('job_tags'); const [jobTagsField, , jobTagsHelpers] = useField('job_tags');
const [skipTagsField, , skipTagsHelpers] = useField('skip_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 { const {
request: fetchProject, request: fetchProject,
@@ -189,19 +185,11 @@ function JobTemplateForm({
callbackUrl = `${origin}${path}`; callbackUrl = `${origin}${path}`;
} }
if ( if (instanceGroupLoading || hasProjectLoading) {
instanceGroupLoading ||
hasProjectLoading
// credentialContentLoading
) {
return <ContentLoading />; return <ContentLoading />;
} }
if ( if (instanceGroupError || projectContentError) {
instanceGroupError ||
projectContentError
// credentialContentError
) {
return <ContentError error={contentError} />; return <ContentError error={contentError} />;
} }
@@ -530,23 +518,9 @@ function JobTemplateForm({
</span> </span>
} }
id="wfjt-enabled-webhooks" id="wfjt-enabled-webhooks"
isChecked={ isChecked={enableWebhooks}
Boolean(webhookService[0].value) || enableWebhooks
}
onChange={checked => { onChange={checked => {
setEnableWebhooks(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 <CheckboxField
@@ -624,7 +598,7 @@ JobTemplateForm.defaultProps = {
}; };
const FormikApp = withFormik({ const FormikApp = withFormik({
mapPropsToValues({ template = {} }) { mapPropsToValues({ template = {}, i18n }) {
const { const {
summary_fields = { summary_fields = {
labels: { results: [] }, labels: { results: [] },
@@ -671,8 +645,10 @@ const FormikApp = withFormik({
webhook_service: template.webhook_service || '', webhook_service: template.webhook_service || '',
webhook_url: template?.related?.webhook_receiver webhook_url: template?.related?.webhook_receiver
? `${origin}${template.related.webhook_receiver}` ? `${origin}${template.related.webhook_receiver}`
: '', : i18n._(t`a new webhook url will be generated on save.`).toUpperCase(),
webhook_key: template.webhook_key || '', 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, 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 () => { test('should call handleSubmit when Submit button is clicked', async () => {
const handleSubmit = jest.fn(); const handleSubmit = jest.fn();
let wrapper; 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 { 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 { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { import {
@@ -20,9 +20,8 @@ import { FieldTooltip } from '@components/FormField';
import { JobTemplatesAPI, CredentialTypesAPI } from '@api'; import { JobTemplatesAPI, CredentialTypesAPI } from '@api';
function WebhookSubForm({ i18n, enableWebhooks }) { function WebhookSubForm({ i18n, enableWebhooks }) {
const [contentError, setContentError] = useState(null); const { id, templateType } = useParams();
const jtAddMatch = useRouteMatch('/templates/job_template/add'); const { pathname } = useLocation();
const { id } = useParams();
const { origin } = document.location; const { origin } = document.location;
@@ -32,7 +31,9 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
webhookServiceHelpers, webhookServiceHelpers,
] = useField('webhook_service'); ] = useField('webhook_service');
const [webhookUrlField, , webhookUrlHelpers] = useField('webhook_url'); const [webhookUrlField, webhookUrlMeta, webhookUrlHelpers] = useField(
'webhook_url'
);
const [webhookKeyField, webhookKeyMeta, webhookKeyHelpers] = useField( const [webhookKeyField, webhookKeyMeta, webhookKeyHelpers] = useField(
'webhook_key' 'webhook_key'
); );
@@ -65,17 +66,37 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
loadCredentialType(); loadCredentialType();
}, [loadCredentialType]); }, [loadCredentialType]);
const changeWebhookKey = async () => { useEffect(() => {
try { 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 { const {
data: { webhook_key: key }, data: { webhook_key: key },
} = await JobTemplatesAPI.updateWebhookKey(id); } = await JobTemplatesAPI.updateWebhookKey(id);
webhookKeyHelpers.setValue(key); webhookKeyHelpers.setValue(key);
} catch (err) { }, [webhookKeyHelpers, id])
setContentError(err); );
}
};
const changeWebhookKey = async () => {
await fetchWebhookKey();
};
const isUpdateKeyDisabled =
pathname.endsWith('/add') ||
webhookKeyMeta.initialValue ===
'A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.';
const webhookServiceOptions = [ const webhookServiceOptions = [
{ {
value: '', value: '',
@@ -97,7 +118,7 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
}, },
]; ];
if (error || contentError) { if (error || webhookKeyError) {
return <ContentError error={error} />; return <ContentError error={error} />;
} }
if (isLoading) { if (isLoading) {
@@ -120,7 +141,11 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
onChange={(event, val) => { onChange={(event, val) => {
webhookServiceHelpers.setValue(val); webhookServiceHelpers.setValue(val);
webhookUrlHelpers.setValue( 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 === '') { if (val === webhookServiceMeta.initialValue || val === '') {
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue); webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
@@ -138,53 +163,53 @@ function WebhookSubForm({ i18n, enableWebhooks }) {
}} }}
/> />
</FormGroup> </FormGroup>
{!jtAddMatch && ( <>
<> <FormGroup
<FormGroup type="text"
type="text" fieldId="jt-webhookURL"
fieldId="jt-webhookURL" label={i18n._(t`Webhook URL`)}
label={i18n._(t`Webhook URL`)} name="webhook_url"
name="webhook_url" >
> <FieldTooltip
<FieldTooltip content={i18n._(
content={i18n._( t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`
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 <TextInput
id="t-webhookURL" id="template-webhook_key"
aria-label={i18n._(t`Webhook URL`)}
value={webhookUrlField.value}
isReadOnly isReadOnly
aria-label="wfjt-webhook-key"
value={webhookKeyField.value}
/> />
</FormGroup> <Button
<FormGroup isDisabled={isUpdateKeyDisabled}
label={i18n._(t`Webhook Key`)} variant="tertiary"
fieldId="template-webhook_key" aria-label={i18n._(t`Update webhook key`)}
> onClick={changeWebhookKey}
<FieldTooltip >
content={i18n._( <SyncAltIcon />
t`Webhook services can use this as a shared secret.` </Button>
)} </InputGroup>
/> </FormGroup>
<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>
</>
)}
{credTypeId && ( {credTypeId && (
<CredentialLookup <CredentialLookup
label={i18n._(t`Webhook Credential`)} 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);
});
});