uptimeAlertForm.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import {ActorFixture} from 'sentry-fixture/actor';
  2. import {MemberFixture} from 'sentry-fixture/member';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {ProjectFixture} from 'sentry-fixture/project';
  5. import {TeamFixture} from 'sentry-fixture/team';
  6. import {UptimeRuleFixture} from 'sentry-fixture/uptimeRule';
  7. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  8. import selectEvent from 'sentry-test/selectEvent';
  9. import OrganizationStore from 'sentry/stores/organizationStore';
  10. import ProjectsStore from 'sentry/stores/projectsStore';
  11. import {UptimeAlertForm} from 'sentry/views/alerts/rules/uptime/uptimeAlertForm';
  12. describe('Uptime Alert Form', function () {
  13. const organization = OrganizationFixture();
  14. const project = ProjectFixture({environments: ['prod', 'dev']});
  15. beforeEach(function () {
  16. OrganizationStore.onUpdate(organization);
  17. ProjectsStore.loadInitialData([project]);
  18. MockApiClient.addMockResponse({
  19. url: '/organizations/org-slug/members/',
  20. body: [MemberFixture()],
  21. });
  22. MockApiClient.addMockResponse({
  23. url: '/organizations/org-slug/teams/',
  24. body: [TeamFixture()],
  25. });
  26. });
  27. function input(name: string) {
  28. return screen.getByRole('textbox', {name});
  29. }
  30. it('can create a new rule', async function () {
  31. render(<UptimeAlertForm organization={organization} project={project} />, {
  32. organization,
  33. });
  34. await screen.findByText('Configure Request');
  35. await selectEvent.select(input('Environment'), 'prod');
  36. await userEvent.clear(input('URL'));
  37. await userEvent.type(input('URL'), 'http://example.com');
  38. await selectEvent.clearAll(input('Method'));
  39. await selectEvent.select(input('Method'), 'POST');
  40. await userEvent.clear(input('Body'));
  41. await userEvent.type(input('Body'), '{{"key": "value"}');
  42. await userEvent.type(input('Name of header 1'), 'X-Something');
  43. await userEvent.type(input('Value of X-Something'), 'Header Value');
  44. const name = input('Uptime rule name');
  45. await userEvent.clear(name);
  46. await userEvent.type(name, 'New Uptime Rule');
  47. await selectEvent.select(input('Owner'), 'Foo Bar');
  48. const updateMock = MockApiClient.addMockResponse({
  49. url: `/projects/${organization.slug}/${project.slug}/uptime/`,
  50. method: 'POST',
  51. });
  52. await userEvent.click(screen.getByRole('button', {name: 'Create Rule'}));
  53. expect(updateMock).toHaveBeenCalledWith(
  54. expect.anything(),
  55. expect.objectContaining({
  56. data: expect.objectContaining({
  57. environment: 'prod',
  58. name: 'New Uptime Rule',
  59. owner: 'user:1',
  60. url: 'http://example.com',
  61. method: 'POST',
  62. headers: [['X-Something', 'Header Value']],
  63. body: '{"key": "value"}',
  64. intervalSeconds: 60,
  65. }),
  66. })
  67. );
  68. });
  69. it('renders existing rule', async function () {
  70. const rule = UptimeRuleFixture({
  71. name: 'Existing Rule',
  72. environment: 'prod',
  73. projectSlug: project.slug,
  74. url: 'https://existing-url.com',
  75. method: 'POST',
  76. headers: [
  77. ['X-Test1', 'value 1'],
  78. ['X-Test2', 'value 2'],
  79. ],
  80. body: '{"key": "value"}',
  81. owner: ActorFixture(),
  82. });
  83. render(
  84. <UptimeAlertForm organization={organization} project={project} rule={rule} />,
  85. {organization}
  86. );
  87. await screen.findByText('Configure Request');
  88. expect(input('Uptime rule name')).toHaveValue('Existing Rule');
  89. expect(input('URL')).toHaveValue('https://existing-url.com');
  90. expect(input('Body')).toHaveValue('{"key": "value"}');
  91. expect(input('Name of header 1')).toHaveValue('X-Test1');
  92. expect(input('Name of header 2')).toHaveValue('X-Test2');
  93. expect(input('Value of X-Test1')).toHaveValue('value 1');
  94. expect(input('Value of X-Test2')).toHaveValue('value 2');
  95. await selectEvent.openMenu(input('Method'));
  96. expect(screen.getByRole('menuitemradio', {name: 'POST'})).toBeChecked();
  97. await selectEvent.openMenu(input('Environment'));
  98. expect(screen.getByRole('menuitemradio', {name: 'prod'})).toBeChecked();
  99. });
  100. it('handles simple edits', async function () {
  101. // XXX(epurkhiser): This test covers the case where the formModel waws not
  102. // triggering the observer that updates the apiEndpoint url based on the
  103. // selected project for existing rules. The other tests all pass as the
  104. // triggered error state from clearing the fields causes the observer to be
  105. // called for the first time and correctly set the apiEndpoint.
  106. const rule = UptimeRuleFixture({
  107. name: 'Existing Rule',
  108. projectSlug: project.slug,
  109. url: 'https://existing-url.com',
  110. owner: ActorFixture(),
  111. });
  112. render(
  113. <UptimeAlertForm organization={organization} project={project} rule={rule} />,
  114. {organization}
  115. );
  116. await screen.findByText('Configure Request');
  117. await userEvent.type(input('URL'), '/test');
  118. const updateMock = MockApiClient.addMockResponse({
  119. url: `/projects/${organization.slug}/${project.slug}/uptime/${rule.id}/`,
  120. method: 'PUT',
  121. });
  122. await userEvent.click(screen.getByRole('button', {name: 'Save Rule'}));
  123. expect(updateMock).toHaveBeenCalledWith(
  124. expect.anything(),
  125. expect.objectContaining({
  126. data: expect.objectContaining({
  127. url: 'https://existing-url.com/test',
  128. }),
  129. })
  130. );
  131. });
  132. it('can edit an existing rule', async function () {
  133. OrganizationStore.onUpdate(organization);
  134. const rule = UptimeRuleFixture({
  135. name: 'Existing Rule',
  136. projectSlug: project.slug,
  137. url: 'https://existing-url.com',
  138. owner: ActorFixture(),
  139. });
  140. render(
  141. <UptimeAlertForm organization={organization} project={project} rule={rule} />,
  142. {organization}
  143. );
  144. await screen.findByText('Configure Request');
  145. await selectEvent.select(input('Interval'), 'Every 10 minutes');
  146. await selectEvent.select(input('Environment'), 'dev');
  147. await userEvent.clear(input('URL'));
  148. await userEvent.type(input('URL'), 'http://another-url.com');
  149. await selectEvent.clearAll(input('Method'));
  150. await selectEvent.select(input('Method'), 'POST');
  151. await userEvent.clear(input('Body'));
  152. await userEvent.type(input('Body'), '{{"different": "value"}');
  153. await userEvent.type(input('Name of header 1'), 'X-Something');
  154. await userEvent.type(input('Value of X-Something'), 'Header Value');
  155. await userEvent.click(screen.getByRole('button', {name: 'Add Header'}));
  156. await userEvent.type(input('Name of header 2'), 'X-Another');
  157. await userEvent.type(input('Value of X-Another'), 'Second Value');
  158. const name = input('Uptime rule name');
  159. await userEvent.clear(name);
  160. await userEvent.type(name, 'Updated name');
  161. await selectEvent.select(input('Owner'), 'Foo Bar');
  162. const updateMock = MockApiClient.addMockResponse({
  163. url: `/projects/${organization.slug}/${project.slug}/uptime/${rule.id}/`,
  164. method: 'PUT',
  165. });
  166. await userEvent.click(screen.getByRole('button', {name: 'Save Rule'}));
  167. expect(updateMock).toHaveBeenCalledWith(
  168. expect.anything(),
  169. expect.objectContaining({
  170. data: expect.objectContaining({
  171. name: 'Updated name',
  172. environment: 'dev',
  173. owner: 'user:1',
  174. url: 'http://another-url.com',
  175. method: 'POST',
  176. headers: [
  177. ['X-Something', 'Header Value'],
  178. ['X-Another', 'Second Value'],
  179. ],
  180. body: '{"different": "value"}',
  181. intervalSeconds: 60 * 10,
  182. }),
  183. })
  184. );
  185. });
  186. it('does not show body for GET and HEAD', async function () {
  187. OrganizationStore.onUpdate(organization);
  188. const rule = UptimeRuleFixture({
  189. projectSlug: project.slug,
  190. owner: ActorFixture(),
  191. });
  192. render(
  193. <UptimeAlertForm organization={organization} project={project} rule={rule} />,
  194. {organization}
  195. );
  196. await screen.findByText('Configure Request');
  197. // GET
  198. await selectEvent.clearAll(input('Method'));
  199. await selectEvent.select(input('Method'), 'GET');
  200. expect(screen.queryByRole('textbox', {name: 'Body'})).not.toBeInTheDocument();
  201. // HEAD
  202. await selectEvent.clearAll(input('Method'));
  203. await selectEvent.select(input('Method'), 'HEAD');
  204. expect(screen.queryByRole('textbox', {name: 'Body'})).not.toBeInTheDocument();
  205. // POST
  206. await selectEvent.clearAll(input('Method'));
  207. await selectEvent.select(input('Method'), 'POST');
  208. expect(input('Body')).toBeInTheDocument();
  209. });
  210. it('updates environments for different projects', async function () {
  211. OrganizationStore.onUpdate(organization);
  212. const project1 = ProjectFixture({
  213. slug: 'project-1',
  214. environments: ['dev-1', 'prod-1'],
  215. });
  216. const project2 = ProjectFixture({
  217. slug: 'project-2',
  218. environments: ['dev-2', 'prod-2'],
  219. });
  220. ProjectsStore.loadInitialData([project, project1, project2]);
  221. render(<UptimeAlertForm organization={organization} project={project} />, {
  222. organization,
  223. });
  224. await screen.findByText('Configure Request');
  225. // Select project 1
  226. await selectEvent.openMenu(input('Project'));
  227. expect(screen.getByRole('menuitemradio', {name: 'project-1'})).toBeInTheDocument();
  228. expect(screen.getByRole('menuitemradio', {name: 'project-2'})).toBeInTheDocument();
  229. await userEvent.click(screen.getByRole('menuitemradio', {name: 'project-1'}));
  230. // Verify correct envs
  231. await selectEvent.openMenu(input('Environment'));
  232. expect(screen.getByRole('menuitemradio', {name: 'dev-1'})).toBeInTheDocument();
  233. expect(screen.getByRole('menuitemradio', {name: 'prod-1'})).toBeInTheDocument();
  234. // Select project 2
  235. await selectEvent.openMenu(input('Project'));
  236. await userEvent.click(screen.getByRole('menuitemradio', {name: 'project-2'}));
  237. // Verify correct envs
  238. await selectEvent.openMenu(input('Environment'));
  239. expect(screen.getByRole('menuitemradio', {name: 'dev-2'})).toBeInTheDocument();
  240. expect(screen.getByRole('menuitemradio', {name: 'prod-2'})).toBeInTheDocument();
  241. });
  242. it('can create a new environment', async function () {
  243. OrganizationStore.onUpdate(organization);
  244. render(<UptimeAlertForm organization={organization} project={project} />, {
  245. organization,
  246. });
  247. await screen.findByText('Configure Request');
  248. await userEvent.type(input('Environment'), 'my-custom-env');
  249. await userEvent.click(
  250. screen.getByRole('menuitemradio', {name: 'Create "my-custom-env"'})
  251. );
  252. await userEvent.clear(input('URL'));
  253. await userEvent.type(input('URL'), 'http://example.com');
  254. const name = input('Uptime rule name');
  255. await userEvent.clear(name);
  256. await userEvent.type(name, 'New Uptime Rule');
  257. const updateMock = MockApiClient.addMockResponse({
  258. url: `/projects/${organization.slug}/${project.slug}/uptime/`,
  259. method: 'POST',
  260. });
  261. await userEvent.click(screen.getByRole('button', {name: 'Create Rule'}));
  262. expect(updateMock).toHaveBeenCalledWith(
  263. expect.anything(),
  264. expect.objectContaining({
  265. data: expect.objectContaining({}),
  266. })
  267. );
  268. });
  269. });