eventAttachments.spec.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import {ConfigFixture} from 'sentry-fixture/config';
  2. import {EventFixture} from 'sentry-fixture/event';
  3. import {EventAttachmentFixture} from 'sentry-fixture/eventAttachment';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {
  6. render,
  7. renderGlobalModal,
  8. screen,
  9. userEvent,
  10. waitFor,
  11. within,
  12. } from 'sentry-test/reactTestingLibrary';
  13. import {EventAttachments} from 'sentry/components/events/eventAttachments';
  14. import ConfigStore from 'sentry/stores/configStore';
  15. describe('EventAttachments', function () {
  16. const {router, organization, project} = initializeOrg({
  17. organization: {
  18. features: ['event-attachments'],
  19. orgRole: 'member',
  20. attachmentsRole: 'member',
  21. },
  22. });
  23. const event = EventFixture({metadata: {stripped_crash: false}});
  24. const props = {
  25. group: undefined,
  26. project: project,
  27. event,
  28. };
  29. const attachmentsUrl = `/projects/${organization.slug}/${project.slug}/events/${event.id}/attachments/`;
  30. beforeEach(() => {
  31. ConfigStore.loadInitialData(ConfigFixture());
  32. MockApiClient.clearMockResponses();
  33. });
  34. it('shows attachments limit reached notice with stripped_crash: true', async function () {
  35. MockApiClient.addMockResponse({
  36. url: attachmentsUrl,
  37. body: [],
  38. });
  39. const strippedCrashEvent = {...event, metadata: {stripped_crash: true}};
  40. render(<EventAttachments {...props} event={strippedCrashEvent} />, {
  41. router,
  42. organization,
  43. });
  44. expect(await screen.findByText('Attachments (0)')).toBeInTheDocument();
  45. await tick();
  46. expect(screen.getByRole('link', {name: 'View crashes'})).toHaveAttribute(
  47. 'href',
  48. '/organizations/org-slug/issues/1/attachments/?types=event.minidump&types=event.applecrashreport'
  49. );
  50. expect(screen.getByRole('link', {name: 'configure limit'})).toHaveAttribute(
  51. 'href',
  52. `/settings/org-slug/projects/${project.slug}/security-and-privacy/`
  53. );
  54. expect(
  55. screen.getByText(
  56. 'Your limit of stored crash reports has been reached for this issue.',
  57. {exact: false}
  58. )
  59. ).toBeInTheDocument();
  60. });
  61. it('does not render anything if no attachments (nor stripped) are available', async function () {
  62. MockApiClient.addMockResponse({
  63. url: attachmentsUrl,
  64. body: [],
  65. });
  66. const {container} = render(
  67. <EventAttachments
  68. {...props}
  69. event={{...event, metadata: {stripped_crash: false}}}
  70. />,
  71. {router, organization}
  72. );
  73. // No loading state to wait for
  74. await tick();
  75. expect(container).toBeEmptyDOMElement();
  76. });
  77. it('displays message when user lacks permission to preview an attachment', async function () {
  78. const {router: newRouter, organization: orgWithWrongAttachmentRole} = initializeOrg({
  79. organization: {
  80. features: ['event-attachments'],
  81. orgRole: 'member',
  82. attachmentsRole: 'admin',
  83. },
  84. });
  85. const attachment = EventAttachmentFixture({
  86. name: 'some_file.txt',
  87. headers: {
  88. 'Content-Type': 'text/plain',
  89. },
  90. mimetype: 'text/plain',
  91. size: 100,
  92. });
  93. MockApiClient.addMockResponse({
  94. url: attachmentsUrl,
  95. body: [attachment],
  96. });
  97. MockApiClient.addMockResponse({
  98. url: `/projects/org-slug/events/${event.id}/attachments/?download`,
  99. body: 'file contents',
  100. });
  101. render(<EventAttachments {...props} />, {
  102. router: newRouter,
  103. organization: orgWithWrongAttachmentRole,
  104. });
  105. expect(await screen.findByText('Attachments (1)')).toBeInTheDocument();
  106. expect(screen.getByRole('button', {name: /preview/i})).toBeDisabled();
  107. await userEvent.hover(screen.getByRole('button', {name: /preview/i}));
  108. await screen.findByText(/insufficient permissions to preview attachments/i);
  109. });
  110. it('can open attachment previews', async function () {
  111. const attachment = EventAttachmentFixture({
  112. name: 'some_file.txt',
  113. headers: {
  114. 'Content-Type': 'text/plain',
  115. },
  116. mimetype: 'text/plain',
  117. size: 100,
  118. });
  119. MockApiClient.addMockResponse({
  120. url: attachmentsUrl,
  121. body: [attachment],
  122. });
  123. MockApiClient.addMockResponse({
  124. url: `/projects/${organization.slug}/${project.slug}/events/${event.id}/attachments/1/?download`,
  125. body: 'file contents',
  126. });
  127. render(<EventAttachments {...props} />, {router, organization});
  128. expect(await screen.findByText('Attachments (1)')).toBeInTheDocument();
  129. await userEvent.click(screen.getByRole('button', {name: /preview/i}));
  130. expect(await screen.findByText('file contents')).toBeInTheDocument();
  131. });
  132. it('can delete attachments', async function () {
  133. const attachment1 = EventAttachmentFixture({
  134. id: '1',
  135. name: 'pic_1.png',
  136. });
  137. const attachment2 = EventAttachmentFixture({
  138. id: '2',
  139. name: 'pic_2.png',
  140. });
  141. MockApiClient.addMockResponse({
  142. url: attachmentsUrl,
  143. body: [attachment1, attachment2],
  144. });
  145. const deleteMock = MockApiClient.addMockResponse({
  146. url: '/projects/org-slug/project-slug/events/1/attachments/1/',
  147. method: 'DELETE',
  148. });
  149. render(<EventAttachments {...props} />, {router, organization});
  150. renderGlobalModal();
  151. expect(await screen.findByText('Attachments (2)')).toBeInTheDocument();
  152. await userEvent.click(screen.getAllByRole('button', {name: 'Delete'})[0]);
  153. await userEvent.click(
  154. within(screen.getByRole('dialog')).getByRole('button', {name: /delete/i})
  155. );
  156. // Should make the delete request and remove the attachment optimistically
  157. await waitFor(() => {
  158. expect(deleteMock).toHaveBeenCalled();
  159. expect(screen.queryByTestId('pic_1.png')).not.toBeInTheDocument();
  160. });
  161. });
  162. });