From 8acc9b2e5b1e4e86e64c5c677b5323791e63febb Mon Sep 17 00:00:00 2001 From: Remi Date: Fri, 5 Feb 2021 20:10:49 +0100 Subject: [PATCH] tests(splunk-on-call-plugin): add tests --- .../components/Escalation/Escalation.test.tsx | 124 +++++++++++++++ .../components/Incident/Incidents.test.tsx | 150 ++++++++++++++++++ .../src/components/SplunkOnCallCard.test.tsx | 147 +++++++++++++++++ .../TriggerDialog/TriggerDialog.test.tsx | 128 +++++++++++++++ 4 files changed, 549 insertions(+) create mode 100644 plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx create mode 100644 plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx create mode 100644 plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx create mode 100644 plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx diff --git a/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx b/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx new file mode 100644 index 0000000000..305d29b8b8 --- /dev/null +++ b/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx @@ -0,0 +1,124 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { EscalationPolicy } from './EscalationPolicy'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { User } from '../types'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { splunkOnCallApiRef } from '../../api'; + +const MOCKED_USER: User = { + createdAt: '2021-02-01T23:38:38Z', + displayName: 'Test User', + email: 'test@example.com', + firstName: 'FirstNameTest', + lastName: 'LastNameTest', + passwordLastUpdated: '2021-02-01T23:38:38Z', + username: 'test_user', + verified: true, + _selfUrl: '/api-public/v1/user/test_user', +}; + +const MOCKED_ON_CALL = [ + { + team: { name: 'team_example', slug: 'team-zEalMCgwYSA0Lt40' }, + oncallNow: [ + { + escalationPolicy: { name: 'Example', slug: 'team-zEalMCgwYSA0Lt40' }, + users: [{ onCalluser: { username: 'test_user' } }], + }, + ], + }, +]; + +const mockSplunkOnCallApi = { + getOnCallUsers: () => [], +}; +const apis = ApiRegistry.from([[splunkOnCallApiRef, mockSplunkOnCallApi]]); + +describe('Escalation', () => { + it('Handles an empty response', async () => { + mockSplunkOnCallApi.getOnCallUsers = jest + .fn() + .mockImplementationOnce(async () => []); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + + expect(getByText('Empty escalation policy')).toBeInTheDocument(); + expect(mockSplunkOnCallApi.getOnCallUsers).toHaveBeenCalled(); + }); + + it('Render a list of users', async () => { + mockSplunkOnCallApi.getOnCallUsers = jest + .fn() + .mockImplementationOnce(async () => MOCKED_ON_CALL); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + + expect(getByText('FirstNameTest LastNameTest')).toBeInTheDocument(); + expect(getByText('test@example.com')).toBeInTheDocument(); + expect(mockSplunkOnCallApi.getOnCallUsers).toHaveBeenCalled(); + }); + + it('Handles errors', async () => { + mockSplunkOnCallApi.getOnCallUsers = jest + .fn() + .mockRejectedValueOnce(new Error('Error message')); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + + expect( + getByText('Error encountered while fetching information. Error message'), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx b/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx new file mode 100644 index 0000000000..51bbe50aef --- /dev/null +++ b/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { Incidents } from './Incidents'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { + ApiProvider, + ApiRegistry, + IdentityApi, + identityApiRef, +} from '@backstage/core'; +import { splunkOnCallApiRef } from '../../api'; +import { Incident, Team } from '../types'; + +const MOCK_INCIDENT: Incident = { + alertCount: 1, + currentPhase: 'ACKED', + entityDisplayName: 'test-incident', + entityId: 'entityId', + entityState: 'CRITICAL', + entityType: 'SERVICE', + incidentNumber: '1', + lastAlertId: 'lastAlertId', + lastAlertTime: '2021-02-03T00:13:11Z', + routingKey: 'routingdefault', + service: 'test', + startTime: '2021-02-03T00:13:11Z', + pagedTeams: ['team-O9SqT13fsnCstjMi'], + pagedUsers: [], + pagedPolicies: [ + { + policy: { + name: 'Generated Direct User Policy for test', + slug: 'directUserPolicySlug-test', + _selfUrl: '/test', + }, + }, + ], + transitions: [{ name: 'ACKED', at: '2021-02-03T01:20:00Z', by: 'test' }], + monitorName: 'vouser-user', + monitorType: 'Manual', + firstAlertUuid: 'firstAlertUuid', + incidentLink: 'https://portal.victorops.com/example', +}; + +const MOCK_TEAM: Team = { + _selfUrl: '/api-public/v1/team/team-O9SqT13fsnCstjMi', + _membersUrl: '/api-public/v1/team/team-O9SqT13fsnCstjMi/members', + _policiesUrl: '/api-public/v1/team/team-O9SqT13fsnCstjMi/policies', + _adminsUrl: '/api-public/v1/team/team-O9SqT13fsnCstjMi/admins', + name: 'test', + slug: 'team-O9SqT13fsnCstjMi', + memberCount: 1, + version: 1, + isDefaultTeam: false, +}; + +const mockIdentityApi: Partial = { + getUserId: () => 'test', +}; + +const mockSplunkOnCallApi = { + getIncidents: () => [], + getTeams: () => [], +}; +const apis = ApiRegistry.from([ + [identityApiRef, mockIdentityApi], + [splunkOnCallApiRef, mockSplunkOnCallApi], +]); + +describe('Incidents', () => { + it('Renders an empty state when there are no incidents', async () => { + mockSplunkOnCallApi.getTeams = jest + .fn() + .mockImplementationOnce(async () => [MOCK_TEAM]); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('Nice! No incidents found!')).toBeInTheDocument(); + }); + + it('Renders all incidents', async () => { + mockSplunkOnCallApi.getIncidents = jest + .fn() + .mockImplementationOnce(async () => [MOCK_INCIDENT]); + + mockSplunkOnCallApi.getTeams = jest + .fn() + .mockImplementationOnce(async () => [MOCK_TEAM]); + const { + getByText, + getByTitle, + getAllByTitle, + getByLabelText, + queryByTestId, + } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('user')).toBeInTheDocument(); + expect(getByText('test-incident')).toBeInTheDocument(); + expect(getByTitle('ACKED')).toBeInTheDocument(); + expect(getByLabelText('Status warning')).toBeInTheDocument(); + + // assert links, mailto and hrefs, date calculation + expect(getAllByTitle('View in Splunk On-Call').length).toEqual(1); + }); + + it('Handle errors', async () => { + mockSplunkOnCallApi.getIncidents = jest + .fn() + .mockRejectedValueOnce(new Error('Error occurred')); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect( + getByText('Error encountered while fetching information. Error occurred'), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx b/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx new file mode 100644 index 0000000000..9ca61614a4 --- /dev/null +++ b/plugins/splunk-on-call/src/components/SplunkOnCallCard.test.tsx @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { render, waitFor, fireEvent, act } from '@testing-library/react'; +import { SplunkOnCallCard } from './SplunkOnCallCard'; +import { Entity } from '@backstage/catalog-model'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { + alertApiRef, + ApiProvider, + ApiRegistry, + createApiRef, +} from '@backstage/core'; +import { + splunkOnCallApiRef, + UnauthorizedError, + SplunkOnCallClient, +} from '../api'; +import { User } from './types'; + +const MOCKED_USER: User = { + createdAt: '2021-02-01T23:38:38Z', + displayName: 'Test User', + email: 'remi.d45@gmail.com', + firstName: 'FirstNameTest', + lastName: 'LastNameTest', + passwordLastUpdated: '2021-02-01T23:38:38Z', + username: 'test_user', + verified: true, + _selfUrl: '/api-public/v1/user/test_user', +}; + +const mockSplunkOnCallApi: Partial = { + getUsers: async () => [], +}; + +const apis = ApiRegistry.from([ + [splunkOnCallApiRef, mockSplunkOnCallApi], + [ + alertApiRef, + createApiRef({ + id: 'core.alert', + description: 'Used to report alerts and forward them to the app', + }), + ], +]); +const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'splunkoncall-test', + annotations: { + 'splunkoncall.com/integration-key': 'abc123', + }, + }, +}; + +describe('PageDutyCard', () => { + it('Render splunkoncall', async () => { + mockSplunkOnCallApi.getUsers = jest + .fn() + .mockImplementationOnce(async () => [MOCKED_USER]); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('Create Incident')).toBeInTheDocument(); + expect(getByText('Nice! No incidents found!')).toBeInTheDocument(); + expect(getByText('Empty escalation policy')).toBeInTheDocument(); + }); + + it('Handles custom error for missing token', async () => { + mockSplunkOnCallApi.getUsers = jest + .fn() + .mockRejectedValueOnce(new UnauthorizedError()); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect( + getByText('Missing or invalid SplunkOnCall Token'), + ).toBeInTheDocument(); + }); + + it('handles general error', async () => { + mockSplunkOnCallApi.getUsers = jest + .fn() + .mockRejectedValueOnce(new Error('An error occurred')); + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + + expect( + getByText( + 'Error encountered while fetching information. An error occurred', + ), + ).toBeInTheDocument(); + }); + it('opens the dialog when trigger button is clicked', async () => { + mockSplunkOnCallApi.getUsers = jest + .fn() + .mockImplementationOnce(async () => [MOCKED_USER]); + + const { getByText, queryByTestId, getByTestId, getByRole } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(getByText('Create Incident')).toBeInTheDocument(); + const triggerButton = getByTestId('trigger-button'); + await act(async () => { + fireEvent.click(triggerButton); + }); + expect(getByRole('dialog')).toBeInTheDocument(); + }); +}); diff --git a/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx new file mode 100644 index 0000000000..8ffe55fe01 --- /dev/null +++ b/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -0,0 +1,128 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { render, fireEvent, act } from '@testing-library/react'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { + ApiRegistry, + alertApiRef, + createApiRef, + ApiProvider, + IdentityApi, + identityApiRef, +} from '@backstage/core'; +import { splunkOnCallApiRef } from '../../api'; +import { TriggerDialog } from './TriggerDialog'; +import { User } from '../types'; + +const MOCKED_USER: User = { + createdAt: '2021-02-01T23:38:38Z', + displayName: 'Test User', + email: 'remi.d45@gmail.com', + firstName: 'FirstNameTest', + lastName: 'LastNameTest', + passwordLastUpdated: '2021-02-01T23:38:38Z', + username: 'test_user', + verified: true, + _selfUrl: '/api-public/v1/user/test_user', +}; + +describe('TriggerDialog', () => { + const mockIdentityApi: Partial = { + getUserId: () => 'guest@example.com', + }; + + const mockTriggerAlarmFn = jest.fn(); + const mockSplunkOnCallApi = { + triggerAlarm: mockTriggerAlarmFn, + getEscalationPolicies: async () => ({ + policies: [ + { + policy: { + name: 'Example', + slug: 'team-zEalMCgwYSA0Lt40', + _selfUrl: '/api-public/v1/policies/team-zEalMCgwYSA0Lt40', + }, + team: { name: 'Example', slug: 'team-zEalMCgwYSA0Lt40' }, + }, + ], + }), + }; + + const apis = ApiRegistry.from([ + [ + alertApiRef, + createApiRef({ + id: 'core.alert', + description: 'Used to report alerts and forward them to the app', + }), + ], + [identityApiRef, mockIdentityApi], + [splunkOnCallApiRef, mockSplunkOnCallApi], + ]); + + it('open the dialog and trigger an alarm', async () => { + const { getByText, getByRole, getByTestId } = render( + wrapInTestApp( + + {}} + users={[MOCKED_USER]} + onIncidentCreated={() => {}} + /> + , + ), + ); + + expect(getByRole('dialog')).toBeInTheDocument(); + expect( + getByText('This action will trigger an incident', { + exact: false, + }), + ).toBeInTheDocument(); + const summary = getByTestId('trigger-summary-input'); + const body = getByTestId('trigger-body-input'); + const userTarget = getByTestId('trigger-select-user-target'); + const behavior = getByTestId('trigger-select-behavior'); + const policiesTarget = getByTestId('trigger-select-policies-target'); + const description = 'Test Trigger Alarm'; + await act(async () => { + fireEvent.change(summary, { target: { value: description } }); + fireEvent.change(body, { target: { value: description } }); + fireEvent.change(userTarget, { target: { value: ['test_user'] } }); + fireEvent.change(behavior, { target: { value: '0' } }); + fireEvent.change(policiesTarget, { + target: { value: ['team-zEalMCgwYSA0Lt40'] }, + }); + }); + const triggerButton = getByTestId('trigger-button'); + await act(async () => { + fireEvent.click(triggerButton); + }); + expect(mockTriggerAlarmFn).toHaveBeenCalled(); + expect(mockTriggerAlarmFn).toHaveBeenCalledWith({ + summary: description, + details: description, + userName: 'guest@example.com', + targets: [ + { slug: 'test_user', type: 'User' }, + { slug: 'team-zEalMCgwYSA0Lt40', type: 'EscalationPolicy' }, + ], + isMultiResponder: false, + }); + }); +});