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

210 lines
9.2 KiB

import React from 'react';
import { render, screen, within } from '@testing-library/react';
import { ApiKeysPageUnconnected, Props } from './ApiKeysPage';
import { ApiKey, OrgRole } from 'app/types';
import { NavModel } from '@grafana/data';
import { setSearchQuery } from './state/reducers';
import { mockToolkitActionCreator } from '../../../test/core/redux/mocks';
import { getMultipleMockKeys } from './__mocks__/apiKeysMock';
import { selectors } from '@grafana/e2e-selectors';
import userEvent from '@testing-library/user-event';
import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
const setup = (propOverrides: Partial<Props>) => {
const loadApiKeysMock = jest.fn();
const deleteApiKeyMock = jest.fn();
const addApiKeyMock = jest.fn();
const setSearchQueryMock = mockToolkitActionCreator(setSearchQuery);
const props: Props = {
navModel: {
main: {
text: 'Configuration',
},
node: {
text: 'Api Keys',
},
} as NavModel,
apiKeys: [] as ApiKey[],
searchQuery: '',
hasFetched: false,
loadApiKeys: loadApiKeysMock,
deleteApiKey: deleteApiKeyMock,
setSearchQuery: setSearchQueryMock,
addApiKey: addApiKeyMock,
apiKeysCount: 0,
timeZone: 'utc',
};
Object.assign(props, propOverrides);
const { rerender } = render(<ApiKeysPageUnconnected {...props} />);
return { rerender, props, loadApiKeysMock, setSearchQueryMock, deleteApiKeyMock, addApiKeyMock };
};
describe('ApiKeysPage', () => {
silenceConsoleOutput();
describe('when mounted', () => {
it('then it should call loadApiKeys without expired', () => {
const { loadApiKeysMock } = setup({});
expect(loadApiKeysMock).toHaveBeenCalledTimes(1);
expect(loadApiKeysMock).toHaveBeenCalledWith(false);
});
});
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.getByLabelText(selectors.components.CallToActionCard.button('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 cancel delete/i })).toBeInTheDocument();
expect(screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00 cancel delete/i })).toBeInTheDocument();
expect(screen.getByRole('row', { name: /third viewer no expiration date cancel delete/i })).toBeInTheDocument();
});
});
describe('when a user toggles the Show expired toggle', () => {
it('then it should call loadApiKeys with correct parameters', async () => {
const apiKeys = getMultipleMockKeys(3);
const { loadApiKeysMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
loadApiKeysMock.mockClear();
toggleShowExpired();
expect(loadApiKeysMock).toHaveBeenCalledTimes(1);
expect(loadApiKeysMock).toHaveBeenCalledWith(true);
loadApiKeysMock.mockClear();
toggleShowExpired();
expect(loadApiKeysMock).toHaveBeenCalledTimes(1);
expect(loadApiKeysMock).toHaveBeenCalledWith(false);
});
});
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 cancel delete/i });
const secondRow = screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00 cancel delete/i });
deleteApiKeyMock.mockClear();
expect(within(firstRow).getByRole('cell', { name: /cancel delete/i })).toBeInTheDocument();
userEvent.click(within(firstRow).getByRole('cell', { name: /cancel delete/i }));
expect(within(firstRow).getByRole('button', { name: /delete/i })).toBeInTheDocument();
userEvent.click(within(firstRow).getByRole('button', { name: /delete/i }));
expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
expect(deleteApiKeyMock).toHaveBeenCalledWith(1, false);
toggleShowExpired();
deleteApiKeyMock.mockClear();
expect(within(secondRow).getByRole('cell', { name: /cancel delete/i })).toBeInTheDocument();
userEvent.click(within(secondRow).getByRole('cell', { name: /cancel delete/i }));
expect(within(secondRow).getByRole('button', { name: /delete/i })).toBeInTheDocument();
userEvent.click(within(secondRow).getByRole('button', { name: /delete/i }));
expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
expect(deleteApiKeyMock).toHaveBeenCalledWith(2, true);
});
});
describe('when a user adds an API key from CTA', () => {
it('then it should call addApiKey with correct parameters', async () => {
const apiKeys: any[] = [];
const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
addApiKeyMock.mockClear();
userEvent.click(screen.getByLabelText(selectors.components.CallToActionCard.button('New API key')));
await addAndVerifyApiKey(addApiKeyMock, false);
});
});
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();
userEvent.click(screen.getByRole('button', { name: /add api key/i }));
await addAndVerifyApiKey(addApiKeyMock, false);
toggleShowExpired();
addApiKeyMock.mockClear();
userEvent.click(screen.getByRole('button', { name: /add api key/i }));
await addAndVerifyApiKey(addApiKeyMock, true);
});
});
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();
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();
userEvent.click(screen.getByRole('button', { name: /^add$/i }));
expect(screen.getByText(/not a valid duration/i)).toBeInTheDocument();
expect(addApiKeyMock).toHaveBeenCalledTimes(0);
});
});
});
function toggleShowExpired() {
expect(screen.queryByLabelText(/show expired/i)).toBeInTheDocument();
userEvent.click(screen.getByLabelText(/show expired/i));
}
async function addAndVerifyApiKey(addApiKeyMock: jest.Mock, includeExpired: boolean) {
expect(screen.getByRole('heading', { name: /add api key/i })).toBeInTheDocument();
expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument();
expect(screen.getByRole('combobox')).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');
userEvent.click(screen.getByRole('button', { name: /^add$/i }));
expect(addApiKeyMock).toHaveBeenCalledTimes(1);
expect(addApiKeyMock).toHaveBeenCalledWith(
{ name: 'Test', role: 'Viewer', secondsToLive: 60 },
expect.anything(),
includeExpired
);
}