diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 905ed88d476..92c50bf446e 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -1,49 +1,35 @@ -import { render, waitFor } from '@testing-library/react'; -import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; import React from 'react'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { render, waitFor, userEvent } from 'test/test-utils'; import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector'; import { dateTime } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService, setDataSourceSrv } from '@grafana/runtime'; -import { contextSrv } from 'app/core/services/context_srv'; -import { AlertState, MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; -import { SilenceState } from '../../../plugins/datasource/alertmanager/types'; - import Silences from './Silences'; -import { createOrUpdateSilence, fetchAlerts, fetchSilences } from './api/alertmanager'; -import { grantUserPermissions, mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv, mockSilence } from './mocks'; +import { grantUserPermissions, mockDataSource, MockDataSourceSrv } from './mocks'; import { AlertmanagerProvider } from './state/AlertmanagerContext'; import { setupDataSources } from './testSetup/datasources'; -import { parseMatchers } from './utils/alertmanager'; import { DataSourceType } from './utils/datasource'; -jest.mock('./api/alertmanager'); jest.mock('app/core/services/context_srv'); const TEST_TIMEOUT = 60000; -const mocks = { - api: { - fetchSilences: jest.mocked(fetchSilences), - fetchAlerts: jest.mocked(fetchAlerts), - createOrUpdateSilence: jest.mocked(createOrUpdateSilence), - }, - contextSrv: jest.mocked(contextSrv), -}; - const renderSilences = (location = '/alerting/silences/') => { locationService.push(location); - return render( - - - - - + + + , + { + routerOptions: { + initialEntries: [location], + }, + } ); }; @@ -57,7 +43,8 @@ const dataSources = { const ui = { notExpiredTable: byTestId('not-expired-table'), expiredTable: byTestId('expired-table'), - expiredCaret: byText(/expired/i), + expiredCaret: byText(/expired silences \(/i), + silencesTags: byLabelText(/tags/i), silenceRow: byTestId('row'), silencedAlertCell: byTestId('alerts'), addSilenceButton: byRole('link', { name: /add silence/i }), @@ -80,28 +67,6 @@ const ui = { const resetMocks = () => { jest.resetAllMocks(); - mocks.api.fetchSilences.mockImplementation(() => { - return Promise.resolve([ - mockSilence({ id: '12345' }), - mockSilence({ id: '67890', matchers: parseMatchers('foo!=bar'), comment: 'Catch all' }), - mockSilence({ id: '1111', status: { state: SilenceState.Expired } }), - ]); - }); - - mocks.api.fetchAlerts.mockImplementation(() => { - return Promise.resolve([ - mockAlertmanagerAlert({ - labels: { foo: 'bar' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - mockAlertmanagerAlert({ - labels: { foo: 'buzz' }, - status: { state: AlertState.Suppressed, silencedBy: ['67890'], inhibitedBy: [] }, - }), - ]); - }); - - mocks.api.createOrUpdateSilence.mockResolvedValue(mockSilence()); grantUserPermissions([ AccessControlAction.AlertingInstanceRead, @@ -117,6 +82,21 @@ const setUserLogged = (isLogged: boolean) => { config.bootData.user.name = isLogged ? 'admin' : ''; }; +const enterSilenceLabel = async (index: number, name: string, matcher: MatcherOperator, value: string) => { + const user = userEvent.setup(); + await user.type(ui.editor.matcherName.getAll()[index], name); + await user.type(ui.editor.matcherOperatorSelect.getAll()[index], matcher); + await user.tab(); + await user.type(ui.editor.matcherValue.getAll()[index], value); +}; + +const addAdditionalMatcher = async () => { + const user = userEvent.setup(); + await user.click(ui.editor.addMatcherButton.get()); +}; + +setupMswServer(); + describe('Silences', () => { beforeAll(resetMocks); afterEach(resetMocks); @@ -128,26 +108,29 @@ describe('Silences', () => { it( 'loads and shows silences', async () => { + const user = userEvent.setup(); renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - await userEvent.click(ui.expiredCaret.get()); - expect(ui.notExpiredTable.get()).not.toBeNull(); - expect(ui.expiredTable.get()).not.toBeNull(); - let silences = ui.silenceRow.queryAll(); - expect(silences).toHaveLength(3); - expect(silences[0]).toHaveTextContent('foo=bar'); - expect(silences[1]).toHaveTextContent('foo!=bar'); - expect(silences[2]).toHaveTextContent('foo=bar'); + expect(await ui.notExpiredTable.find()).toBeInTheDocument(); - await userEvent.click(ui.expiredCaret.getAll()[0]); - expect(ui.notExpiredTable.get()).not.toBeNull(); - expect(ui.expiredTable.query()).toBeNull(); - silences = ui.silenceRow.queryAll(); - expect(silences).toHaveLength(2); - expect(silences[0]).toHaveTextContent('foo=bar'); - expect(silences[1]).toHaveTextContent('foo!=bar'); + await user.click(ui.expiredCaret.get()); + expect(ui.expiredTable.get()).toBeInTheDocument(); + + const allSilences = ui.silenceRow.queryAll(); + expect(allSilences).toHaveLength(3); + expect(allSilences[0]).toHaveTextContent('foo=bar'); + expect(allSilences[1]).toHaveTextContent('foo!=bar'); + expect(allSilences[2]).toHaveTextContent('foo=bar'); + + await user.click(ui.expiredCaret.get()); + + expect(ui.notExpiredTable.get()).toBeInTheDocument(); + expect(ui.expiredTable.query()).not.toBeInTheDocument(); + + const activeSilences = ui.silenceRow.queryAll(); + expect(activeSilences).toHaveLength(2); + expect(activeSilences[0]).toHaveTextContent('foo=bar'); + expect(activeSilences[1]).toHaveTextContent('foo!=bar'); }, TEST_TIMEOUT ); @@ -155,25 +138,13 @@ describe('Silences', () => { it( 'shows the correct number of silenced alerts', async () => { - mocks.api.fetchAlerts.mockImplementation(() => { - return Promise.resolve([ - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - ]); - }); - renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - const silencedAlertRows = ui.silencedAlertCell.getAll(ui.notExpiredTable.get()); - expect(silencedAlertRows).toHaveLength(2); + const notExpiredTable = await ui.notExpiredTable.find(); + + expect(notExpiredTable).toBeInTheDocument(); + + const silencedAlertRows = await ui.silencedAlertCell.findAll(notExpiredTable); expect(silencedAlertRows[0]).toHaveTextContent('2'); expect(silencedAlertRows[1]).toHaveTextContent('0'); }, @@ -184,12 +155,9 @@ describe('Silences', () => { 'filters silences by matchers', async () => { renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - const queryBar = ui.queryBar.get(); - await userEvent.click(queryBar); - await userEvent.paste('foo=bar'); + const queryBar = await ui.queryBar.find(); + await userEvent.type(queryBar, 'foo=bar'); await waitFor(() => expect(ui.silenceRow.getAll()).toHaveLength(2)); }, @@ -199,24 +167,23 @@ describe('Silences', () => { it('shows creating a silence button for users with access', async () => { renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - - expect(ui.addSilenceButton.get()).toBeInTheDocument(); + expect(await ui.addSilenceButton.find()).toBeInTheDocument(); }); it('hides actions for creating a silence for users without access', async () => { grantUserPermissions([AccessControlAction.AlertingInstanceRead, AccessControlAction.AlertingInstancesExternalRead]); renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); + + const notExpiredTable = await ui.notExpiredTable.find(); + + expect(notExpiredTable).toBeInTheDocument(); expect(ui.addSilenceButton.query()).not.toBeInTheDocument(); }); }); -describe('Silence edit', () => { +describe('Silence create/edit', () => { const baseUrlPath = '/alerting/silence/new'; beforeAll(resetMocks); afterEach(resetMocks); @@ -242,7 +209,7 @@ describe('Silence edit', () => { const matchersQueryString = matchersParams.map((matcher) => `matcher=${encodeURIComponent(matcher)}`).join('&'); renderSilences(`${baseUrlPath}?${matchersQueryString}`); - await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); + expect(await ui.editor.durationField.find()).toBeInTheDocument(); const matchers = ui.editor.matchersField.queryAll(); expect(matchers).toHaveLength(4); @@ -270,7 +237,7 @@ describe('Silence edit', () => { 'creates a new silence', async () => { renderSilences(baseUrlPath); - await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); + expect(await ui.editor.durationField.find()).toBeInTheDocument(); const start = new Date(); const end = new Date(start.getTime() + 24 * 60 * 60 * 1000); @@ -285,48 +252,20 @@ describe('Silence edit', () => { await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(startDateString)); await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(endDateString)); - await userEvent.type(ui.editor.matcherName.get(), 'foo'); - await userEvent.type(ui.editor.matcherOperatorSelect.get(), '='); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.get(), 'bar'); + await enterSilenceLabel(0, 'foo', MatcherOperator.equal, 'bar'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - await userEvent.type(ui.editor.matcherName.getAll()[1], 'bar'); - await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[1], '!='); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.getAll()[1], 'buzz'); + await addAdditionalMatcher(); + await enterSilenceLabel(1, 'bar', MatcherOperator.notEqual, 'buzz'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - await userEvent.type(ui.editor.matcherName.getAll()[2], 'region'); - await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[2], '=~'); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.getAll()[2], 'us-west-.*'); + await addAdditionalMatcher(); + await enterSilenceLabel(2, 'region', MatcherOperator.regex, 'us-west-.*'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - await userEvent.type(ui.editor.matcherName.getAll()[3], 'env'); - await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[3], '!~'); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.getAll()[3], 'dev|staging'); + await addAdditionalMatcher(); + await enterSilenceLabel(3, 'env', MatcherOperator.notRegex, 'dev|staging'); await userEvent.click(ui.editor.submit.get()); - await waitFor(() => - expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( - 'grafana', - expect.objectContaining({ - comment: expect.stringMatching(/created (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/), - matchers: [ - { isEqual: true, isRegex: false, name: 'foo', value: 'bar' }, - { isEqual: false, isRegex: false, name: 'bar', value: 'buzz' }, - { isEqual: true, isRegex: true, name: 'region', value: 'us-west-.*' }, - { isEqual: false, isRegex: true, name: 'env', value: 'dev|staging' }, - ], - }) - ) - ); + expect(await ui.notExpiredTable.find()).toBeInTheDocument(); }, TEST_TIMEOUT ); @@ -339,20 +278,11 @@ describe('Silence edit', () => { renderSilences(`${baseUrlPath}?alertmanager=Alertmanager`); await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); - await user.type(ui.editor.matcherName.getAll()[0], 'foo'); - await user.type(ui.editor.matcherOperatorSelect.getAll()[0], '='); - await user.type(ui.editor.matcherValue.getAll()[0], 'bar'); + await enterSilenceLabel(0, 'foo', MatcherOperator.equal, 'bar'); await user.click(ui.editor.submit.get()); - await waitFor(() => - expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( - 'Alertmanager', - expect.objectContaining({ - matchers: [{ isEqual: true, isRegex: false, name: 'foo', value: 'bar' }], - }) - ) - ); + expect(await ui.notExpiredTable.find()).toBeInTheDocument(); expect(locationService.getSearch().get('alertmanager')).toBe('Alertmanager'); },