From 75b7eb376d950fab9377bae608910d44e100f572 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Wed, 13 Nov 2024 17:45:52 -0300 Subject: [PATCH] fix: add tests --- src/components/ProbeCard/ProbeCard.test.tsx | 14 ++- .../ProbeEditor/ProbeEditor.test.tsx | 14 ++- .../ProbeStatus/ProbeStatus.test.tsx | 10 +- src/page/CheckRouter.test.tsx | 40 +++++++- src/page/ConfigPage/ConfigPage.test.tsx | 94 ++++++++++++++++++- .../EditCheck/__tests__/EditCheck.test.tsx | 8 +- src/test/fixtures/rbacPermissions.ts | 50 ++++++++++ src/test/mocks/@grafana/runtime.tsx | 3 +- src/test/utils.ts | 55 +++++++++++ 9 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 src/test/fixtures/rbacPermissions.ts diff --git a/src/components/ProbeCard/ProbeCard.test.tsx b/src/components/ProbeCard/ProbeCard.test.tsx index cb574312d..ba3ffbb51 100644 --- a/src/components/ProbeCard/ProbeCard.test.tsx +++ b/src/components/ProbeCard/ProbeCard.test.tsx @@ -6,7 +6,7 @@ import { userEvent } from '@testing-library/user-event'; import { DataTestIds } from 'test/dataTestIds'; import { OFFLINE_PROBE, ONLINE_PROBE, PRIVATE_PROBE, PUBLIC_PROBE } from 'test/fixtures/probes'; import { render } from 'test/render'; -import { probeToExtendedProbe, runTestAsViewer } from 'test/utils'; +import { probeToExtendedProbe, runTestAsRbacReader, runTestAsViewer } from 'test/utils'; import { type ExtendedProbe, ROUTES } from 'types'; import { getRoute } from 'components/Routing.utils'; @@ -91,6 +91,18 @@ it(`Displays the correct information for a private probe as a viewer`, async () expect(button).toHaveTextContent('View'); }); +it(`Displays the correct information for a private probe as a RBAC viewer`, async () => { + runTestAsRbacReader(); + const probe = probeToExtendedProbe(PRIVATE_PROBE); + + render(); + await screen.findByText(probe.name, { exact: false }); + + const button = screen.getByTestId('probe-card-action-button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('View'); +}); + it(`Displays the correct information for a public probe`, async () => { const probe = probeToExtendedProbe(PUBLIC_PROBE); diff --git a/src/components/ProbeEditor/ProbeEditor.test.tsx b/src/components/ProbeEditor/ProbeEditor.test.tsx index 1508dfed4..8b5986bb3 100644 --- a/src/components/ProbeEditor/ProbeEditor.test.tsx +++ b/src/components/ProbeEditor/ProbeEditor.test.tsx @@ -3,7 +3,7 @@ import { config } from '@grafana/runtime'; import { screen } from '@testing-library/react'; import { PRIVATE_PROBE, PUBLIC_PROBE } from 'test/fixtures/probes'; import { render } from 'test/render'; -import { fillProbeForm, probeToExtendedProbe, runTestAsViewer, UPDATED_VALUES } from 'test/utils'; +import { fillProbeForm, probeToExtendedProbe, runTestAsRbacReader, runTestAsViewer, UPDATED_VALUES } from 'test/utils'; import { ExtendedProbe, FeatureName, Probe } from 'types'; import { TEMPLATE_PROBE } from 'page/NewProbe'; @@ -104,6 +104,12 @@ it('the form is uneditable when logged in as a viewer', async () => { await assertUneditable(); }); +it('the form is uneditable when logged in as a RBAC viewer', async () => { + runTestAsRbacReader(); + await renderProbeEditor(); + await assertUneditable(); +}); + it('the form actions are unavailable when viewing a public probe', async () => { await renderProbeEditor({ probe: PUBLIC_PROBE }); await assertNoActions(); @@ -115,6 +121,12 @@ it('the form actions are unavailable as a viewer', async () => { await assertNoActions(); }); +it('the form actions are unavailable as a RBAC viewer', async () => { + runTestAsRbacReader(); + await renderProbeEditor(); + await assertNoActions(); +}); + async function assertUneditable() { const nameInput = await screen.findByLabelText('Probe Name', { exact: false }); expect(nameInput).toBeDisabled(); diff --git a/src/components/ProbeStatus/ProbeStatus.test.tsx b/src/components/ProbeStatus/ProbeStatus.test.tsx index a6b396611..8d8e5f23f 100644 --- a/src/components/ProbeStatus/ProbeStatus.test.tsx +++ b/src/components/ProbeStatus/ProbeStatus.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { screen, waitFor } from '@testing-library/react'; import { OFFLINE_PROBE, ONLINE_PROBE, PRIVATE_PROBE } from 'test/fixtures/probes'; import { render } from 'test/render'; -import { probeToExtendedProbe, runTestAsViewer } from 'test/utils'; +import { probeToExtendedProbe, runTestAsRbacReader, runTestAsViewer } from 'test/utils'; import { formatDate } from 'utils'; @@ -16,6 +16,14 @@ it(`hides the reset button when the user is a viewer`, async () => { expect(resetButton).not.toBeInTheDocument(); }); +it(`hides the reset button when the user is a RBAC viewer`, async () => { + runTestAsRbacReader(); + // We need to wait for contexts to finish loading to avoid issue with act + await waitFor(() => render()); + const resetButton = await getResetButton(true); + expect(resetButton).not.toBeInTheDocument(); +}); + it(`shows the reset probe access token when the user is an editor`, async () => { render(); const resetButton = await getResetButton(); diff --git a/src/page/CheckRouter.test.tsx b/src/page/CheckRouter.test.tsx index 0922136c5..ada488d9e 100644 --- a/src/page/CheckRouter.test.tsx +++ b/src/page/CheckRouter.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { screen, waitFor } from '@testing-library/react'; import { BASIC_HTTP_CHECK, BASIC_SCRIPTED_CHECK } from 'test/fixtures/checks'; import { render } from 'test/render'; -import { runTestAsViewer } from 'test/utils'; +import { runTestAsRbacEditor, runTestAsRbacReader, runTestAsViewer } from 'test/utils'; import { CheckType, CheckTypeGroup, ROUTES } from 'types'; import { PLUGIN_URL_PATH } from 'components/Routing.consts'; @@ -102,3 +102,41 @@ describe(``, () => { }); }); }); + +describe('RBAC Permissions', () => { + describe('When user is viewer', () => { + beforeEach(() => { + runTestAsRbacReader(); + }); + it(`Should not load the edit check route and redirect to the homepage`, async () => { + const checkType = CheckType.Scripted; + const checkID = BASIC_SCRIPTED_CHECK.id; + + const { history } = render(, { + path: `${PLUGIN_URL_PATH}${ROUTES.Checks}/edit/${checkType}/${checkID}`, + route: `${PLUGIN_URL_PATH}${ROUTES.Checks}`, + }); + + await waitFor(() => expect(history.location.pathname).toBe(`${PLUGIN_URL_PATH}${ROUTES.Checks}`)); + }); + }); + + describe('When user is editor', () => { + beforeEach(() => { + runTestAsRbacEditor(); + }); + + it(`Should load the edit check route`, async () => { + const checkType = CheckType.Scripted; + const checkID = BASIC_SCRIPTED_CHECK.id; + + render(, { + path: `${PLUGIN_URL_PATH}${ROUTES.Checks}/edit/${checkType}/${checkID}`, + route: `${PLUGIN_URL_PATH}${ROUTES.Checks}`, + }); + + const title = await screen.findByText(`Editing ${BASIC_SCRIPTED_CHECK.job}`); + expect(title).toBeInTheDocument(); + }); + }); +}); diff --git a/src/page/ConfigPage/ConfigPage.test.tsx b/src/page/ConfigPage/ConfigPage.test.tsx index f98a3a432..4b37d9145 100644 --- a/src/page/ConfigPage/ConfigPage.test.tsx +++ b/src/page/ConfigPage/ConfigPage.test.tsx @@ -3,7 +3,13 @@ import { screen } from '@testing-library/react'; import { LOGS_DATASOURCE, METRICS_DATASOURCE, SM_DATASOURCE } from 'test/fixtures/datasources'; import { CREATE_ACCESS_TOKEN } from 'test/fixtures/tokens'; import { render } from 'test/render'; -import { runTestWithoutLogsAccess, runTestWithoutMetricsAccess } from 'test/utils'; +import { + runTestAsRbacAdmin, + runTestAsRbacEditor, + runTestAsRbacReader, + runTestWithoutLogsAccess, + runTestWithoutMetricsAccess, +} from 'test/utils'; import { ConfigPage } from './ConfigPage'; @@ -92,3 +98,89 @@ describe(` initialised state`, () => { expect(pluginVersion).toBeInTheDocument(); }); }); + +describe('RBAC Permissions', () => { + describe('When user is viewer', () => { + beforeEach(() => { + runTestAsRbacReader(); + }); + it(`does not render the setup button`, async () => { + render(); + + const setupButton = await screen.queryByText(/Setup/i); + expect(setupButton).not.toBeInTheDocument(); + }); + + it(`does not render access token generation`, async () => { + render(); + + const accessTokenButton = await screen.queryByText(/Generate access token/); + expect(accessTokenButton).not.toBeInTheDocument(); + }); + + it(`does not render disabling the plugin`, async () => { + render(); + + const disableButton = await screen.queryByText(/Disable synthetic monitoring/i); + expect(disableButton).not.toBeInTheDocument(); + }); + }); + + describe('When user is editor', () => { + beforeEach(() => { + runTestAsRbacEditor(); + }); + + it(`renders the setup button`, async () => { + render(); + + const setupButton = await screen.findByText(/Setup/i); + expect(setupButton).toBeInTheDocument(); + }); + + it(`does not render access token generation`, async () => { + render(); + + const accessTokenButton = await screen.queryByText(/Generate access token/); + expect(accessTokenButton).not.toBeInTheDocument(); + }); + + it(`does not render disabling the plugin`, async () => { + render(); + + const disableButton = await screen.queryByText(/Disable synthetic monitoring/i); + expect(disableButton).not.toBeInTheDocument(); + }); + }); + + describe('When user is admin', () => { + beforeEach(() => { + runTestAsRbacAdmin(); + }); + + it(`renders the setup button`, async () => { + render(); + + const setupButton = await screen.findByText(/Setup/i); + expect(setupButton).toBeInTheDocument(); + }); + + it(`renders access token generation and can generate a token`, async () => { + const { user } = render(); + + const accessTokenButton = await screen.findByText(/Generate access token/); + expect(accessTokenButton).toBeInTheDocument(); + await user.click(accessTokenButton); + + const accessToken = await screen.findByText(CREATE_ACCESS_TOKEN); + expect(accessToken).toBeInTheDocument(); + }); + + it(`renders disabling the plugin`, async () => { + render(); + + const disableButton = await screen.findByText(/Disable synthetic monitoring/i); + expect(disableButton).toBeInTheDocument(); + }); + }); +}); diff --git a/src/page/EditCheck/__tests__/EditCheck.test.tsx b/src/page/EditCheck/__tests__/EditCheck.test.tsx index afef56ed8..f47ef39d9 100644 --- a/src/page/EditCheck/__tests__/EditCheck.test.tsx +++ b/src/page/EditCheck/__tests__/EditCheck.test.tsx @@ -2,7 +2,7 @@ import { screen } from '@testing-library/react'; import { BASIC_HTTP_CHECK } from 'test/fixtures/checks'; import { apiRoute } from 'test/handlers'; import { server } from 'test/server'; -import { runTestAsViewer } from 'test/utils'; +import { runTestAsRbacReader, runTestAsViewer } from 'test/utils'; import { renderEditForm } from 'page/__testHelpers__/checkForm'; @@ -59,4 +59,10 @@ describe(``, () => { await renderEditForm(BASIC_HTTP_CHECK); expect(screen.getByRole(`button`, { name: `Submit` })).toBeDisabled(); }); + + it(`disables the form when the user is a RBAC viewer`, async () => { + runTestAsRbacReader(); + await renderEditForm(BASIC_HTTP_CHECK); + expect(screen.getByRole(`button`, { name: `Submit` })).toBeDisabled(); + }); }); diff --git a/src/test/fixtures/rbacPermissions.ts b/src/test/fixtures/rbacPermissions.ts new file mode 100644 index 000000000..f5833f007 --- /dev/null +++ b/src/test/fixtures/rbacPermissions.ts @@ -0,0 +1,50 @@ +export const FULL_ADMIN_ACCESS = { + 'grafana-synthetic-monitoring-app:read': true, + 'grafana-synthetic-monitoring-app:write': true, + 'grafana-synthetic-monitoring-app.plugin:enable': true, + 'grafana-synthetic-monitoring-app.plugin:disable': true, + 'grafana-synthetic-monitoring-app.checks:write': true, + 'grafana-synthetic-monitoring-app.probes:write': true, + 'grafana-synthetic-monitoring-app.alerts:write': true, + 'grafana-synthetic-monitoring-app.thresholds:write': true, + 'grafana-synthetic-monitoring-app.tokens:write': true, + 'grafana-synthetic-monitoring-app.checks:read': true, + 'grafana-synthetic-monitoring-app.probes:read': true, + 'grafana-synthetic-monitoring-app.alerts:read': true, + 'grafana-synthetic-monitoring-app.thresholds:read': true, + 'grafana-synthetic-monitoring-app.tokens:read': true, + 'grafana-synthetic-monitoring-app.checks:delete': true, + 'grafana-synthetic-monitoring-app.probes:delete': true, + 'grafana-synthetic-monitoring-app.alerts:delete': true, + 'grafana-synthetic-monitoring-app.thresholds:delete': true, + 'grafana-synthetic-monitoring-app.tokens:delete': true, +}; + +export const FULL_WRITER_ACCESS = { + 'grafana-synthetic-monitoring-app:read': true, + 'grafana-synthetic-monitoring-app:write': true, + 'grafana-synthetic-monitoring-app.checks:write': true, + 'grafana-synthetic-monitoring-app.probes:write': true, + 'grafana-synthetic-monitoring-app.alerts:write': true, + 'grafana-synthetic-monitoring-app.thresholds:write': true, + 'grafana-synthetic-monitoring-app.tokens:write': true, + 'grafana-synthetic-monitoring-app.checks:read': true, + 'grafana-synthetic-monitoring-app.probes:read': true, + 'grafana-synthetic-monitoring-app.alerts:read': true, + 'grafana-synthetic-monitoring-app.thresholds:read': true, + 'grafana-synthetic-monitoring-app.tokens:read': true, + 'grafana-synthetic-monitoring-app.checks:delete': true, + 'grafana-synthetic-monitoring-app.probes:delete': true, + 'grafana-synthetic-monitoring-app.alerts:delete': true, + 'grafana-synthetic-monitoring-app.thresholds:delete': true, + 'grafana-synthetic-monitoring-app.tokens:delete': true, +}; + +export const FULL_READONLY_ACCESS = { + 'grafana-synthetic-monitoring-app:read': true, + 'grafana-synthetic-monitoring-app.checks:read': true, + 'grafana-synthetic-monitoring-app.probes:read': true, + 'grafana-synthetic-monitoring-app.alerts:read': true, + 'grafana-synthetic-monitoring-app.thresholds:read': true, + 'grafana-synthetic-monitoring-app.tokens:read': true, +}; diff --git a/src/test/mocks/@grafana/runtime.tsx b/src/test/mocks/@grafana/runtime.tsx index 5e24e318e..db98c9602 100644 --- a/src/test/mocks/@grafana/runtime.tsx +++ b/src/test/mocks/@grafana/runtime.tsx @@ -4,12 +4,12 @@ import { BackendSrvRequest } from '@grafana/runtime'; import axios from 'axios'; import { from } from 'rxjs'; import { LOGS_DATASOURCE, METRICS_DATASOURCE, SM_DATASOURCE } from 'test/fixtures/datasources'; +import { FULL_ADMIN_ACCESS } from 'test/fixtures/rbacPermissions'; import { SMDataSource } from 'datasource/DataSource'; jest.mock('@grafana/runtime', () => { const actual = jest.requireActual('@grafana/runtime'); - return { ...actual, config: { @@ -25,6 +25,7 @@ jest.mock('@grafana/runtime', () => { user: { ...actual.config.user, orgRole: OrgRole.Admin, + permissions: FULL_ADMIN_ACCESS, }, }, }, diff --git a/src/test/utils.ts b/src/test/utils.ts index 4325aec06..c1219e681 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -12,6 +12,7 @@ import { ExtendedProbe, type Probe } from 'types'; import { apiRoute } from './handlers'; import { server } from './server'; +import { FULL_ADMIN_ACCESS, FULL_READONLY_ACCESS, FULL_WRITER_ACCESS } from './fixtures/rbacPermissions'; export const UPDATED_VALUES: Pick = { latitude: 19.05758, @@ -149,6 +150,60 @@ export function runTestWithoutSMAccess() { }); } +export function runTestAsRbacReader() { + const runtime = require('@grafana/runtime'); + jest.replaceProperty(runtime, `config`, { + ...config, + featureToggles: { + ...runtime.config.featureToggles, + accessControlOnCall: true, + }, + + bootData: { + ...runtime.config.bootData, + user: { + permissions: FULL_READONLY_ACCESS, + }, + }, + }); +} + +export function runTestAsRbacEditor() { + const runtime = require('@grafana/runtime'); + jest.replaceProperty(runtime, `config`, { + ...config, + featureToggles: { + ...runtime.config.featureToggles, + accessControlOnCall: true, + }, + + bootData: { + ...runtime.config.bootData, + user: { + permissions: FULL_WRITER_ACCESS, + }, + }, + }); +} + +export function runTestAsRbacAdmin() { + const runtime = require('@grafana/runtime'); + jest.replaceProperty(runtime, `config`, { + ...config, + featureToggles: { + ...runtime.config.featureToggles, + accessControlOnCall: true, + }, + + bootData: { + ...runtime.config.bootData, + user: { + permissions: FULL_ADMIN_ACCESS, + }, + }, + }); +} + export function runTestAsHGFreeUserOverLimit() { // this gets reset in afterEach in jest-setup.js const runtime = require('@grafana/runtime');