diff --git a/awx/ui_next/src/screens/Setting/SAML/SAML.test.jsx b/awx/ui_next/src/screens/Setting/SAML/SAML.test.jsx
index 0c662fd927..70d7ae5bb2 100644
--- a/awx/ui_next/src/screens/Setting/SAML/SAML.test.jsx
+++ b/awx/ui_next/src/screens/Setting/SAML/SAML.test.jsx
@@ -3,11 +3,31 @@ import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import { SettingsAPI } from '../../../api';
+import { SettingsProvider } from '../../../contexts/Settings';
+import mockAllOptions from '../shared/data.allSettingOptions.json';
import SAML from './SAML';
jest.mock('../../../api/models/Settings');
SettingsAPI.readCategory.mockResolvedValue({
- data: {},
+ data: {
+ SOCIAL_AUTH_SAML_CALLBACK_URL: 'https://towerhost/sso/complete/saml/',
+ SOCIAL_AUTH_SAML_METADATA_URL: 'https://towerhost/sso/metadata/saml/',
+ SOCIAL_AUTH_SAML_SP_ENTITY_ID: '',
+ SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: '',
+ SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '',
+ SOCIAL_AUTH_SAML_ORG_INFO: {},
+ SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: {},
+ SOCIAL_AUTH_SAML_SUPPORT_CONTACT: {},
+ SOCIAL_AUTH_SAML_ENABLED_IDPS: {},
+ SOCIAL_AUTH_SAML_SECURITY_CONFIG: {},
+ SOCIAL_AUTH_SAML_SP_EXTRA: {},
+ SOCIAL_AUTH_SAML_EXTRA_DATA: [],
+ SOCIAL_AUTH_SAML_ORGANIZATION_MAP: {},
+ SOCIAL_AUTH_SAML_TEAM_MAP: {},
+ SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: {},
+ SOCIAL_AUTH_SAML_TEAM_ATTR: {},
+ SAML_AUTO_CREATE_OBJECTS: false,
+ },
});
describe('', () => {
@@ -23,9 +43,14 @@ describe('', () => {
initialEntries: ['/settings/saml/details'],
});
await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
+ wrapper = mountWithContexts(
+
+
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
});
expect(wrapper.find('SAMLDetail').length).toBe(1);
});
@@ -35,9 +60,14 @@ describe('', () => {
initialEntries: ['/settings/saml/edit'],
});
await act(async () => {
- wrapper = mountWithContexts(, {
- context: { router: { history } },
- });
+ wrapper = mountWithContexts(
+
+
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
});
expect(wrapper.find('SAMLEdit').length).toBe(1);
});
diff --git a/awx/ui_next/src/screens/Setting/SAML/SAMLDetail/SAMLDetail.test.jsx b/awx/ui_next/src/screens/Setting/SAML/SAMLDetail/SAMLDetail.test.jsx
index 1afaee4e24..85fd5aa0d8 100644
--- a/awx/ui_next/src/screens/Setting/SAML/SAMLDetail/SAMLDetail.test.jsx
+++ b/awx/ui_next/src/screens/Setting/SAML/SAMLDetail/SAMLDetail.test.jsx
@@ -32,6 +32,7 @@ SettingsAPI.readCategory.mockResolvedValue({
SOCIAL_AUTH_SAML_TEAM_MAP: {},
SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: {},
SOCIAL_AUTH_SAML_TEAM_ATTR: {},
+ SAML_AUTO_CREATE_OBJECTS: false,
},
});
@@ -59,6 +60,11 @@ describe('', () => {
});
test('should render expected details', () => {
+ assertDetail(
+ wrapper,
+ 'Automatically Create Organizations and Teams on SAML Login',
+ 'Off'
+ );
assertDetail(
wrapper,
'SAML Assertion Consumer Service (ACS) URL',
diff --git a/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.jsx b/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.jsx
index fc9740b16c..93010d1ee5 100644
--- a/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.jsx
+++ b/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.jsx
@@ -1,25 +1,208 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { withI18n } from '@lingui/react';
-import { t } from '@lingui/macro';
-import { Button } from '@patternfly/react-core';
-import { CardBody, CardActionsRow } from '../../../../components/Card';
+import React, { useCallback, useEffect } from 'react';
+import { useHistory } from 'react-router-dom';
+import { Formik } from 'formik';
+import { Form } from '@patternfly/react-core';
+import { CardBody } from '../../../../components/Card';
+import ContentError from '../../../../components/ContentError';
+import ContentLoading from '../../../../components/ContentLoading';
+import { FormSubmitError } from '../../../../components/FormField';
+import { FormColumnLayout } from '../../../../components/FormLayout';
+import { useSettings } from '../../../../contexts/Settings';
+import { RevertAllAlert, RevertFormActionGroup } from '../../shared';
+import {
+ BooleanField,
+ FileUploadField,
+ InputField,
+ ObjectField,
+} from '../../shared/SharedFields';
+import { formatJson } from '../../shared/settingUtils';
+import useModal from '../../../../util/useModal';
+import useRequest from '../../../../util/useRequest';
+import { SettingsAPI } from '../../../../api';
+
+function SAMLEdit() {
+ const history = useHistory();
+ const { isModalOpen, toggleModal, closeModal } = useModal();
+ const { PUT: options } = useSettings();
+
+ const { isLoading, error, request: fetchSAML, result: saml } = useRequest(
+ useCallback(async () => {
+ const { data } = await SettingsAPI.readCategory('saml');
+ const mergedData = {};
+ Object.keys(data).forEach(key => {
+ if (!options[key]) {
+ return;
+ }
+ mergedData[key] = options[key];
+ mergedData[key].value = data[key];
+ });
+ return mergedData;
+ }, [options]),
+ null
+ );
+
+ useEffect(() => {
+ fetchSAML();
+ }, [fetchSAML]);
+
+ const { error: submitError, request: submitForm } = useRequest(
+ useCallback(
+ async values => {
+ await SettingsAPI.updateAll(values);
+ history.push('/settings/saml/details');
+ },
+ [history]
+ ),
+ null
+ );
+
+ const handleSubmit = async form => {
+ await submitForm({
+ ...form,
+ SOCIAL_AUTH_SAML_ORG_INFO: formatJson(form.SOCIAL_AUTH_SAML_ORG_INFO),
+ SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: formatJson(
+ form.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT
+ ),
+ SOCIAL_AUTH_SAML_SUPPORT_CONTACT: formatJson(
+ form.SOCIAL_AUTH_SAML_SUPPORT_CONTACT
+ ),
+ SOCIAL_AUTH_SAML_ENABLED_IDPS: formatJson(
+ form.SOCIAL_AUTH_SAML_ENABLED_IDPS
+ ),
+ SOCIAL_AUTH_SAML_ORGANIZATION_MAP: formatJson(
+ form.SOCIAL_AUTH_SAML_ORGANIZATION_MAP
+ ),
+ SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: formatJson(
+ form.SOCIAL_AUTH_SAML_ORGANIZATION_ATTR
+ ),
+ SOCIAL_AUTH_SAML_TEAM_MAP: formatJson(form.SOCIAL_AUTH_SAML_TEAM_MAP),
+ SOCIAL_AUTH_SAML_TEAM_ATTR: formatJson(form.SOCIAL_AUTH_SAML_TEAM_ATTR),
+ SOCIAL_AUTH_SAML_SECURITY_CONFIG: formatJson(
+ form.SOCIAL_AUTH_SAML_SECURITY_CONFIG
+ ),
+ SOCIAL_AUTH_SAML_SP_EXTRA: formatJson(form.SOCIAL_AUTH_SAML_SP_EXTRA),
+ SOCIAL_AUTH_SAML_EXTRA_DATA: formatJson(form.SOCIAL_AUTH_SAML_EXTRA_DATA),
+ });
+ };
+
+ const handleRevertAll = async () => {
+ const defaultValues = Object.assign(
+ ...Object.entries(saml).map(([key, value]) => ({
+ [key]: value.default,
+ }))
+ );
+ await submitForm(defaultValues);
+ closeModal();
+ };
+
+ const handleCancel = () => {
+ history.push('/settings/saml/details');
+ };
+
+ const initialValues = fields =>
+ Object.keys(fields).reduce((acc, key) => {
+ if (fields[key].type === 'list' || fields[key].type === 'nested object') {
+ const emptyDefault = fields[key].type === 'list' ? '[]' : '{}';
+ acc[key] = fields[key].value
+ ? JSON.stringify(fields[key].value, null, 2)
+ : emptyDefault;
+ } else {
+ acc[key] = fields[key].value ?? '';
+ }
+ return acc;
+ }, {});
-function SAMLEdit({ i18n }) {
return (
- {i18n._(t`Edit form coming soon :)`)}
-
-
-
+ {isLoading && }
+ {!isLoading && error && }
+ {!isLoading && saml && (
+
+ {formik => (
+
+ )}
+
+ )}
);
}
-export default withI18n()(SAMLEdit);
+export default SAMLEdit;
diff --git a/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.test.jsx b/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.test.jsx
index d6319d9b2e..858bf814f7 100644
--- a/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.test.jsx
+++ b/awx/ui_next/src/screens/Setting/SAML/SAMLEdit/SAMLEdit.test.jsx
@@ -1,16 +1,251 @@
import React from 'react';
-import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
+import { act } from 'react-dom/test-utils';
+import { createMemoryHistory } from 'history';
+import {
+ mountWithContexts,
+ waitForElement,
+} from '../../../../../testUtils/enzymeHelpers';
+import mockAllOptions from '../../shared/data.allSettingOptions.json';
+import { SettingsProvider } from '../../../../contexts/Settings';
+import { SettingsAPI } from '../../../../api';
import SAMLEdit from './SAMLEdit';
+jest.mock('../../../../api/models/Settings');
+SettingsAPI.updateAll.mockResolvedValue({});
+SettingsAPI.readCategory.mockResolvedValue({
+ data: {
+ SAML_AUTO_CREATE_OBJECTS: true,
+ SOCIAL_AUTH_SAML_CALLBACK_URL: 'https://towerhost/sso/complete/saml/',
+ SOCIAL_AUTH_SAML_METADATA_URL: 'https://towerhost/sso/metadata/saml/',
+ SOCIAL_AUTH_SAML_SP_ENTITY_ID: 'mock_id',
+ SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: 'mock_cert',
+ SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '$encrypted$',
+ SOCIAL_AUTH_SAML_ORG_INFO: {},
+ SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: {
+ givenName: 'Mock User',
+ emailAddress: 'mockuser@example.com',
+ },
+ SOCIAL_AUTH_SAML_SUPPORT_CONTACT: {},
+ SOCIAL_AUTH_SAML_ENABLED_IDPS: {},
+ SOCIAL_AUTH_SAML_SP_EXTRA: {},
+ SOCIAL_AUTH_SAML_EXTRA_DATA: [],
+ SOCIAL_AUTH_SAML_ORGANIZATION_MAP: {},
+ SOCIAL_AUTH_SAML_TEAM_MAP: {},
+ SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: {},
+ SOCIAL_AUTH_SAML_TEAM_ATTR: {},
+ SOCIAL_AUTH_SAML_SECURITY_CONFIG: {
+ requestedAuthnContext: false,
+ },
+ },
+});
+
describe('', () => {
let wrapper;
- beforeEach(() => {
- wrapper = mountWithContexts();
- });
+ let history;
+
afterEach(() => {
wrapper.unmount();
+ jest.clearAllMocks();
});
+
+ beforeEach(async () => {
+ history = createMemoryHistory({
+ initialEntries: ['/settings/saml/edit'],
+ });
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
+ });
+ await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
+ });
+
test('initially renders without crashing', () => {
expect(wrapper.find('SAMLEdit').length).toBe(1);
});
+
+ test('should display expected form fields', async () => {
+ expect(
+ wrapper.find('FormGroup[label="SAML Service Provider Entity ID"]').length
+ ).toBe(1);
+ expect(
+ wrapper.find(
+ 'FormGroup[label="Automatically Create Organizations and Teams on SAML Login"]'
+ ).length
+ ).toBe(1);
+ expect(
+ wrapper.find(
+ 'FormGroup[label="SAML Service Provider Public Certificate"]'
+ ).length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Service Provider Private Key"]')
+ .length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Service Provider Organization Info"]')
+ .length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Service Provider Technical Contact"]')
+ .length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Service Provider Support Contact"]')
+ .length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Enabled Identity Providers"]').length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Organization Map"]').length
+ ).toBe(1);
+ expect(wrapper.find('FormGroup[label="SAML Team Map"]').length).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Organization Attribute Mapping"]')
+ .length
+ ).toBe(1);
+ expect(
+ wrapper.find('FormGroup[label="SAML Team Attribute Mapping"]').length
+ ).toBe(1);
+ expect(wrapper.find('FormGroup[label="SAML Security Config"]').length).toBe(
+ 1
+ );
+ expect(
+ wrapper.find(
+ 'FormGroup[label="SAML Service Provider extra configuration data"]'
+ ).length
+ ).toBe(1);
+ expect(
+ wrapper.find(
+ 'FormGroup[label="SAML IDP to extra_data attribute mapping"]'
+ ).length
+ ).toBe(1);
+ });
+
+ test('should successfully send default values to api on form revert all', async () => {
+ expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
+ expect(wrapper.find('RevertAllAlert')).toHaveLength(0);
+ await act(async () => {
+ wrapper
+ .find('button[aria-label="Revert all to default"]')
+ .invoke('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('RevertAllAlert')).toHaveLength(1);
+ await act(async () => {
+ wrapper
+ .find('RevertAllAlert button[aria-label="Confirm revert all"]')
+ .invoke('onClick')();
+ });
+ wrapper.update();
+ expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
+ expect(SettingsAPI.updateAll).toHaveBeenCalledWith({
+ SAML_AUTO_CREATE_OBJECTS: true,
+ SOCIAL_AUTH_SAML_ENABLED_IDPS: {},
+ SOCIAL_AUTH_SAML_EXTRA_DATA: null,
+ SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: {},
+ SOCIAL_AUTH_SAML_ORGANIZATION_MAP: null,
+ SOCIAL_AUTH_SAML_ORG_INFO: {},
+ SOCIAL_AUTH_SAML_SP_ENTITY_ID: '',
+ SOCIAL_AUTH_SAML_SP_EXTRA: null,
+ SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '',
+ SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: '',
+ SOCIAL_AUTH_SAML_SUPPORT_CONTACT: {},
+ SOCIAL_AUTH_SAML_TEAM_ATTR: {},
+ SOCIAL_AUTH_SAML_TEAM_MAP: null,
+ SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: {},
+ SOCIAL_AUTH_SAML_SECURITY_CONFIG: {
+ requestedAuthnContext: false,
+ },
+ });
+ });
+
+ test('should successfully send request to api on form submission', async () => {
+ act(() => {
+ wrapper.find('input#SOCIAL_AUTH_SAML_SP_ENTITY_ID').simulate('change', {
+ target: { value: 'new_id', name: 'SOCIAL_AUTH_SAML_SP_ENTITY_ID' },
+ });
+ wrapper
+ .find(
+ 'FormGroup[fieldId="SOCIAL_AUTH_SAML_TECHNICAL_CONTACT"] button[aria-label="Revert"]'
+ )
+ .invoke('onClick')();
+ });
+ wrapper.update();
+ await act(async () => {
+ wrapper.find('Form').invoke('onSubmit')();
+ });
+ expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
+ expect(SettingsAPI.updateAll).toHaveBeenCalledWith({
+ SAML_AUTO_CREATE_OBJECTS: true,
+ SOCIAL_AUTH_SAML_ENABLED_IDPS: {},
+ SOCIAL_AUTH_SAML_EXTRA_DATA: [],
+ SOCIAL_AUTH_SAML_ORGANIZATION_ATTR: {},
+ SOCIAL_AUTH_SAML_ORGANIZATION_MAP: {},
+ SOCIAL_AUTH_SAML_ORG_INFO: {},
+ SOCIAL_AUTH_SAML_SP_ENTITY_ID: 'new_id',
+ SOCIAL_AUTH_SAML_SP_EXTRA: {},
+ SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '$encrypted$',
+ SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: 'mock_cert',
+ SOCIAL_AUTH_SAML_SUPPORT_CONTACT: {},
+ SOCIAL_AUTH_SAML_TEAM_ATTR: {},
+ SOCIAL_AUTH_SAML_TEAM_MAP: {},
+ SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: {},
+ SOCIAL_AUTH_SAML_SECURITY_CONFIG: {
+ requestedAuthnContext: false,
+ },
+ });
+ });
+
+ test('should navigate to saml detail on successful submission', async () => {
+ await act(async () => {
+ wrapper.find('Form').invoke('onSubmit')();
+ });
+ expect(history.location.pathname).toEqual('/settings/saml/details');
+ });
+
+ test('should navigate to saml detail when cancel is clicked', async () => {
+ await act(async () => {
+ wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
+ });
+ expect(history.location.pathname).toEqual('/settings/saml/details');
+ });
+
+ test('should display error message on unsuccessful submission', async () => {
+ const error = {
+ response: {
+ data: { detail: 'An error occurred' },
+ },
+ };
+ SettingsAPI.updateAll.mockImplementation(() => Promise.reject(error));
+ expect(wrapper.find('FormSubmitError').length).toBe(0);
+ expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
+ await act(async () => {
+ wrapper.find('Form').invoke('onSubmit')();
+ });
+ wrapper.update();
+ expect(wrapper.find('FormSubmitError').length).toBe(1);
+ expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
+ });
+
+ test('should display ContentError on throw', async () => {
+ SettingsAPI.readCategory.mockImplementationOnce(() =>
+ Promise.reject(new Error())
+ );
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+ await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
+ expect(wrapper.find('ContentError').length).toBe(1);
+ });
});
diff --git a/awx/ui_next/src/screens/Setting/shared/RevertButton.jsx b/awx/ui_next/src/screens/Setting/shared/RevertButton.jsx
index f0fe7e8b72..a997388395 100644
--- a/awx/ui_next/src/screens/Setting/shared/RevertButton.jsx
+++ b/awx/ui_next/src/screens/Setting/shared/RevertButton.jsx
@@ -13,7 +13,13 @@ const ButtonWrapper = styled.div`
}
`;
-function RevertButton({ i18n, id, defaultValue, isDisabled = false }) {
+function RevertButton({
+ i18n,
+ id,
+ defaultValue,
+ isDisabled = false,
+ onRevertCallback = () => null,
+}) {
const [field, meta, helpers] = useField(id);
const initialValue = meta.initialValue ?? '';
const currentValue = field.value;
@@ -30,6 +36,7 @@ function RevertButton({ i18n, id, defaultValue, isDisabled = false }) {
function handleConfirm() {
helpers.setValue(isRevertable ? defaultValue : initialValue);
+ onRevertCallback();
}
const revertTooltipContent = isRevertable
diff --git a/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx b/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx
index 877aa2be35..f668289976 100644
--- a/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx
+++ b/awx/ui_next/src/screens/Setting/shared/SharedFields.jsx
@@ -1,9 +1,10 @@
-import React from 'react';
+import React, { useState } from 'react';
import { bool, oneOf, shape, string } from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { useField } from 'formik';
import {
+ FileUpload,
FormGroup as PFFormGroup,
InputGroup,
TextInput,
@@ -42,6 +43,7 @@ const SettingGroup = withI18n()(
isDisabled,
isRequired,
label,
+ onRevertCallback,
popoverContent,
validated,
}) => (
@@ -62,6 +64,7 @@ const SettingGroup = withI18n()(
id={fieldId}
defaultValue={defaultValue}
isDisabled={isDisabled}
+ onRevertCallback={onRevertCallback}
/>
>
}
@@ -261,4 +264,52 @@ ObjectField.propTypes = {
isRequired: bool,
};
-export { BooleanField, ChoiceField, EncryptedField, InputField, ObjectField };
+const FileUploadField = withI18n()(
+ ({ i18n, name, config, isRequired = false }) => {
+ const validate = isRequired ? required(null, i18n) : null;
+ const [filename, setFilename] = useState('');
+ const [fileIsUploading, setFileIsUploading] = useState(false);
+ const [field, meta, helpers] = useField({ name, validate });
+ const isValid = !(meta.touched && meta.error);
+
+ return config ? (
+
+ setFilename('')}
+ >
+ {
+ helpers.setValue(value);
+ setFilename(title);
+ }}
+ onReadStarted={() => setFileIsUploading(true)}
+ onReadFinished={() => setFileIsUploading(false)}
+ isLoading={fileIsUploading}
+ allowEditingUploadedText
+ validated={isValid ? 'default' : 'error'}
+ />
+
+
+ ) : null;
+ }
+);
+
+export {
+ BooleanField,
+ ChoiceField,
+ EncryptedField,
+ FileUploadField,
+ InputField,
+ ObjectField,
+};
diff --git a/awx/ui_next/src/screens/Setting/shared/SharedFields.test.jsx b/awx/ui_next/src/screens/Setting/shared/SharedFields.test.jsx
index d0c8a1437a..39b49f9428 100644
--- a/awx/ui_next/src/screens/Setting/shared/SharedFields.test.jsx
+++ b/awx/ui_next/src/screens/Setting/shared/SharedFields.test.jsx
@@ -8,6 +8,7 @@ import {
BooleanField,
ChoiceField,
EncryptedField,
+ FileUploadField,
InputField,
ObjectField,
} from './SharedFields';
@@ -161,4 +162,46 @@ describe('Setting form fields', () => {
wrapper.update();
expect(wrapper.find('CodeMirrorInput').prop('value')).toBe('[]');
});
+
+ test('FileUploadField renders the expected content', async () => {
+ const wrapper = mountWithContexts(
+
+ {() => (
+
+ )}
+
+ );
+ expect(wrapper.find('FileUploadField')).toHaveLength(1);
+ expect(wrapper.find('label').text()).toEqual('mock file label');
+ expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual('');
+ await act(async () => {
+ wrapper.find('FileUpload').invoke('onChange')(
+ {
+ text: () =>
+ '-----BEGIN PRIVATE KEY-----\\nAAAAAAAAAAAAAA\\n-----END PRIVATE KEY-----\\n',
+ },
+ 'new file name'
+ );
+ });
+ wrapper.update();
+ expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual(
+ 'new file name'
+ );
+ await act(async () => {
+ wrapper.find('button[aria-label="Revert"]').invoke('onClick')();
+ });
+ wrapper.update();
+ expect(wrapper.find('input#mock_file-filename').prop('value')).toEqual('');
+ });
});