Hook up Test button on Metadata step in credential plugin wizard

This commit is contained in:
mabashian 2020-06-11 13:23:36 -04:00
parent 4b51c71220
commit 67f46b4d7e
6 changed files with 213 additions and 5 deletions

View File

@ -13,6 +13,8 @@ import CredentialPluginPrompt from './CredentialPluginPrompt';
jest.mock('../../../../../../api/models/Credentials');
jest.mock('../../../../../../api/models/CredentialTypes');
CredentialsAPI.test.mockResolvedValue({});
CredentialsAPI.read.mockResolvedValue({
data: {
count: 3,
@ -234,5 +236,13 @@ describe('<CredentialPluginPrompt />', () => {
wrapper.find('input#credential-secret_version').prop('value')
).toBe('9000');
});
test('clicking Test button makes correct call', async () => {
await act(async () => {
wrapper.find('Button#credential-plugin-test').simulate('click');
});
expect(CredentialsAPI.test).toHaveBeenCalledWith(1, {
metadata: { secret_path: '/foo/bar', secret_version: '9000' },
});
});
});
});

View File

@ -13,6 +13,7 @@ import FormField from '../../../../../../components/FormField';
import { FormFullWidthLayout } from '../../../../../../components/FormLayout';
import useRequest from '../../../../../../util/useRequest';
import { required } from '../../../../../../util/validators';
import { CredentialPluginTestAlert } from '..';
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
margin-left: 10px;
@ -27,6 +28,21 @@ function MetadataStep({ i18n }) {
const [selectedCredential] = useField('credential');
const [inputValues] = useField('inputs');
const {
result: testPluginSuccess,
error: testPluginError,
request: testPluginMetadata,
} = useRequest(
useCallback(
async (credential, metadata) =>
CredentialsAPI.test(credential.id, {
metadata,
}),
[]
),
null
);
const {
result: fields,
error,
@ -65,10 +81,6 @@ function MetadataStep({ i18n }) {
fetchMetadataOptions();
}, [fetchMetadataOptions]);
const testMetadata = () => {
// https://github.com/ansible/awx/issues/7126
};
if (isLoading) {
return <ContentLoading />;
}
@ -143,13 +155,21 @@ function MetadataStep({ i18n }) {
position="right"
>
<TestButton
id="credential-plugin-test"
variant="primary"
type="submit"
onClick={() => testMetadata()}
onClick={() =>
testPluginMetadata(selectedCredential.value, inputValues.value)
}
>
{i18n._(t`Test`)}
</TestButton>
</Tooltip>
<CredentialPluginTestAlert
credentialName={selectedCredential.value.name}
successResponse={testPluginSuccess}
errorResponse={testPluginError}
/>
</>
);
}

View File

@ -0,0 +1,82 @@
import React, { useEffect, useState } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Alert,
AlertActionCloseButton,
AlertGroup,
} from '@patternfly/react-core';
function CredentialPluginTestAlert({
i18n,
credentialName,
successResponse,
errorResponse,
}) {
const [testMessage, setTestMessage] = useState('');
const [testVariant, setTestVariant] = useState(false);
useEffect(() => {
if (errorResponse) {
if (errorResponse?.response?.data?.inputs) {
if (errorResponse.response.data.inputs.startsWith('HTTP')) {
const [
errorCode,
errorStr,
] = errorResponse.response.data.inputs.split('\n');
try {
const errorJSON = JSON.parse(errorStr);
setTestMessage(
`${errorCode}${
errorJSON?.errors[0] ? `: ${errorJSON.errors[0]}` : ''
}`
);
} catch {
setTestMessage(errorResponse.response.data.inputs);
}
} else {
setTestMessage(errorResponse.response.data.inputs);
}
} else {
setTestMessage(
i18n._(
t`Something went wrong with the request to test this credential and metadata.`
)
);
}
setTestVariant('danger');
} else if (successResponse) {
setTestMessage(i18n._(t`Test passed`));
setTestVariant('success');
}
}, [i18n, successResponse, errorResponse]);
return (
<AlertGroup isToast>
{testMessage && testVariant && (
<Alert
title={
<>
<b id="credential-plugin-test-name">{credentialName}</b>
<p id="credential-plugin-test-message">{testMessage}</p>
</>
}
variant={testVariant}
action={
<AlertActionCloseButton
onClose={() => {
setTestMessage(null);
setTestVariant(null);
}}
/>
}
/>
)}
</AlertGroup>
);
}
CredentialPluginTestAlert.propTypes = {};
CredentialPluginTestAlert.defaultProps = {};
export default withI18n()(CredentialPluginTestAlert);

View File

@ -0,0 +1,63 @@
import React from 'react';
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
import CredentialPluginTestAlert from './CredentialPluginTestAlert';
describe('<CredentialPluginTestAlert />', () => {
let wrapper;
afterEach(() => {
wrapper.unmount();
});
test('renders expected content when test is successful', () => {
wrapper = mountWithContexts(
<CredentialPluginTestAlert
credentialName="Foobar"
successResponse={{}}
errorResponse={null}
/>
);
expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar');
expect(wrapper.find('p#credential-plugin-test-message').text()).toBe(
'Test passed'
);
});
test('renders expected content when test fails with the expected return string formatting', () => {
wrapper = mountWithContexts(
<CredentialPluginTestAlert
credentialName="Foobar"
successResponse={null}
errorResponse={{
response: {
data: {
inputs: `HTTP 404
{"errors":["not found"]}
`,
},
},
}}
/>
);
expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar');
expect(wrapper.find('p#credential-plugin-test-message').text()).toBe(
'HTTP 404: not found'
);
});
test('renders expected content when test fails without the expected return string formatting', () => {
wrapper = mountWithContexts(
<CredentialPluginTestAlert
credentialName="Foobar"
successResponse={null}
errorResponse={{
response: {
data: {
inputs: 'usernamee is not present at /secret/foo/bar/baz',
},
},
}}
/>
);
expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar');
expect(wrapper.find('p#credential-plugin-test-message').text()).toBe(
'usernamee is not present at /secret/foo/bar/baz'
);
});
});

View File

@ -39,6 +39,7 @@ export default function useRequest(makeRequest, initialValue) {
async (...args) => {
setIsLoading(true);
if (isMounted.current) {
setResult(initialValue);
setError(null);
}
try {
@ -56,6 +57,7 @@ export default function useRequest(makeRequest, initialValue) {
}
}
},
/* eslint-disable-next-line react-hooks/exhaustive-deps */
[makeRequest]
),
setValue: setResult,

View File

@ -96,6 +96,37 @@ describe('useRequest hooks', () => {
expect(wrapper.find('TestInner').prop('error')).toEqual(error);
});
test('should reset error/result on each request', async () => {
const error = new Error('error');
const makeRequest = throwError => {
if (throwError) {
throw error;
}
return { data: 'foo' };
};
const wrapper = mount(<Test makeRequest={makeRequest} />);
await act(async () => {
wrapper.find('TestInner').invoke('request')(true);
});
wrapper.update();
expect(wrapper.find('TestInner').prop('result')).toEqual({});
expect(wrapper.find('TestInner').prop('error')).toEqual(error);
await act(async () => {
wrapper.find('TestInner').invoke('request')();
});
wrapper.update();
expect(wrapper.find('TestInner').prop('result')).toEqual({ data: 'foo' });
expect(wrapper.find('TestInner').prop('error')).toEqual(null);
await act(async () => {
wrapper.find('TestInner').invoke('request')(true);
});
wrapper.update();
expect(wrapper.find('TestInner').prop('result')).toEqual({});
expect(wrapper.find('TestInner').prop('error')).toEqual(error);
});
test('should not update state after unmount', async () => {
const makeRequest = jest.fn();
let resolve;