diff --git a/x-pack/solutions/security/packages/connectors/src/connector_selector.test.tsx b/x-pack/solutions/security/packages/connectors/src/connector_selector.test.tsx new file mode 100644 index 0000000000000..a389634081ea6 --- /dev/null +++ b/x-pack/solutions/security/packages/connectors/src/connector_selector.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { ConnectorSelector } from './connector_selector'; + +const testSubj = 'connector-selector'; + +describe('ConnectorSelector', () => { + const mockOnChange = jest.fn(); + const mockOnNewConnectorClicked = jest.fn(); + + const connectors = [ + { id: '1', name: 'Connector One', description: 'First test connector' }, + { id: '2', name: 'Connector Two', description: 'Second test connector' }, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render with provided connectors', () => { + const { getByTestId } = render( + + ); + expect(getByTestId(testSubj)).toBeInTheDocument(); + }); + + it('should call onChange when a connector is selected', () => { + const { getByTestId } = render( + + ); + + fireEvent.click(getByTestId(testSubj)); + fireEvent.click(getByTestId('1')); + + expect(mockOnChange).toHaveBeenCalledWith('1'); + }); + + it('should show the add new connector option when onNewConnectorClicked is provided', () => { + const { getByTestId } = render( + + ); + fireEvent.click(getByTestId(testSubj)); + expect(getByTestId('addNewConnectorButton')).toBeInTheDocument(); + }); + + it('should call onNewConnectorClicked when add new connector is selected', () => { + const { getByTestId } = render( + + ); + fireEvent.click(getByTestId(testSubj)); + fireEvent.click(getByTestId('addNewConnectorButton')); + expect(mockOnNewConnectorClicked).toHaveBeenCalled(); + }); + + it('should disable selection when isDisabled is true', () => { + const { getByTestId } = render( + + ); + expect(getByTestId(testSubj)).toBeDisabled(); + }); + + it('should render a button to add a new connector when no connectors exist', () => { + const { getByTestId } = render( + + ); + expect(getByTestId('addNewConnectorButton')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.test.tsx new file mode 100644 index 0000000000000..21be2f596728a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_cards.test.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ConnectorCards } from './connector_cards'; +import { useLoadActionTypes } from '@kbn/elastic-assistant/impl/connectorland/use_load_action_types'; +import type { AIConnector } from './types'; + +jest.mock('@kbn/elastic-assistant/impl/connectorland/use_load_action_types'); +jest.mock('@kbn/elastic-assistant/impl/connectorland/use_load_action_types', () => ({ + useLoadActionTypes: jest.fn(), +})); + +jest.mock('../../../../../../common/lib/kibana/kibana_react', () => ({ + useKibana: () => ({ + services: { + http: { + get: jest.fn(), + }, + notifications: { + toasts: { + addDanger: jest.fn(), + addSuccess: jest.fn(), + }, + }, + triggersActionsUi: { + actionTypeRegistry: { + get: jest.fn(() => ({ iconClass: 'testIcon' })), + }, + }, + }, + }), +})); + +const mockConnectors: AIConnector[] = [ + { + id: '1', + name: 'Connector 1', + actionTypeId: 'testType', + isPreconfigured: false, + isSystemAction: false, + isDeprecated: false, + config: {}, + secrets: {}, + }, + { + id: '2', + name: 'Connector 2', + actionTypeId: 'testType', + isPreconfigured: false, + isSystemAction: false, + isDeprecated: false, + config: {}, + secrets: {}, + }, +]; + +describe('ConnectorCards', () => { + beforeEach(() => { + (useLoadActionTypes as jest.Mock).mockReturnValue({ + data: [ + { + id: 'testType1', + name: 'Test Action 1', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + isSystemActionType: false, + }, + ], + isLoading: false, + error: null, + }); + }); + + it('renders a loading spinner when connectors are not provided', () => { + render( + + ); + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); + + it('calls onConnectorSelected when a connector is selected', async () => { + const onConnectorSelected = jest.fn(); + render( + + ); + + await userEvent.click(screen.getByRole('button', { name: /Connector Selector/i })); + await userEvent.click(screen.getByText('Connector 1')); + expect(onConnectorSelected).toHaveBeenCalledWith(mockConnectors[0]); + }); + + it('shows missing privileges callout if user lacks privileges and has no connectors', () => { + render( + + ); + expect(screen.getByText('Missing privileges')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.test.tsx new file mode 100644 index 0000000000000..5c05ed008ad3c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_selector_panel.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ConnectorSelectorPanel } from './connector_selector_panel'; +import type { AIConnector } from './types'; + +const mockConnectors: AIConnector[] = [ + { + id: '1', + name: 'Connector 1', + actionTypeId: 'testType', + isPreconfigured: false, + isSystemAction: false, + isDeprecated: false, + config: {}, + secrets: {}, + }, + { + id: '2', + name: 'Connector 2', + actionTypeId: 'testType', + isPreconfigured: false, + isSystemAction: false, + isDeprecated: false, + config: {}, + secrets: {}, + }, +]; + +const mockActionTypeRegistry = { + get: jest.fn(() => ({ iconClass: 'testIcon', name: 'Test Action' })), +}; + +jest.mock('../../../../../../common/lib/kibana/kibana_react', () => ({ + useKibana: () => ({ + services: { triggersActionsUi: { actionTypeRegistry: mockActionTypeRegistry } }, + }), +})); + +describe('ConnectorSelectorPanel', () => { + it('renders correctly', () => { + render(); + expect(screen.getByText('Selected provider')).toBeInTheDocument(); + }); + + it('preselects the only connector if there is one', () => { + const onConnectorSelected = jest.fn(); + render( + + ); + expect(onConnectorSelected).toHaveBeenCalledWith(mockConnectors[0]); + }); + + it('calls onConnectorSelected when a connector is selected', async () => { + const onConnectorSelected = jest.fn(); + render( + + ); + await userEvent.click(screen.getByRole('button', { name: /Connector Selector/i })); + await userEvent.click(screen.getByText('Connector 2')); + expect(onConnectorSelected).toHaveBeenCalledWith(mockConnectors[1]); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.test.tsx new file mode 100644 index 0000000000000..b15ce466568f5 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/connectors/connector_setup.test.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ConnectorSetup } from './connector_setup'; +import type { ActionType } from '@kbn/actions-plugin/common'; +import { useKibana } from '../../../../../../common/lib/kibana'; + +jest.mock('../../../../../../common/lib/kibana', () => ({ + useKibana: jest.fn(), +})); + +jest.mock('@kbn/elastic-assistant/impl/connectorland/add_connector_modal', () => ({ + AddConnectorModal: jest.fn(() =>
{'Mock Modal'}
), +})); + +describe('ConnectorSetup', () => { + const mockActionTypeRegistry = { + get: jest.fn(() => ({ iconClass: 'testIcon' })), + }; + + const mockActionTypes: ActionType[] = [ + { + id: 'testType1', + name: 'Test Action 1', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + isSystemActionType: false, + }, + { + id: 'testType2', + name: 'Test Action 2', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'gold', + supportedFeatureIds: ['alerting'], + isSystemActionType: false, + }, + ]; + + beforeEach(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + triggersActionsUi: { actionTypeRegistry: mockActionTypeRegistry }, + }, + }); + }); + + it('renders correctly', () => { + render(); + + expect(mockActionTypeRegistry.get).toHaveBeenCalledWith('testType1'); + expect(mockActionTypeRegistry.get).toHaveBeenCalledWith('testType2'); + + expect(screen.getByRole('button', { name: 'AI service provider' })).toBeInTheDocument(); + }); + + it('opens the modal when the button is clicked', async () => { + render(); + + await userEvent.click(screen.getByRole('button', { name: 'AI service provider' })); + + expect(screen.getByTestId('addConnectorModal')).toBeInTheDocument(); + }); + + it('calls onConnectorSaved when a connector is saved', async () => { + const mockOnConnectorSaved = jest.fn(); + render( + + ); + await userEvent.click(screen.getByRole('button', { name: 'AI service provider' })); + + mockOnConnectorSaved({ id: '1', name: 'New Connector' }); + + expect(mockOnConnectorSaved).toHaveBeenCalledWith({ id: '1', name: 'New Connector' }); + }); +});