ApiKeysPage.test.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import { render, screen, within } from '@testing-library/react';
  2. import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
  3. import React from 'react';
  4. import { NavModel } from '@grafana/data';
  5. import { selectors } from '@grafana/e2e-selectors';
  6. import { ApiKey, OrgRole } from 'app/types';
  7. import { mockToolkitActionCreator } from '../../../test/core/redux/mocks';
  8. import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
  9. import { ApiKeysPageUnconnected, Props } from './ApiKeysPage';
  10. import { getMultipleMockKeys } from './__mocks__/apiKeysMock';
  11. import { setSearchQuery } from './state/reducers';
  12. jest.mock('app/core/core', () => {
  13. return {
  14. contextSrv: {
  15. hasPermission: () => true,
  16. hasPermissionInMetadata: () => true,
  17. },
  18. };
  19. });
  20. const setup = (propOverrides: Partial<Props>) => {
  21. const loadApiKeysMock = jest.fn();
  22. const deleteApiKeyMock = jest.fn();
  23. const addApiKeyMock = jest.fn();
  24. const toggleIncludeExpiredMock = jest.fn();
  25. const setSearchQueryMock = mockToolkitActionCreator(setSearchQuery);
  26. const props: Props = {
  27. navModel: {
  28. main: {
  29. text: 'Configuration',
  30. },
  31. node: {
  32. text: 'Api Keys',
  33. },
  34. } as NavModel,
  35. apiKeys: [] as ApiKey[],
  36. searchQuery: '',
  37. hasFetched: false,
  38. loadApiKeys: loadApiKeysMock,
  39. deleteApiKey: deleteApiKeyMock,
  40. setSearchQuery: setSearchQueryMock,
  41. addApiKey: addApiKeyMock,
  42. apiKeysCount: 0,
  43. timeZone: 'utc',
  44. includeExpired: false,
  45. includeExpiredDisabled: false,
  46. toggleIncludeExpired: toggleIncludeExpiredMock,
  47. canCreate: true,
  48. };
  49. Object.assign(props, propOverrides);
  50. const { rerender } = render(<ApiKeysPageUnconnected {...props} />);
  51. return {
  52. rerender,
  53. props,
  54. loadApiKeysMock,
  55. setSearchQueryMock,
  56. deleteApiKeyMock,
  57. addApiKeyMock,
  58. toggleIncludeExpiredMock,
  59. };
  60. };
  61. describe('ApiKeysPage', () => {
  62. silenceConsoleOutput();
  63. describe('when mounted', () => {
  64. it('then it should call loadApiKeys', () => {
  65. const { loadApiKeysMock } = setup({});
  66. expect(loadApiKeysMock).toHaveBeenCalledTimes(1);
  67. });
  68. });
  69. describe('when loading', () => {
  70. it('then should show Loading message', () => {
  71. setup({ hasFetched: false });
  72. expect(screen.getByText(/loading \.\.\./i)).toBeInTheDocument();
  73. });
  74. });
  75. describe('when there are no API keys', () => {
  76. it('then it should render CTA', () => {
  77. setup({ apiKeys: getMultipleMockKeys(0), apiKeysCount: 0, hasFetched: true });
  78. expect(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key'))).toBeInTheDocument();
  79. });
  80. });
  81. describe('when there are API keys', () => {
  82. it('then it should render API keys table', async () => {
  83. const apiKeys = [
  84. { id: 1, name: 'First', role: OrgRole.Admin, secondsToLive: 60, expiration: '2021-01-01' },
  85. { id: 2, name: 'Second', role: OrgRole.Editor, secondsToLive: 60, expiration: '2021-01-02' },
  86. { id: 3, name: 'Third', role: OrgRole.Viewer, secondsToLive: 0, expiration: undefined },
  87. ];
  88. setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  89. expect(screen.getByRole('table')).toBeInTheDocument();
  90. expect(screen.getAllByRole('row').length).toBe(4);
  91. expect(screen.getByRole('row', { name: /first admin 2021-01-01 00:00:00/i })).toBeInTheDocument();
  92. expect(screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00/i })).toBeInTheDocument();
  93. expect(screen.getByRole('row', { name: /third viewer no expiration date/i })).toBeInTheDocument();
  94. });
  95. });
  96. describe('when a user toggles the Show expired toggle', () => {
  97. it('then it should dispatch toggleIncludeExpired', async () => {
  98. const apiKeys = getMultipleMockKeys(3);
  99. const { toggleIncludeExpiredMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  100. await toggleShowExpired();
  101. expect(toggleIncludeExpiredMock).toHaveBeenCalledTimes(1);
  102. });
  103. });
  104. describe('when a user searches for an API key', () => {
  105. it('then it should dispatch setSearchQuery with correct parameters', async () => {
  106. const apiKeys = getMultipleMockKeys(3);
  107. const { setSearchQueryMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  108. setSearchQueryMock.mockClear();
  109. expect(screen.getByPlaceholderText(/search keys/i)).toBeInTheDocument();
  110. await userEvent.type(screen.getByPlaceholderText(/search keys/i), 'First');
  111. expect(setSearchQueryMock).toHaveBeenCalledTimes(5);
  112. });
  113. });
  114. describe('when a user deletes an API key', () => {
  115. it('then it should dispatch deleteApi with correct parameters', async () => {
  116. const apiKeys = [
  117. { id: 1, name: 'First', role: OrgRole.Admin, secondsToLive: 60, expiration: '2021-01-01' },
  118. { id: 2, name: 'Second', role: OrgRole.Editor, secondsToLive: 60, expiration: '2021-01-02' },
  119. { id: 3, name: 'Third', role: OrgRole.Viewer, secondsToLive: 0, expiration: undefined },
  120. ];
  121. const { deleteApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  122. const firstRow = screen.getByRole('row', { name: /first admin 2021-01-01 00:00:00/i });
  123. const secondRow = screen.getByRole('row', { name: /second editor 2021-01-02 00:00:00/i });
  124. deleteApiKeyMock.mockClear();
  125. expect(within(firstRow).getByLabelText('Delete API key')).toBeInTheDocument();
  126. await userEvent.click(within(firstRow).getByLabelText('Delete API key'));
  127. expect(within(firstRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
  128. await userEvent.click(within(firstRow).getByRole('button', { name: /delete$/i }));
  129. expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
  130. expect(deleteApiKeyMock).toHaveBeenCalledWith(1);
  131. await toggleShowExpired();
  132. deleteApiKeyMock.mockClear();
  133. expect(within(secondRow).getByLabelText('Delete API key')).toBeInTheDocument();
  134. await userEvent.click(within(secondRow).getByLabelText('Delete API key'));
  135. expect(within(secondRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
  136. await userEvent.click(within(secondRow).getByRole('button', { name: /delete$/i }), {
  137. pointerEventsCheck: PointerEventsCheckLevel.Never,
  138. });
  139. expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
  140. expect(deleteApiKeyMock).toHaveBeenCalledWith(2);
  141. });
  142. });
  143. describe('when a user adds an API key from CTA', () => {
  144. it('then it should call addApiKey with correct parameters', async () => {
  145. const apiKeys: any[] = [];
  146. const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  147. addApiKeyMock.mockClear();
  148. await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key')));
  149. await addAndVerifyApiKey(addApiKeyMock);
  150. });
  151. });
  152. describe('when a user adds an API key from Add API key', () => {
  153. it('then it should call addApiKey with correct parameters', async () => {
  154. const apiKeys = getMultipleMockKeys(1);
  155. const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  156. addApiKeyMock.mockClear();
  157. await userEvent.click(screen.getByRole('button', { name: /add api key/i }));
  158. await addAndVerifyApiKey(addApiKeyMock);
  159. await toggleShowExpired();
  160. addApiKeyMock.mockClear();
  161. await userEvent.click(screen.getByRole('button', { name: /add api key/i }));
  162. await addAndVerifyApiKey(addApiKeyMock);
  163. });
  164. });
  165. describe('when a user adds an API key with an invalid expiration', () => {
  166. it('then it should display a message', async () => {
  167. const apiKeys = getMultipleMockKeys(1);
  168. const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true });
  169. addApiKeyMock.mockClear();
  170. await userEvent.click(screen.getByRole('button', { name: /add api key/i }));
  171. await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test');
  172. await userEvent.type(screen.getByPlaceholderText(/1d/i), '60x');
  173. expect(screen.queryByText(/not a valid duration/i)).not.toBeInTheDocument();
  174. await userEvent.click(screen.getByRole('button', { name: /^add$/i }));
  175. expect(screen.getByText(/not a valid duration/i)).toBeInTheDocument();
  176. expect(addApiKeyMock).toHaveBeenCalledTimes(0);
  177. });
  178. });
  179. });
  180. async function toggleShowExpired() {
  181. expect(screen.queryByLabelText(/include expired keys/i)).toBeInTheDocument();
  182. await userEvent.click(screen.getByLabelText(/include expired keys/i));
  183. }
  184. async function addAndVerifyApiKey(addApiKeyMock: jest.Mock) {
  185. expect(screen.getByRole('heading', { name: /add api key/i })).toBeInTheDocument();
  186. expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument();
  187. expect(screen.getByPlaceholderText(/1d/i)).toBeInTheDocument();
  188. expect(screen.getByRole('button', { name: /^add$/i })).toBeInTheDocument();
  189. await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test');
  190. await userEvent.type(screen.getByPlaceholderText(/1d/i), '60s');
  191. await userEvent.click(screen.getByRole('button', { name: /^add$/i }));
  192. expect(addApiKeyMock).toHaveBeenCalledTimes(1);
  193. expect(addApiKeyMock).toHaveBeenCalledWith({ name: 'Test', role: 'Viewer', secondsToLive: 60 }, expect.anything());
  194. }