uptimeAlertForm.spec.tsx 12 KB

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