mirror of
https://github.com/ansible/awx.git
synced 2026-03-19 09:57:33 -02:30
Adds test coverage for cred plugin prompt
This commit is contained in:
@@ -72,7 +72,7 @@ function CredentialPluginField(props) {
|
|||||||
)}
|
)}
|
||||||
{showPluginWizard && (
|
{showPluginWizard && (
|
||||||
<CredentialPluginPrompt
|
<CredentialPluginPrompt
|
||||||
initialValues={field.value}
|
initialValues={typeof field.value === 'object' ? field.value : {}}
|
||||||
onClose={() => setShowPluginWizard(false)}
|
onClose={() => setShowPluginWizard(false)}
|
||||||
onSubmit={val => {
|
onSubmit={val => {
|
||||||
val.touched = true;
|
val.touched = true;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { func } from 'prop-types';
|
import { func, shape } from 'prop-types';
|
||||||
import { Formik, useField } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
@@ -14,6 +14,7 @@ function CredentialPluginWizard({ i18n, handleSubmit, onClose }) {
|
|||||||
id: 1,
|
id: 1,
|
||||||
name: i18n._(t`Credential`),
|
name: i18n._(t`Credential`),
|
||||||
component: <CredentialsStep />,
|
component: <CredentialsStep />,
|
||||||
|
enableNext: !!selectedCredential.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -58,8 +59,11 @@ function CredentialPluginPrompt({ i18n, onClose, onSubmit, initialValues }) {
|
|||||||
CredentialPluginPrompt.propTypes = {
|
CredentialPluginPrompt.propTypes = {
|
||||||
onClose: func.isRequired,
|
onClose: func.isRequired,
|
||||||
onSubmit: func.isRequired,
|
onSubmit: func.isRequired,
|
||||||
|
initialValues: shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
CredentialPluginPrompt.defaultProps = {};
|
CredentialPluginPrompt.defaultProps = {
|
||||||
|
initialValues: {},
|
||||||
|
};
|
||||||
|
|
||||||
export default withI18n()(CredentialPluginPrompt);
|
export default withI18n()(CredentialPluginPrompt);
|
||||||
|
|||||||
@@ -1,18 +1,228 @@
|
|||||||
import React from 'react';
|
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';
|
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 />', () => {
|
describe('<CredentialPluginPrompt />', () => {
|
||||||
let wrapper;
|
describe('Plugin not configured', () => {
|
||||||
beforeAll(() => {
|
let wrapper;
|
||||||
wrapper = mountWithContexts(
|
const onClose = jest.fn();
|
||||||
<CredentialPluginPrompt onClose={jest.fn()} onSubmit={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();
|
describe('Plugin already configured', () => {
|
||||||
});
|
let wrapper;
|
||||||
test('renders the expected content', () => {
|
const onClose = jest.fn();
|
||||||
expect(wrapper).toHaveLength(1);
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ function MetadataStep({ i18n }) {
|
|||||||
}, [fetchMetadataOptions]);
|
}, [fetchMetadataOptions]);
|
||||||
|
|
||||||
const testMetadata = () => {
|
const testMetadata = () => {
|
||||||
// todo: implement
|
// https://github.com/ansible/awx/issues/7126
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { Credential } from '../../../../types';
|
|||||||
const SelectedCredential = styled.div`
|
const SelectedCredential = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 10px;
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-bottom-color: var(--pf-global--BorderColor--200);
|
border-bottom-color: var(--pf-global--BorderColor--200);
|
||||||
`;
|
`;
|
||||||
@@ -20,6 +19,10 @@ const SpacedCredentialChip = styled(CredentialChip)`
|
|||||||
margin: 5px 8px;
|
margin: 5px 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const PluginHelpText = styled.p`
|
||||||
|
margin-top: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
function CredentialPluginSelected({
|
function CredentialPluginSelected({
|
||||||
i18n,
|
i18n,
|
||||||
credential,
|
credential,
|
||||||
@@ -28,12 +31,6 @@ function CredentialPluginSelected({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>
|
|
||||||
<Trans>
|
|
||||||
This field will be retrieved from an external secret management system
|
|
||||||
using the following credential:
|
|
||||||
</Trans>
|
|
||||||
</p>
|
|
||||||
<SelectedCredential>
|
<SelectedCredential>
|
||||||
<SpacedCredentialChip onClick={onClearPlugin} credential={credential} />
|
<SpacedCredentialChip onClick={onClearPlugin} credential={credential} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -49,6 +46,12 @@ function CredentialPluginSelected({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SelectedCredential>
|
</SelectedCredential>
|
||||||
|
<PluginHelpText>
|
||||||
|
<Trans>
|
||||||
|
This field will be retrieved from an external secret management system
|
||||||
|
using the specified credential.
|
||||||
|
</Trans>
|
||||||
|
</PluginHelpText>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user