Adds test coverage for cred plugin prompt

This commit is contained in:
mabashian 2020-05-26 14:47:36 -04:00
parent df069f3874
commit 5367bc4d3b
7 changed files with 406 additions and 22 deletions

View File

@ -72,7 +72,7 @@ function CredentialPluginField(props) {
)}
{showPluginWizard && (
<CredentialPluginPrompt
initialValues={field.value}
initialValues={typeof field.value === 'object' ? field.value : {}}
onClose={() => setShowPluginWizard(false)}
onSubmit={val => {
val.touched = true;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { func } from 'prop-types';
import { func, shape } from 'prop-types';
import { Formik, useField } from 'formik';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@ -14,6 +14,7 @@ function CredentialPluginWizard({ i18n, handleSubmit, onClose }) {
id: 1,
name: i18n._(t`Credential`),
component: <CredentialsStep />,
enableNext: !!selectedCredential.value,
},
{
id: 2,
@ -58,8 +59,11 @@ function CredentialPluginPrompt({ i18n, onClose, onSubmit, initialValues }) {
CredentialPluginPrompt.propTypes = {
onClose: func.isRequired,
onSubmit: func.isRequired,
initialValues: shape({}),
};
CredentialPluginPrompt.defaultProps = {};
CredentialPluginPrompt.defaultProps = {
initialValues: {},
};
export default withI18n()(CredentialPluginPrompt);

View File

@ -1,18 +1,228 @@
import React from 'react';
import { mountWithContexts } from '../../../../../../testUtils/enzymeHelpers';
import { act } from 'react-dom/test-utils';
import {
mountWithContexts,
waitForElement,
} from '../../../../../../testUtils/enzymeHelpers';
import { CredentialsAPI, CredentialTypesAPI } from '../../../../../api';
import selectedCredential from '../../data.cyberArkCredential.json';
import azureVaultCredential from '../../data.azureVaultCredential.json';
import hashiCorpCredential from '../../data.hashiCorpCredential.json';
import CredentialPluginPrompt from './CredentialPluginPrompt';
jest.mock('../../../../../api/models/Credentials');
jest.mock('../../../../../api/models/CredentialTypes');
CredentialsAPI.read.mockResolvedValue({
data: {
count: 3,
results: [selectedCredential, azureVaultCredential, hashiCorpCredential],
},
});
CredentialTypesAPI.readDetail.mockResolvedValue({
data: {
id: 20,
type: 'credential_type',
url: '/api/v2/credential_types/20/',
related: {
named_url:
'/api/v2/credential_types/CyberArk Conjur Secret Lookup+external/',
credentials: '/api/v2/credential_types/20/credentials/',
activity_stream: '/api/v2/credential_types/20/activity_stream/',
},
summary_fields: { user_capabilities: { edit: false, delete: false } },
created: '2020-05-18T21:53:35.398260Z',
modified: '2020-05-18T21:54:05.451444Z',
name: 'CyberArk Conjur Secret Lookup',
description: '',
kind: 'external',
namespace: 'conjur',
managed_by_tower: true,
inputs: {
fields: [
{ id: 'url', label: 'Conjur URL', type: 'string', format: 'url' },
{ id: 'api_key', label: 'API Key', type: 'string', secret: true },
{ id: 'account', label: 'Account', type: 'string' },
{ id: 'username', label: 'Username', type: 'string' },
{
id: 'cacert',
label: 'Public Key Certificate',
type: 'string',
multiline: true,
},
],
metadata: [
{
id: 'secret_path',
label: 'Secret Identifier',
type: 'string',
help_text: 'The identifier for the secret e.g., /some/identifier',
},
{
id: 'secret_version',
label: 'Secret Version',
type: 'string',
help_text:
'Used to specify a specific secret version (if left empty, the latest version will be used).',
},
],
required: ['url', 'api_key', 'account', 'username'],
},
injectors: {},
},
});
describe('<CredentialPluginPrompt />', () => {
let wrapper;
beforeAll(() => {
wrapper = mountWithContexts(
<CredentialPluginPrompt onClose={jest.fn()} onSubmit={jest.fn()} />
);
describe('Plugin not configured', () => {
let wrapper;
const onClose = jest.fn();
const onSubmit = jest.fn();
beforeAll(async () => {
await act(async () => {
wrapper = mountWithContexts(
<CredentialPluginPrompt onClose={onClose} onSubmit={onSubmit} />
);
});
});
afterAll(() => {
wrapper.unmount();
});
test('should render Wizard with all steps', async () => {
const wizard = await waitForElement(wrapper, 'Wizard');
const steps = wizard.prop('steps');
expect(steps).toHaveLength(2);
expect(steps[0].name).toEqual('Credential');
expect(steps[1].name).toEqual('Metadata');
});
test('credentials step renders correctly', () => {
expect(wrapper.find('CredentialsStep').length).toBe(1);
expect(wrapper.find('DataListItem').length).toBe(3);
expect(
wrapper.find('Radio').filterWhere(radio => radio.isChecked).length
).toBe(0);
});
test('next button disabled until credential selected', () => {
expect(wrapper.find('Button[children="Next"]').prop('isDisabled')).toBe(
true
);
});
test('clicking cancel button calls correct function', () => {
wrapper.find('Button[children="Cancel"]').simulate('click');
expect(onClose).toHaveBeenCalledTimes(1);
});
test('clicking credential row enables next button', async () => {
await act(async () => {
wrapper
.find('Radio')
.at(0)
.invoke('onChange')(true);
});
wrapper.update();
expect(
wrapper
.find('Radio')
.at(0)
.prop('isChecked')
).toBe(true);
expect(wrapper.find('Button[children="Next"]').prop('isDisabled')).toBe(
false
);
});
test('clicking next button shows metatdata step', async () => {
await act(async () => {
wrapper.find('Button[children="Next"]').simulate('click');
});
wrapper.update();
expect(wrapper.find('MetadataStep').length).toBe(1);
expect(wrapper.find('FormField').length).toBe(2);
});
test('submit button calls correct function with parameters', async () => {
await act(async () => {
wrapper.find('input#credential-secret_path').simulate('change', {
target: { value: '/foo/bar', name: 'secret_path' },
});
});
await act(async () => {
wrapper.find('input#credential-secret_version').simulate('change', {
target: { value: '9000', name: 'secret_version' },
});
});
await act(async () => {
wrapper.find('Button[children="OK"]').simulate('click');
});
// expect(wrapper.debug()).toBe(false);
// wrapper.find('Button[children="OK"]').simulate('click');
expect(onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
credential: selectedCredential,
secret_path: '/foo/bar',
secret_version: '9000',
}),
expect.anything()
);
});
});
afterAll(() => {
wrapper.unmount();
});
test('renders the expected content', () => {
expect(wrapper).toHaveLength(1);
describe('Plugin already configured', () => {
let wrapper;
const onClose = jest.fn();
const onSubmit = jest.fn();
beforeAll(async () => {
await act(async () => {
wrapper = mountWithContexts(
<CredentialPluginPrompt
onClose={onClose}
onSubmit={onSubmit}
initialValues={{
credential: selectedCredential,
inputs: {
secret_path: '/foo/bar',
secret_version: '9000',
},
}}
/>
);
});
});
afterAll(() => {
wrapper.unmount();
});
test('should render Wizard with all steps', async () => {
const wizard = await waitForElement(wrapper, 'Wizard');
const steps = wizard.prop('steps');
expect(steps).toHaveLength(2);
expect(steps[0].name).toEqual('Credential');
expect(steps[1].name).toEqual('Metadata');
});
test('credentials step renders correctly', () => {
expect(wrapper.find('CredentialsStep').length).toBe(1);
expect(wrapper.find('DataListItem').length).toBe(3);
expect(
wrapper
.find('Radio')
.at(0)
.prop('isChecked')
).toBe(true);
expect(wrapper.find('Button[children="Next"]').prop('isDisabled')).toBe(
false
);
});
test('metadata step renders correctly', async () => {
await act(async () => {
wrapper.find('Button[children="Next"]').simulate('click');
});
wrapper.update();
expect(wrapper.find('MetadataStep').length).toBe(1);
expect(wrapper.find('FormField').length).toBe(2);
expect(wrapper.find('input#credential-secret_path').prop('value')).toBe(
'/foo/bar'
);
expect(
wrapper.find('input#credential-secret_version').prop('value')
).toBe('9000');
});
});
});

View File

@ -66,7 +66,7 @@ function MetadataStep({ i18n }) {
}, [fetchMetadataOptions]);
const testMetadata = () => {
// todo: implement
// https://github.com/ansible/awx/issues/7126
};
if (isLoading) {

View File

@ -11,7 +11,6 @@ import { Credential } from '../../../../types';
const SelectedCredential = styled.div`
display: flex;
justify-content: space-between;
margin-top: 10px;
background-color: white;
border-bottom-color: var(--pf-global--BorderColor--200);
`;
@ -20,6 +19,10 @@ const SpacedCredentialChip = styled(CredentialChip)`
margin: 5px 8px;
`;
const PluginHelpText = styled.p`
margin-top: 5px;
`;
function CredentialPluginSelected({
i18n,
credential,
@ -28,12 +31,6 @@ function CredentialPluginSelected({
}) {
return (
<>
<p>
<Trans>
This field will be retrieved from an external secret management system
using the following credential:
</Trans>
</p>
<SelectedCredential>
<SpacedCredentialChip onClick={onClearPlugin} credential={credential} />
<Tooltip
@ -49,6 +46,12 @@ function CredentialPluginSelected({
</Button>
</Tooltip>
</SelectedCredential>
<PluginHelpText>
<Trans>
This field will be retrieved from an external secret management system
using the specified credential.
</Trans>
</PluginHelpText>
</>
);
}

View File

@ -0,0 +1,85 @@
{
"id": 12,
"type": "credential",
"url": "/api/v2/credentials/12/",
"related": {
"created_by": "/api/v2/users/1/",
"modified_by": "/api/v2/users/1/",
"activity_stream": "/api/v2/credentials/12/activity_stream/",
"access_list": "/api/v2/credentials/12/access_list/",
"object_roles": "/api/v2/credentials/12/object_roles/",
"owner_users": "/api/v2/credentials/12/owner_users/",
"owner_teams": "/api/v2/credentials/12/owner_teams/",
"copy": "/api/v2/credentials/12/copy/",
"input_sources": "/api/v2/credentials/12/input_sources/",
"credential_type": "/api/v2/credential_types/19/",
"user": "/api/v2/users/1/"
},
"summary_fields": {
"credential_type": {
"id": 19,
"name": "Microsoft Azure Key Vault",
"description": ""
},
"created_by": {
"id": 1,
"username": "admin",
"first_name": "",
"last_name": ""
},
"modified_by": {
"id": 1,
"username": "admin",
"first_name": "",
"last_name": ""
},
"object_roles": {
"admin_role": {
"description": "Can manage all aspects of the credential",
"name": "Admin",
"id": 60
},
"use_role": {
"description": "Can use the credential in a job template",
"name": "Use",
"id": 61
},
"read_role": {
"description": "May view settings for the credential",
"name": "Read",
"id": 62
}
},
"user_capabilities": {
"edit": true,
"delete": true,
"copy": true,
"use": true
},
"owners": [
{
"id": 1,
"type": "user",
"name": "admin",
"description": " ",
"url": "/api/v2/users/1/"
}
]
},
"created": "2020-05-26T14:54:45.612847Z",
"modified": "2020-05-26T14:54:45.612861Z",
"name": "Microsoft Azure Key Vault",
"description": "",
"organization": null,
"credential_type": 19,
"inputs": {
"url": "https://localhost",
"client": "foo",
"secret": "$encrypted$",
"tenant": "9000",
"cloud_name": "AzureCloud"
},
"kind": "azure_kv",
"cloud": false,
"kubernetes": false
}

View File

@ -0,0 +1,82 @@
{
"id": 11,
"type": "credential",
"url": "/api/v2/credentials/11/",
"related": {
"created_by": "/api/v2/users/1/",
"modified_by": "/api/v2/users/1/",
"activity_stream": "/api/v2/credentials/11/activity_stream/",
"access_list": "/api/v2/credentials/11/access_list/",
"object_roles": "/api/v2/credentials/11/object_roles/",
"owner_users": "/api/v2/credentials/11/owner_users/",
"owner_teams": "/api/v2/credentials/11/owner_teams/",
"copy": "/api/v2/credentials/11/copy/",
"input_sources": "/api/v2/credentials/11/input_sources/",
"credential_type": "/api/v2/credential_types/21/",
"user": "/api/v2/users/1/"
},
"summary_fields": {
"credential_type": {
"id": 21,
"name": "HashiCorp Vault Secret Lookup",
"description": ""
},
"created_by": {
"id": 1,
"username": "admin",
"first_name": "",
"last_name": ""
},
"modified_by": {
"id": 1,
"username": "admin",
"first_name": "",
"last_name": ""
},
"object_roles": {
"admin_role": {
"description": "Can manage all aspects of the credential",
"name": "Admin",
"id": 57
},
"use_role": {
"description": "Can use the credential in a job template",
"name": "Use",
"id": 58
},
"read_role": {
"description": "May view settings for the credential",
"name": "Read",
"id": 59
}
},
"user_capabilities": {
"edit": true,
"delete": true,
"copy": true,
"use": true
},
"owners": [
{
"id": 1,
"type": "user",
"name": "admin",
"description": " ",
"url": "/api/v2/users/1/"
}
]
},
"created": "2020-05-26T14:54:00.674404Z",
"modified": "2020-05-26T14:54:00.674418Z",
"name": "HashiCorp Vault Secret Lookup",
"description": "",
"organization": null,
"credential_type": 21,
"inputs": {
"url": "https://localhost",
"api_version": "v1"
},
"kind": "hashivault_kv",
"cloud": false,
"kubernetes": false
}