Files
grafana/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.v1.test.tsx
2023-11-28 11:48:42 +02:00

378 lines
12 KiB
TypeScript

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { byRole, byText } from 'testing-library-selector';
import { PluginExtensionTypes } from '@grafana/data';
import { getPluginLinkExtensions, locationService, setBackendSrv } from '@grafana/runtime';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { backendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/services/context_srv';
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
import { CombinedRule } from 'app/types/unified-alerting';
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
import { discoverFeatures } from '../../api/buildInfo';
import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities';
import { mockAlertRuleApi, setupMswServer } from '../../mockApi';
import {
getCloudRule,
getGrafanaRule,
grantUserPermissions,
mockDataSource,
mockPromAlertingRule,
mockRulerAlertingRule,
promRuleFromRulerRule,
} from '../../mocks';
import { mockAlertmanagerChoiceResponse } from '../../mocks/alertmanagerApi';
import { mockPluginSettings } from '../../mocks/plugins';
import { setupDataSources } from '../../testSetup/datasources';
import { SupportedPlugin } from '../../types/pluginBridges';
import * as ruleId from '../../utils/rule-id';
import { RuleViewer } from './RuleViewer.v1';
const mockGrafanaRule = getGrafanaRule({ name: 'Test alert' }, { uid: 'test1', title: 'Test alert' });
const mockCloudRule = getCloudRule({ name: 'cloud test alert' });
const mockRoute = (id?: string): GrafanaRouteComponentProps<{ id?: string; sourceName?: string }> => ({
route: {
path: '/',
component: RuleViewer,
},
queryParams: { returnTo: '/alerting/list' },
match: { params: { id: id ?? 'test1', sourceName: 'grafana' }, isExact: false, url: 'asdf', path: '' },
history: locationService.getHistory(),
location: { pathname: '', hash: '', search: '', state: '' },
staticContext: {},
});
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getPluginLinkExtensions: jest.fn(),
}));
jest.mock('../../hooks/useAbilities');
jest.mock('../../api/buildInfo');
const mocks = {
getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions),
useAlertRuleAbility: jest.mocked(useAlertRuleAbility),
};
const ui = {
actionButtons: {
edit: byRole('link', { name: /edit/i }),
silence: byRole('link', { name: 'Silence' }),
},
moreButton: byRole('button', { name: /More/i }),
moreButtons: {
duplicate: byRole('menuitem', { name: /^Duplicate$/i }),
delete: byRole('menuitem', { name: /delete/i }),
},
loadingIndicator: byText(/Loading rule/i),
};
const renderRuleViewer = async (ruleId: string) => {
locationService.push(`/alerting/grafana/${ruleId}/view`);
render(
<TestProvider>
<RuleViewer {...mockRoute(ruleId)} />
</TestProvider>
);
await waitFor(() => expect(ui.loadingIndicator.query()).not.toBeInTheDocument());
};
const server = setupMswServer();
const user = userEvent.setup();
const dsName = 'prometheus';
const rulerRule = mockRulerAlertingRule({ alert: 'cloud test alert' });
const rulerRuleIdentifier = ruleId.fromRulerRule('prometheus', 'ns-default', 'group-default', rulerRule);
beforeAll(() => {
setBackendSrv(backendSrv);
const promDsSettings = mockDataSource({
name: dsName,
uid: dsName,
});
setupDataSources(promDsSettings);
});
beforeEach(() => {
// some action buttons need to check what Alertmanager setup we have for Grafana managed rules
mockAlertmanagerChoiceResponse(server, {
alertmanagersChoice: AlertmanagerChoice.Internal,
numExternalAlertmanagers: 1,
});
// we need to mock this one for the "declare incident" button
mockPluginSettings(server, SupportedPlugin.Incident);
mockAlertRuleApi(server).rulerRules('grafana', {
[mockGrafanaRule.namespace.name]: [
{ name: mockGrafanaRule.group.name, interval: '1m', rules: [mockGrafanaRule.rulerRule!] },
],
});
const { name, query, labels, annotations } = mockGrafanaRule;
mockAlertRuleApi(server).prometheusRuleNamespaces('grafana', {
data: {
groups: [
{
file: mockGrafanaRule.namespace.name,
interval: 60,
name: mockGrafanaRule.group.name,
rules: [mockPromAlertingRule({ name, query, labels, annotations })],
},
],
},
status: 'success',
});
mockAlertRuleApi(server).rulerRuleGroup(dsName, 'ns-default', 'group-default', {
name: 'group-default',
interval: '1m',
rules: [rulerRule],
});
mockAlertRuleApi(server).prometheusRuleNamespaces(dsName, {
data: {
groups: [
{
file: 'ns-default',
interval: 60,
name: 'group-default',
rules: [promRuleFromRulerRule(rulerRule, { state: PromAlertingRuleState.Inactive })],
},
],
},
status: 'success',
});
mocks.getPluginLinkExtensionsMock.mockReturnValue({
extensions: [
{
pluginId: 'grafana-ml-app',
id: '1',
type: PluginExtensionTypes.link,
title: 'Run investigation',
category: 'Sift',
description: 'Run a Sift investigation for this alert',
onClick: jest.fn(),
},
],
});
});
describe('RuleViewer', () => {
let mockCombinedRule = jest.fn();
afterEach(() => {
mockCombinedRule.mockReset();
});
it('should render page with grafana alert', async () => {
mocks.useAlertRuleAbility.mockReturnValue([true, true]);
await renderRuleViewer('test1');
expect(screen.getByText(/test alert/i)).toBeInTheDocument();
});
it('should render page with cloud alert', async () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
jest
.mocked(discoverFeatures)
.mockResolvedValue({ application: PromApplication.Mimir, features: { rulerApiEnabled: true } });
mocks.useAlertRuleAbility.mockReturnValue([true, true]);
await renderRuleViewer(ruleId.stringifyIdentifier(rulerRuleIdentifier));
expect(screen.getByText(/cloud test alert/i)).toBeInTheDocument();
});
});
describe('RuleDetails RBAC', () => {
describe('Grafana rules action buttons in details', () => {
let mockCombinedRule = jest.fn();
beforeEach(() => {
// mockCombinedRule = jest.mocked(useCombinedRule);
});
afterEach(() => {
mockCombinedRule.mockReset();
});
it('Should render Edit button for users with the update permission', async () => {
// Arrange
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false];
});
mockCombinedRule.mockReturnValue({
result: mockGrafanaRule as CombinedRule,
loading: false,
dispatched: true,
requestId: 'A',
error: undefined,
});
// Act
await renderRuleViewer('test1');
// Assert
expect(ui.actionButtons.edit.get()).toBeInTheDocument();
});
it('Should render Delete button for users with the delete permission', async () => {
// Arrange
mockCombinedRule.mockReturnValue({
result: mockGrafanaRule as CombinedRule,
loading: false,
dispatched: true,
requestId: 'A',
error: undefined,
});
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false];
});
// Act
await renderRuleViewer('test1');
await user.click(ui.moreButton.get());
// Assert
expect(ui.moreButtons.delete.get()).toBeInTheDocument();
});
it('Should not render Silence button for users wihout the instance create permission', async () => {
// Arrange
mockCombinedRule.mockReturnValue({
result: mockGrafanaRule as CombinedRule,
loading: false,
dispatched: true,
requestId: 'A',
error: undefined,
});
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
// Act
await renderRuleViewer('test1');
// Assert
await waitFor(() => {
expect(ui.actionButtons.silence.query()).not.toBeInTheDocument();
});
});
it('Should render Silence button for users with the instance create permissions', async () => {
// Arrange
mocks.useAlertRuleAbility.mockReturnValue([true, true]);
mockCombinedRule.mockReturnValue({
result: mockGrafanaRule as CombinedRule,
loading: false,
dispatched: true,
requestId: 'A',
error: undefined,
});
jest
.spyOn(contextSrv, 'hasPermission')
.mockImplementation((action) => action === AccessControlAction.AlertingInstanceCreate);
// Act
await renderRuleViewer('test1');
// Assert
await waitFor(() => {
expect(ui.actionButtons.silence.get()).toBeInTheDocument();
});
});
it('Should render clone button for users having create rule permission', async () => {
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Duplicate ? [true, true] : [false, false];
});
mockCombinedRule.mockReturnValue({
result: getGrafanaRule({ name: 'Grafana rule' }),
loading: false,
dispatched: true,
});
grantUserPermissions([AccessControlAction.AlertingRuleCreate]);
await renderRuleViewer('test1');
await user.click(ui.moreButton.get());
expect(ui.moreButtons.duplicate.get()).toBeInTheDocument();
});
it('Should NOT render clone button for users without create rule permission', async () => {
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Duplicate ? [true, false] : [true, true];
});
mockCombinedRule.mockReturnValue({
result: getGrafanaRule({ name: 'Grafana rule' }),
loading: false,
dispatched: true,
});
const { AlertingRuleRead, AlertingRuleUpdate, AlertingRuleDelete } = AccessControlAction;
grantUserPermissions([AlertingRuleRead, AlertingRuleUpdate, AlertingRuleDelete]);
await renderRuleViewer('test1');
await user.click(ui.moreButton.get());
expect(ui.moreButtons.duplicate.query()).not.toBeInTheDocument();
});
});
describe('Cloud rules action buttons', () => {
const mockCombinedRule = jest.fn();
afterEach(() => {
mockCombinedRule.mockReset();
});
it('Should render edit button for users with the update permission', async () => {
// Arrange
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Update ? [true, true] : [false, false];
});
mockCombinedRule.mockReturnValue({
result: mockCloudRule as CombinedRule,
loading: false,
dispatched: true,
requestId: 'A',
error: undefined,
});
// Act
await renderRuleViewer('test1');
// Assert
expect(ui.actionButtons.edit.query()).toBeInTheDocument();
});
it('Should render Delete button for users with the delete permission', async () => {
// Arrange
mocks.useAlertRuleAbility.mockImplementation((_rule, action) => {
return action === AlertRuleAction.Delete ? [true, true] : [false, false];
});
mockCombinedRule.mockReturnValue({
result: mockCloudRule as CombinedRule,
loading: false,
dispatched: true,
requestId: 'A',
error: undefined,
});
// Act
await renderRuleViewer('test1');
await user.click(ui.moreButton.get());
// Assert
expect(ui.moreButtons.delete.query()).toBeInTheDocument();
});
});
});