diff --git a/CHANGELOG.md b/CHANGELOG.md index 1521fc591d67..ed0f52691207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🪛 Refactoring ### 🔩 Tests +* [Multi DataSource] Add unit test coverage for Update Data source management stack ([#2567](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2567)) ## [2.x] ### 💥 Breaking Changes diff --git a/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap b/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap new file mode 100644 index 000000000000..85e08d85ecef --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap @@ -0,0 +1,1320 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Datasource Management: Edit Datasource Wizard should load resources successfully should render normally 1`] = ` + + + +
+ + +
+ +
+ +
+
+ +

+ create-test-ds +

+
+ +
+ +
+
+ + +
+ + + + + + + +
+
+
+ +
+ +
+ +
+ +
+

+ + + Connection Details + + +

+
+
+ +
+
+ + +

+ } + title={ +

+ +

+ } + > +
+ +
+ +
+ +

+ + + Object Details + + +

+
+ +
+ +
+

+ + + This connection information is used for reference in tables and when adding to a data source connection + + +

+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+ + + + + - + + + + + } + labelType="label" + > +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+

+ + + Endpoint + + +

+
+
+ +
+
+ + +

+ } + title={ +

+ +

+ } + > +
+ +
+ +
+ +

+ + + Endpoint + + +

+
+ +
+ +
+

+ + + This connection information is used for reference in tables and when adding to a data source connection + + +

+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+

+ + + Authentication + + +

+
+
+ +
+
+ + + + } + > +
+ +
+ +
+ +

+ + + Authentication Method + + +

+
+
+
+ +
+ +
+
+ + + +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+
+ + +
+ + +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+ +
+
+ + + +
+
+ +
+ +
+ + , + ] + } + compressed={false} + fullWidth={false} + icon="lock" + isLoading={false} + > +
+
+ + + + +
+ + + + + +
+
+
+ + + +
+
+
+
+
+ +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ +
+ +
+ +
+ + +
+ + +
+ + + + +`; diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap new file mode 100644 index 000000000000..fbdc0767bf3d --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap @@ -0,0 +1,2288 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Datasource Management: Edit Datasource Form Case 1: With Username & Password should render normally 1`] = ` + + +
+ +
+ +
+
+ +

+ create-test-ds +

+
+ +
+ +
+
+ + +
+ + + + + + + +
+
+
+ +
+ +
+ +
+ +
+

+ + + Connection Details + + +

+
+
+ +
+
+ + +

+ } + title={ +

+ +

+ } + > +
+ +
+ +
+ +

+ + + Object Details + + +

+
+ +
+ +
+

+ + + This connection information is used for reference in tables and when adding to a data source connection + + +

+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+ + + + + - + + + + + } + labelType="label" + > +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+

+ + + Endpoint + + +

+
+
+ +
+
+ + +

+ } + title={ +

+ +

+ } + > +
+ +
+ +
+ +

+ + + Endpoint + + +

+
+ +
+ +
+

+ + + This connection information is used for reference in tables and when adding to a data source connection + + +

+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+

+ + + Authentication + + +

+
+
+ +
+
+ + + + } + > +
+ +
+ +
+ +

+ + + Authentication Method + + +

+
+
+
+ +
+ +
+
+ + + +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+
+ + +
+ + +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+ +
+
+ + + +
+
+ +
+ +
+ + , + ] + } + compressed={false} + fullWidth={false} + icon="lock" + isLoading={false} + > +
+
+ + + + +
+ + + + + +
+
+
+ + + +
+
+
+
+
+ +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ +
+ +
+ +
+ + +
+ + +
+ + + +`; + +exports[`Datasource Management: Edit Datasource Form Case 2: With No Authentication should render normally 1`] = ` + + +
+ +
+ +
+
+ +

+ create-test-ds123 +

+
+ +
+ +
+
+ + +
+ + + + + + + +
+
+
+ +
+ +
+ +
+ +
+

+ + + Connection Details + + +

+
+
+ +
+
+ + +

+ } + title={ +

+ +

+ } + > +
+ +
+ +
+ +

+ + + Object Details + + +

+
+ +
+ +
+

+ + + This connection information is used for reference in tables and when adding to a data source connection + + +

+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+ + + + + - + + + + + } + labelType="label" + > +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+

+ + + Endpoint + + +

+
+
+ +
+
+ + +

+ } + title={ +

+ +

+ } + > +
+ +
+ +
+ +

+ + + Endpoint + + +

+
+ +
+ +
+

+ + + This connection information is used for reference in tables and when adding to a data source connection + + +

+
+
+
+
+
+
+ +
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+

+ + + Authentication + + +

+
+
+ +
+
+ + + + } + > +
+ +
+ +
+ +

+ + + Authentication Method + + +

+
+
+
+ +
+ +
+
+ + + +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + +
+ + + +`; diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx new file mode 100644 index 000000000000..000b019b3ee3 --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.test.tsx @@ -0,0 +1,328 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { wrapWithIntl } from 'test_utils/enzyme_helpers'; +import { + mockDataSourceAttributesWithAuth, + mockManagementPlugin, + existingDatasourceNamesList, + mockDataSourceAttributesWithNoAuth, +} from '../../../../mocks'; +import { OpenSearchDashboardsContextProvider } from '../../../../../../opensearch_dashboards_react/public'; +import { EditDataSourceForm } from './edit_data_source_form'; +import { act } from 'react-dom/test-utils'; +import { AuthType } from '../../../../types'; + +const titleFieldIdentifier = 'dataSourceTitle'; +const titleFormRowIdentifier = '[data-test-subj="editDataSourceTitleFormRow"]'; +const endpointFieldIdentifier = '[data-test-subj="editDatasourceEndpointField"]'; +const descriptionFieldIdentifier = 'dataSourceDescription'; +const descriptionFormRowIdentifier = '[data-test-subj="editDataSourceDescriptionFormRow"]'; +const authTypeRadioIdentifier = '[data-test-subj="editDataSourceSelectAuthType"]'; +const usernameFieldIdentifier = 'datasourceUsername'; +const usernameFormRowIdentifier = '[data-test-subj="editDatasourceUsernameFormRow"]'; +const passwordFieldIdentifier = '[data-test-subj="updateDataSourceFormPasswordField"]'; +const updatePasswordBtnIdentifier = '[data-test-subj="editDatasourceUpdatePasswordBtn"]'; +describe('Datasource Management: Edit Datasource Form', () => { + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + let component: ReactWrapper, React.Component<{}, {}, any>>; + const mockFn = jest.fn(); + + const updateInputFieldAndBlur = ( + comp: ReactWrapper, React.Component<{}, {}, any>>, + fieldName: string, + updatedValue: string, + isTestSubj?: boolean + ) => { + const field = isTestSubj ? comp.find(fieldName) : comp.find({ name: fieldName }); + act(() => { + field.last().simulate('change', { target: { value: updatedValue } }); + }); + comp.update(); + act(() => { + field.last().simulate('focus').simulate('blur'); + }); + comp.update(); + }; + + describe('Case 1: With Username & Password', () => { + beforeEach(() => { + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + component.update(); + }); + + test('should render normally', () => { + expect(component).toMatchSnapshot(); + // @ts-ignore + expect(component.find({ name: titleFieldIdentifier }).first().props().value).toBe( + mockDataSourceAttributesWithAuth.title + ); + expect(component.find(endpointFieldIdentifier).first().props().disabled).toBe(true); + }); + + /* Validation */ + test('should validate title as required field & no duplicates allowed', () => { + /* Validate empty title - required */ + updateInputFieldAndBlur(component, titleFieldIdentifier, ''); + // @ts-ignore + expect(component.find(titleFormRowIdentifier).first().props().isInvalid).toBe(true); + + /* Validate duplicate title */ + updateInputFieldAndBlur(component, titleFieldIdentifier, 'DuP20'); + // @ts-ignore + expect(component.find(titleFormRowIdentifier).first().props().isInvalid).toBe(true); + + /* change to original title */ + updateInputFieldAndBlur( + component, + titleFieldIdentifier, + mockDataSourceAttributesWithAuth.title + ); + // @ts-ignore + expect(component.find(titleFormRowIdentifier).first().props().isInvalid).toBe(false); + + /* change to valid updated title */ + updateInputFieldAndBlur(component, titleFieldIdentifier, 'test007'); + // @ts-ignore + expect(component.find(titleFormRowIdentifier).first().props().isInvalid).toBe(false); + }); + test('should validate username as required field', () => { + /* Validate empty username - required */ + updateInputFieldAndBlur(component, usernameFieldIdentifier, ''); + // @ts-ignore + expect(component.find(usernameFormRowIdentifier).first().props().isInvalid).toBe(true); + + /* change to original username */ + updateInputFieldAndBlur( + component, + usernameFieldIdentifier, + mockDataSourceAttributesWithAuth.auth.credentials.username + ); + // @ts-ignore + expect(component.find(usernameFormRowIdentifier).first().props().isInvalid).toBe(false); + /* change to valid updated username */ + updateInputFieldAndBlur(component, usernameFieldIdentifier, 'test'); + // @ts-ignore + expect(component.find(usernameFormRowIdentifier).first().props().isInvalid).toBe(false); + }); + test('should validate that password field is disabled & update stored password button is shown', () => { + expect(component.find(passwordFieldIdentifier).first().props().disabled).toBe(true); + expect(component.find(updatePasswordBtnIdentifier).exists()).toBe(true); + }); + /* Functionality */ + test('should display update password modal on update stored password button click & on update confirmation should update the password', () => { + act(() => { + component.find(updatePasswordBtnIdentifier).first().simulate('click'); + }); + component.update(); + expect(component.find('UpdatePasswordModal').exists()).toBe(true); + + /* Update password */ + act(() => { + // @ts-ignore + component.find('UpdatePasswordModal').prop('handleUpdatePassword')('testPassword'); + }); + component.update(); + expect(mockFn).toHaveBeenCalled(); + expect(component.find('UpdatePasswordModal').exists()).toBe(false); + }); + test("should hide username & password fields when 'No Authentication' is selected as the credential type", () => { + act(() => { + // @ts-ignore + component.find(authTypeRadioIdentifier).first().prop('onChange')(AuthType.NoAuth); + }); + component.update(); + expect(component.find(usernameFormRowIdentifier).exists()).toBe(false); + expect(component.find(passwordFieldIdentifier).exists()).toBe(false); + }); + + /* Cancel Changes */ + test('should reset form on click cancel changes', async () => { + await new Promise((resolve) => + setTimeout(() => { + updateInputFieldAndBlur(component, descriptionFieldIdentifier, ''); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + resolve(); + }, 100) + ); + await new Promise((resolve) => + setTimeout(() => { + /* Updated description*/ + updateInputFieldAndBlur(component, descriptionFieldIdentifier, 'testDescription'); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + + expect(component.find('[data-test-subj="datasource-edit-cancelButton"]').exists()).toBe( + true + ); + component + .find('[data-test-subj="datasource-edit-cancelButton"]') + .first() + .simulate('click'); + resolve(); + }, 100) + ); + }); + + /* Save Changes */ + test('should update the form with Username&Password on click save changes', async () => { + await new Promise((resolve) => + setTimeout(() => { + updateInputFieldAndBlur(component, descriptionFieldIdentifier, ''); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + resolve(); + }, 100) + ); + await new Promise((resolve) => + setTimeout(() => { + /* Updated description*/ + updateInputFieldAndBlur(component, descriptionFieldIdentifier, 'testDescription'); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + + expect(component.find('[data-test-subj="datasource-edit-saveButton"]').exists()).toBe( + true + ); + component.find('[data-test-subj="datasource-edit-saveButton"]').first().simulate('click'); + expect(mockFn).toHaveBeenCalled(); + resolve(); + }, 100) + ); + }); + }); + + describe('Case 2: With No Authentication', () => { + beforeEach(() => { + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + component.update(); + }); + + test('should render normally', () => { + expect(component).toMatchSnapshot(); + // @ts-ignore + expect(component.find({ name: titleFieldIdentifier }).first().props().value).toBe( + mockDataSourceAttributesWithNoAuth.title + ); + expect(component.find(endpointFieldIdentifier).first().props().disabled).toBe(true); + }); + + /* functionality */ + + test("should show username & password fields when 'Username & Password' is selected as the credential type", () => { + act(() => { + // @ts-ignore + component.find(authTypeRadioIdentifier).first().prop('onChange')( + // @ts-ignore + AuthType.UsernamePasswordType + ); + }); + component.update(); + expect(component.find(usernameFormRowIdentifier).exists()).toBe(true); + expect(component.find(passwordFieldIdentifier).exists()).toBe(true); + }); + + /* validation - Password */ + + test('should validate password as required field', () => { + act(() => { + // @ts-ignore + component.find(authTypeRadioIdentifier).first().prop('onChange')( + // @ts-ignore + AuthType.UsernamePasswordType + ); + }); + component.update(); + + /* Validate empty username - required */ + updateInputFieldAndBlur(component, passwordFieldIdentifier, '', true); + // @ts-ignore + expect(component.find(passwordFieldIdentifier).first().props().isInvalid).toBe(true); + + /* change to original username */ + updateInputFieldAndBlur(component, passwordFieldIdentifier, 'test123', true); + // @ts-ignore + expect(component.find(passwordFieldIdentifier).first().props().isInvalid).toBe(false); + }); + + test('should delete datasource on confirmation from header', () => { + // @ts-ignore + component.find('Header').prop('onClickDeleteIcon')(); + expect(mockFn).toHaveBeenCalled(); + }); + + /* Save Changes */ + test('should update the form with NoAUth on click save changes', async () => { + await new Promise((resolve) => + setTimeout(() => { + updateInputFieldAndBlur(component, descriptionFieldIdentifier, ''); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + resolve(); + }, 100) + ); + await new Promise((resolve) => + setTimeout(() => { + /* Updated description*/ + updateInputFieldAndBlur(component, descriptionFieldIdentifier, 'testDescription'); + expect( + // @ts-ignore + component.find(descriptionFormRowIdentifier).first().props().isInvalid + ).toBeUndefined(); + + expect(component.find('[data-test-subj="datasource-edit-saveButton"]').exists()).toBe( + true + ); + component.find('[data-test-subj="datasource-edit-saveButton"]').first().simulate('click'); + expect(mockFn).toHaveBeenCalled(); + resolve(); + }, 100) + ); + }); + }); +}); diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx index 61a7f3ba1f9b..00c205fa1ade 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx @@ -275,7 +275,10 @@ export class EditDataSourceForm extends React.Component { return ( <> - + { @@ -512,6 +518,7 @@ export class EditDataSourceForm extends React.Component this.onChangeAuthType(id)} name="Credential" + data-test-subj="editDataSourceSelectAuthType" /> @@ -531,8 +538,10 @@ export class EditDataSourceForm extends React.Component { - let bottomBar = null; - - if (this.state.showUpdateOptions) { - bottomBar = ( - - - - - - this.resetFormValues()} - aria-describedby="aria-describedby.countOfUnsavedSettings" - data-test-subj="datasource-edit-cancelButton" - > - { - - } - - - - - { - - } - - - - - ); - } - return bottomBar; + return ( + + + + + + this.resetFormValues()} + aria-describedby="aria-describedby.countOfUnsavedSettings" + data-test-subj="datasource-edit-cancelButton" + > + + + + + + + + + + + ); }; renderContent = () => { @@ -684,7 +683,7 @@ export class EditDataSourceForm extends React.Component - {this.renderBottomBar()} + {this.state.showUpdateOptions ? this.renderBottomBar() : null} diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/data_source_management/public/components/edit_data_source/components/header/__snapshots__/header.test.tsx.snap new file mode 100644 index 000000000000..d9877a2cdc1d --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/header/__snapshots__/header.test.tsx.snap @@ -0,0 +1,175 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Datasource Management: Edit Datasource Header do not show delete icon should render normally 1`] = ` + +
+ +
+ +
+
+ +

+ testTest20 +

+
+ +
+ +
+
+ + +
+ +
+ +
+
+`; + +exports[`Datasource Management: Edit Datasource Header show delete icon should render normally 1`] = ` + +
+ +
+ +
+
+ +

+ testTest20 +

+
+ +
+ +
+
+ + +
+ + + + + + + +
+
+
+ +
+
+`; diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx new file mode 100644 index 000000000000..36a3551d9ada --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { Header } from './header'; +import { wrapWithIntl } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../mocks'; +import { OpenSearchDashboardsContextProvider } from '../../../../../../opensearch_dashboards_react/public'; +import { act } from 'react-dom/test-utils'; + +const headerTitleIdentifier = '[data-test-subj="editDataSourceTitle"]'; +const deleteIconIdentifier = '[data-test-subj="editDatasourceDeleteIcon"]'; +const confirmModalIdentifier = '[data-test-subj="editDatasourceDeleteConfirmModal"]'; + +describe('Datasource Management: Edit Datasource Header', () => { + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + let component: ReactWrapper, React.Component<{}, {}, any>>; + const mockFn = jest.fn(); + const dataSourceName = 'testTest20'; + + describe('show delete icon', () => { + beforeEach(() => { + component = mount( + wrapWithIntl( +
+ ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + }); + + test('should render normally', () => { + expect(component).toMatchSnapshot(); + expect(component.find(headerTitleIdentifier).last().text()).toBe(dataSourceName); + }); + test('should show confirm delete modal pop up on trash icon click and cancel button work normally', () => { + component.find(deleteIconIdentifier).first().simulate('click'); + // test if modal pop up when click the delete button + expect(component.find(confirmModalIdentifier).exists()).toBe(true); + + act(() => { + // @ts-ignore + component.find(confirmModalIdentifier).first().props().onCancel(); + }); + + component.update(); + expect(component.find(confirmModalIdentifier).exists()).toBe(false); + }); + test('should show confirm delete modal pop up on trash icon click and confirm button should delete datasource', () => { + component.find(deleteIconIdentifier).first().simulate('click'); + // test if modal pop up when click the delete button + expect(component.find(confirmModalIdentifier).exists()).toBe(true); + + act(() => { + // @ts-ignore + component.find(confirmModalIdentifier).first().props().onConfirm(); + }); + + component.update(); + expect(component.find(confirmModalIdentifier).exists()).toBe(false); + }); + }); + describe('do not show delete icon', () => { + beforeEach(() => { + component = mount( + wrapWithIntl( +
+ ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + }); + test('should render normally', () => { + expect(component).toMatchSnapshot(); + expect(component.find(headerTitleIdentifier).last().text()).toBe(dataSourceName); + expect(component.find(deleteIconIdentifier).exists()).toBe(false); + }); + }); +}); diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx index 2dda7b4ccf75..88e050394b89 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/header/header.tsx @@ -46,6 +46,7 @@ export const Header = ({ > { setIsDeleteModalVisible(true); }} @@ -80,6 +81,7 @@ export const Header = ({ defaultMessage: 'Delete', })} defaultFocusedButton="confirm" + data-test-subj="editDatasourceDeleteConfirmModal" >

{ @@ -107,7 +109,7 @@ export const Header = ({

- +

{dataSourceName}

diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/__snapshots__/update_password_modal.test.tsx.snap b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/__snapshots__/update_password_modal.test.tsx.snap new file mode 100644 index 000000000000..ef19172398e1 --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/__snapshots__/update_password_modal.test.tsx.snap @@ -0,0 +1,920 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Datasource Management: Update Stored Password Modal should render normally 1`] = ` + + +
+ + + + +
+
+ +
+
+
+

+ + Update stored password + +

+
+
+
+
+
+
+
+ + Update credential password to reflect accurate password to gain access to the endpoint. + +
+
+
+
+
+
+
+ +
+
+
+ test_user +
+
+
+
+
+ +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+
+ } + > + +
+
+ + + + + +
+ +
+ +
+

+ + + Update stored password + + +

+
+
+
+
+ +
+
+ +
+
+ +
+ + + Update credential password to reflect accurate password to gain access to the endpoint. + + +
+
+
+
+
+ +
+ + +
+ +
+
+ + + +
+
+ +
+ test_user +
+
+
+
+
+ +
+
+ + + +
+
+ + , + ] + } + compressed={false} + fullWidth={false} + icon="lock" + isLoading={false} + > +
+
+ + + + +
+ + + + + +
+
+
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+
+ + , + ] + } + compressed={false} + fullWidth={false} + icon="lock" + isLoading={false} + > +
+
+ + + + +
+ + + + + +
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ + +
+ + + + + + + + +
+
+
+
+
+ + + + + +
+ + +`; diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.test.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.test.tsx new file mode 100644 index 000000000000..b72f44cf5c95 --- /dev/null +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.test.tsx @@ -0,0 +1,129 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { UpdatePasswordModal } from './update_password_modal'; +import { wrapWithIntl } from 'test_utils/enzyme_helpers'; +import { OpenSearchDashboardsContextProvider } from '../../../../../../opensearch_dashboards_react/public'; +import { mockManagementPlugin } from '../../../../mocks'; + +const usernameIdentifier = '[data-test-subj="data-source-update-password-username"]'; +const confirmBtnIdentifier = '[data-test-subj="updateStoredPasswordConfirmBtn"]'; +const cancelBtnIdentifier = '[data-test-subj="updateStoredPasswordCancelBtn"]'; +const updatedPasswordFieldIdentifier = + '[data-test-subj="updateStoredPasswordUpdatedPasswordField"]'; +const confirmUpdatedPasswordFieldIdentifier = + '[data-test-subj="updateStoredPasswordConfirmUpdatedPasswordField"]'; +describe('Datasource Management: Update Stored Password Modal', () => { + let component: ReactWrapper, React.Component<{}, {}, any>>; + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + const mockUserName = 'test_user'; + const mockFn = jest.fn(); + + beforeEach(async () => { + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + component.update(); + }); + + test('should render normally', () => { + expect(component).toMatchSnapshot(); + expect(component.find(usernameIdentifier).last().text()).toBe(mockUserName); + expect(component.find(confirmBtnIdentifier).last().props().disabled).toBe(true); + }); + + test('should close modal when cancel button is clicked', () => { + component.find(cancelBtnIdentifier).last().simulate('click'); + expect(mockFn).toHaveBeenCalled(); + }); + + /* Validations */ + test('should show validation error on blur on Confirm Password field & remove existing error when input is provided and onblur is called', () => { + // @ts-ignore + component.find(updatedPasswordFieldIdentifier).last().simulate('focus').simulate('blur'); + component.update(); + expect(component.find(updatedPasswordFieldIdentifier).first().prop('isInvalid')).toBe(true); + expect(component.find(confirmBtnIdentifier).last().props().disabled).toBe(true); + + // @ts-ignore + component + .find(updatedPasswordFieldIdentifier) + .last() + .simulate('change', { target: { value: 'abc' } }) + .simulate('blur'); + expect(component.find(updatedPasswordFieldIdentifier).first().prop('isInvalid')).toBe(false); + }); + test('should show validation error on blur on Confirm Password fields & remove existing error when input is provided and onblur is called', () => { + /* Set updated Password field*/ + + // @ts-ignore + component + .find(updatedPasswordFieldIdentifier) + .last() + .simulate('change', { target: { value: 'abc' } }) + .simulate('blur'); + + // @ts-ignore + component.find(confirmUpdatedPasswordFieldIdentifier).last().simulate('blur'); + + expect(component.find(confirmUpdatedPasswordFieldIdentifier).first().prop('isInvalid')).toBe( + true + ); + + /* Password not match */ + // @ts-ignore + component + .find(confirmUpdatedPasswordFieldIdentifier) + .last() + .simulate('change', { target: { value: 'ab' } }) + .simulate('blur'); + expect(component.find(confirmUpdatedPasswordFieldIdentifier).first().prop('isInvalid')).toBe( + true + ); + + /* Valid passwords */ + // @ts-ignore + component + .find(confirmUpdatedPasswordFieldIdentifier) + .last() + .simulate('change', { target: { value: 'abc' } }) + .simulate('blur'); + expect(component.find(confirmUpdatedPasswordFieldIdentifier).first().prop('isInvalid')).toBe( + false + ); + }); + test('should update password when form is valid', () => { + // @ts-ignore + component + .find(updatedPasswordFieldIdentifier) + .last() + .simulate('change', { target: { value: 'abc' } }) + .simulate('blur'); + + component + .find(confirmUpdatedPasswordFieldIdentifier) + .last() + .simulate('change', { target: { value: 'abc' } }) + .simulate('blur'); + + expect(component.find(updatedPasswordFieldIdentifier).first().prop('isInvalid')).toBe(false); + component.find(confirmBtnIdentifier).last().simulate('click'); + expect(mockFn).toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx index aad6b8fd647c..95ca0abe6e90 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx @@ -102,7 +102,9 @@ export const UpdatePasswordModal = ({ defaultMessage: 'Username', })} > - {username} + + {username} + {/* updated Password */} - + { { + const mockedContext = mockManagementPlugin.createDataSourceManagementContext(); + let component: ReactWrapper, React.Component<{}, {}, any>>; + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + + describe('should fail to load resources', () => { + beforeEach(async () => { + spyOn(utils, 'getDataSources').and.throwError(''); + spyOn(utils, 'getDataSourceById').and.throwError(''); + await act(async () => { + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + }); + component.update(); + }); + + test('should NOT render normally', () => { + expect(utils.getDataSources).not.toHaveBeenCalled(); + expect(utils.getDataSourceById).toHaveBeenCalled(); + expect(history.push).toBeCalledWith(''); + }); + }); + + describe('should load resources successfully', () => { + beforeEach(async () => { + spyOn(utils, 'getDataSources').and.returnValue(Promise.resolve(getMappedDataSources)); + spyOn(utils, 'getDataSourceById').and.returnValue( + Promise.resolve(mockDataSourceAttributesWithAuth) + ); + await act(async () => { + component = mount( + wrapWithIntl( + + ), + { + wrappingComponent: OpenSearchDashboardsContextProvider, + wrappingComponentProps: { + services: mockedContext, + }, + } + ); + }); + component.update(); + }); + + test('should render normally', () => { + expect(component).toMatchSnapshot(); + expect(component.find(notFoundIdentifier).exists()).toBe(false); + expect(utils.getDataSources).toHaveBeenCalled(); + expect(utils.getDataSourceById).toHaveBeenCalled(); + }); + test('should update datasource successfully', async () => { + spyOn(utils, 'updateDataSourceById').and.returnValue({}); + + await act(async () => { + // @ts-ignore + await component.find(formIdentifier).first().prop('handleSubmit')( + mockDataSourceAttributesWithAuth + ); + }); + expect(utils.updateDataSourceById).toHaveBeenCalled(); + expect(history.push).toBeCalledWith(''); + }); + test('should fail to update datasource', async () => { + spyOn(utils, 'updateDataSourceById').and.throwError('error'); + await act(async () => { + // @ts-ignore + await component.find(formIdentifier).first().prop('handleSubmit')( + mockDataSourceAttributesWithAuth + ); + }); + component.update(); + expect(utils.updateDataSourceById).toHaveBeenCalled(); + }); + test('should delete datasource successfully', async () => { + spyOn(utils, 'deleteDataSourceById').and.returnValue({}); + + await act(async () => { + // @ts-ignore + await component.find(formIdentifier).first().prop('onDeleteDataSource')( + mockDataSourceAttributesWithAuth + ); + }); + expect(utils.deleteDataSourceById).toHaveBeenCalled(); + expect(history.push).toBeCalledWith(''); + }); + test('should fail to delete datasource', async () => { + spyOn(utils, 'deleteDataSourceById').and.throwError('error'); + await act(async () => { + // @ts-ignore + await component.find(formIdentifier).first().prop('onDeleteDataSource')( + mockDataSourceAttributesWithAuth + ); + }); + component.update(); + expect(utils.deleteDataSourceById).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx index 6c9c39939c51..24e6448df35a 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/edit_data_source.tsx @@ -32,7 +32,7 @@ const defaultDataSource: DataSourceAttributes = { }, }; -const EditDataSource: React.FunctionComponent> = ( +export const EditDataSource: React.FunctionComponent> = ( props: RouteComponentProps<{ id: string }> ) => { /* Initialization */ @@ -65,6 +65,7 @@ const EditDataSource: React.FunctionComponent { if (!isLoading && (!dataSource || !dataSource.id)) { - return

Data Source not found!

; + return

Data Source not found!

; } return ( <> diff --git a/src/plugins/data_source_management/public/mocks.ts b/src/plugins/data_source_management/public/mocks.ts index 2e0333121e28..c078247956e0 100644 --- a/src/plugins/data_source_management/public/mocks.ts +++ b/src/plugins/data_source_management/public/mocks.ts @@ -96,6 +96,14 @@ export const getDataSourcesResponse = { ], }; +export const existingDatasourceNamesList = [ + 'test123', + 'testTest20', + 'TeSt', + 'duplicateTest', + 'dup20', +]; + export const getMappedDataSources = [ { id: 'test', @@ -136,6 +144,17 @@ export const mockDataSourceAttributesWithAuth = { }, }, }; + +export const mockDataSourceAttributesWithNoAuth = { + id: 'test123', + title: 'create-test-ds123', + description: 'jest testing', + endpoint: 'https://test.com', + auth: { + type: AuthType.NoAuth, + credentials: undefined, + }, +}; export const getDataSourceByIdWithCredential = { attributes: { id: 'alpha-test',