The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/api-keys/ApiKeysPage.test.tsx

226 lines
9.4 KiB

import { render, screen, within } from '@testing-library/react';
import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { ApiKey, OrgRole } from 'app/types';
import { mockToolkitActionCreator } from '../../../test/core/redux/mocks';
import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
import { ApiKeysPageUnconnected, Props } from './ApiKeysPage';
import { getMultipleMockKeys } from './__mocks__/apiKeysMock';
import { setSearchQuery } from './state/reducers';
jest.mock('app/core/core', () => {
return {
contextSrv: {
hasPermission: () => true,
hasPermissionInMetadata: () => true,
},
};
});
const setup = (propOverrides: Partial<Props>) => {
const loadApiKeysMock = jest.fn();
const deleteApiKeyMock = jest.fn();
const migrateApiKeyMock = jest.fn();
const addApiKeyMock = jest.fn();
const migrateAllMock = jest.fn();
const toggleIncludeExpiredMock = jest.fn();
const setSearchQueryMock = mockToolkitActionCreator(setSearchQuery);
const getApiKeysMigrationStatusMock = jest.fn();
const hideApiKeysMock = jest.fn();
const props: Props = {
apiKeys: [] as ApiKey[],
searchQuery: '',
hasFetched: false,
loadApiKeys: loadApiKeysMock,
deleteApiKey: deleteApiKeyMock,
setSearchQuery: setSearchQueryMock,
addApiKey: addApiKeyMock,
getApiKeysMigrationStatus: getApiKeysMigrationStatusMock,
migrateApiKey: migrateApiKeyMock,
migrateAll: migrateAllMock,
hideApiKeys: hideApiKeysMock,
apiKeysCount: 0,
timeZone: 'utc',
includeExpired: false,
includeExpiredDisabled: false,
toggleIncludeExpired: toggleIncludeExpiredMock,
canCreate: true,
apiKeysMigrated: false,
};
Object.assign(props, propOverrides);
const { rerender } = render(<ApiKeysPageUnconnected {...props} />);
return {
rerender,
props,
loadApiKeysMock,
setSearchQueryMock,
deleteApiKeyMock,
addApiKeyMock,
toggleIncludeExpiredMock,
};
};
describe('ApiKeysPage', () => {
silenceConsoleOutput();
describe('when mounted', () => {
it('then it should call loadApiKeys', () => {
const { loadApiKeysMock } = setup({});
expect(loadApiKeysMock).toHaveBeenCalledTimes(1);
});
});
describe('when loading', () => {
it('then should show Loading message', () => {
setup({ hasFetched: false });
expect(screen.getByText(/loading \.\.\./i)).toBeInTheDocument();
});
});
describe('when there are no API keys', () => {
it('then it should render CTA', () => {
setup({ apiKeys: getMultipleMockKeys(0), apiKeysCount: 0, hasFetched: true });
expect(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key'))).toBeInTheDocument();
});
});
describe('when there are API keys', () => {
it('then it should render API keys table', async () => {
const apiKeys = [
{ id: 1, name: 'First', role: OrgRole.Admin, secondsToLive: 60, expiration: '2021-01-01' },
{ id: 2, name: 'Second', role: OrgRole.Editor, secondsToLive: 60, expiration: '2021-01-02' },
{ id: 3, name: 'Third', role: OrgRole.Viewer, secondsToLive: 0, expiration: undefined },
];
setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.getAllByRole('row').length).toBe(4);
expect(screen.getByRole('row', { name: /first admin 2021-01-01 00:00:00/i })).toBeInTheDocument();
expect(screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00/i })).toBeInTheDocument();
expect(screen.getByRole('row', { name: /third viewer no expiration date/i })).toBeInTheDocument();
});
});
describe('when a user toggles the Show expired toggle', () => {
it('then it should dispatch toggleIncludeExpired', async () => {
const apiKeys = getMultipleMockKeys(3);
const { toggleIncludeExpiredMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
await toggleShowExpired();
expect(toggleIncludeExpiredMock).toHaveBeenCalledTimes(1);
});
});
describe('when a user searches for an API key', () => {
it('then it should dispatch setSearchQuery with correct parameters', async () => {
const apiKeys = getMultipleMockKeys(3);
const { setSearchQueryMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
setSearchQueryMock.mockClear();
expect(screen.getByPlaceholderText(/search keys/i)).toBeInTheDocument();
await userEvent.type(screen.getByPlaceholderText(/search keys/i), 'First');
expect(setSearchQueryMock).toHaveBeenCalledTimes(5);
});
});
describe('when a user deletes an API key', () => {
it('then it should dispatch deleteApi with correct parameters', async () => {
const apiKeys = [
{ id: 1, name: 'First', role: OrgRole.Admin, secondsToLive: 60, expiration: '2021-01-01' },
{ id: 2, name: 'Second', role: OrgRole.Editor, secondsToLive: 60, expiration: '2021-01-02' },
{ id: 3, name: 'Third', role: OrgRole.Viewer, secondsToLive: 0, expiration: undefined },
];
const { deleteApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
const firstRow = screen.getByRole('row', { name: /first admin 2021-01-01 00:00:00/i });
const secondRow = screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00/i });
deleteApiKeyMock.mockClear();
expect(within(firstRow).getByLabelText('Delete API key')).toBeInTheDocument();
await userEvent.click(within(firstRow).getByLabelText('Delete API key'));
expect(within(firstRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
await userEvent.click(within(firstRow).getByRole('button', { name: /delete$/i }));
expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
expect(deleteApiKeyMock).toHaveBeenCalledWith(1);
await toggleShowExpired();
deleteApiKeyMock.mockClear();
expect(within(secondRow).getByLabelText('Delete API key')).toBeInTheDocument();
await userEvent.click(within(secondRow).getByLabelText('Delete API key'));
expect(within(secondRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
await userEvent.click(within(secondRow).getByRole('button', { name: /delete$/i }), {
pointerEventsCheck: PointerEventsCheckLevel.Never,
});
expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
expect(deleteApiKeyMock).toHaveBeenCalledWith(2);
});
});
describe('when a user adds an API key from CTA', () => {
it('then it should call addApiKey with correct parameters', async () => {
const apiKeys: ApiKey[] = [];
const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
addApiKeyMock.mockClear();
await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key')));
await addAndVerifyApiKey(addApiKeyMock);
});
});
describe('when a user adds an API key from Add API key', () => {
it('then it should call addApiKey with correct parameters', async () => {
const apiKeys = getMultipleMockKeys(1);
const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
addApiKeyMock.mockClear();
await userEvent.click(screen.getByRole('button', { name: /add api key/i }));
await addAndVerifyApiKey(addApiKeyMock);
await toggleShowExpired();
addApiKeyMock.mockClear();
await userEvent.click(screen.getByRole('button', { name: /add api key/i }));
await addAndVerifyApiKey(addApiKeyMock);
});
});
describe('when a user adds an API key with an invalid expiration', () => {
it('then it should display a message', async () => {
const apiKeys = getMultipleMockKeys(1);
const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
addApiKeyMock.mockClear();
await userEvent.click(screen.getByRole('button', { name: /add api key/i }));
await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test');
await userEvent.type(screen.getByPlaceholderText(/1d/i), '60x');
expect(screen.queryByText(/not a valid duration/i)).not.toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: /^add$/i }));
expect(screen.getByText(/not a valid duration/i)).toBeInTheDocument();
expect(addApiKeyMock).toHaveBeenCalledTimes(0);
});
});
});
async function toggleShowExpired() {
expect(screen.queryByLabelText(/include expired keys/i)).toBeInTheDocument();
await userEvent.click(screen.getByLabelText(/include expired keys/i));
}
async function addAndVerifyApiKey(addApiKeyMock: jest.Mock) {
expect(screen.getByRole('heading', { name: /add api key/i })).toBeInTheDocument();
expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument();
expect(screen.getByPlaceholderText(/1d/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^add$/i })).toBeInTheDocument();
await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test');
await userEvent.type(screen.getByPlaceholderText(/1d/i), '60s');
await userEvent.click(screen.getByRole('button', { name: /^add$/i }));
expect(addApiKeyMock).toHaveBeenCalledTimes(1);
expect(addApiKeyMock).toHaveBeenCalledWith({ name: 'Test', role: 'Viewer', secondsToLive: 60 }, expect.anything());
}