content.spec.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  3. import {textWithMarkupMatcher} from 'sentry-test/utils';
  4. import {Content} from 'sentry/components/events/interfaces/crashContent/exception/content';
  5. import ProjectsStore from 'sentry/stores/projectsStore';
  6. import {EntryType} from 'sentry/types';
  7. import {STACK_TYPE, STACK_VIEW} from 'sentry/types/stacktrace';
  8. describe('Exception Content', function () {
  9. it('display redacted values from exception entry', async function () {
  10. const project = TestStubs.Project({
  11. id: '0',
  12. relayPiiConfig: JSON.stringify(TestStubs.DataScrubbingRelayPiiConfig()),
  13. });
  14. const {organization, router, routerContext} = initializeOrg({
  15. router: {
  16. location: {query: {project: '0'}},
  17. },
  18. project: '0',
  19. projects: [project],
  20. });
  21. ProjectsStore.loadInitialData([project]);
  22. const event = {
  23. ...TestStubs.Event(),
  24. _meta: {
  25. entries: {
  26. 0: {
  27. data: {
  28. values: {
  29. '0': {
  30. mechanism: {
  31. data: {
  32. relevant_address: {
  33. '': {
  34. rem: [['project:0', 's', 0, 0]],
  35. len: 43,
  36. },
  37. },
  38. },
  39. },
  40. value: {
  41. '': {
  42. rem: [['project:0', 's', 0, 0]],
  43. len: 43,
  44. },
  45. },
  46. },
  47. },
  48. },
  49. },
  50. },
  51. },
  52. entries: [
  53. {
  54. type: EntryType.EXCEPTION,
  55. data: {
  56. values: [
  57. {
  58. mechanism: {
  59. type: 'celery',
  60. handled: false,
  61. data: {relevant_address: null},
  62. },
  63. module: 'sentry.models.organization',
  64. rawStacktrace: null,
  65. stacktrace: {
  66. frames: [
  67. {
  68. function: null,
  69. colNo: null,
  70. vars: {},
  71. symbol: null,
  72. module: '<unknown module>',
  73. lineNo: null,
  74. errors: null,
  75. package: null,
  76. absPath:
  77. 'https://sentry.io/hiventy/kraken-prod/issues/438681831/?referrer=slack#',
  78. inApp: false,
  79. instructionAddr: null,
  80. filename: '/hiventy/kraken-prod/issues/438681831/',
  81. platform: null,
  82. context: [],
  83. symbolAddr: null,
  84. },
  85. ],
  86. framesOmitted: null,
  87. registers: null,
  88. hasSystemFrames: false,
  89. },
  90. threadId: null,
  91. type: 'Organization.DoesNotExist',
  92. value: null,
  93. },
  94. ],
  95. },
  96. },
  97. ],
  98. };
  99. render(
  100. <Content
  101. type={STACK_TYPE.ORIGINAL}
  102. groupingCurrentLevel={0}
  103. hasHierarchicalGrouping
  104. newestFirst
  105. platform="python"
  106. stackView={STACK_VIEW.APP}
  107. event={event}
  108. values={event.entries[0].data.values}
  109. meta={event._meta.entries[0].data.values}
  110. projectSlug={project.slug}
  111. />,
  112. {organization, router, context: routerContext}
  113. );
  114. expect(screen.getAllByText(/redacted/)).toHaveLength(2);
  115. await userEvent.hover(screen.getAllByText(/redacted/)[0]);
  116. expect(
  117. await screen.findByText(
  118. textWithMarkupMatcher(
  119. 'Replaced because of the data scrubbing rule [Replace] [Password fields] with [Scrubbed] from [password] in the settings of the project project-slug'
  120. )
  121. )
  122. ).toBeInTheDocument(); // tooltip description
  123. expect(
  124. screen.getByRole('link', {
  125. name: '[Replace] [Password fields] with [Scrubbed] from [password]',
  126. })
  127. ).toHaveAttribute(
  128. 'href',
  129. '/settings/org-slug/projects/project-slug/security-and-privacy/advanced-data-scrubbing/0/'
  130. );
  131. expect(screen.getByRole('link', {name: 'project-slug'})).toHaveAttribute(
  132. 'href',
  133. '/settings/org-slug/projects/project-slug/security-and-privacy/'
  134. );
  135. });
  136. describe('exception groups', function () {
  137. const event = TestStubs.Event({entries: [TestStubs.EventEntryExceptionGroup()]});
  138. const project = TestStubs.Project();
  139. const defaultProps = {
  140. type: STACK_TYPE.ORIGINAL,
  141. hasHierarchicalGrouping: false,
  142. newestFirst: true,
  143. platform: 'python' as const,
  144. stackView: STACK_VIEW.APP,
  145. event,
  146. values: event.entries[0].data.values,
  147. projectSlug: project.slug,
  148. };
  149. it('displays exception group tree under first exception', function () {
  150. render(<Content {...defaultProps} />);
  151. const exceptions = screen.getAllByTestId('exception-value');
  152. // First exception should be the parent ExceptionGroup
  153. expect(within(exceptions[0]).getByText('ExceptionGroup 1')).toBeInTheDocument();
  154. expect(
  155. within(exceptions[0]).getByRole('heading', {name: 'ExceptionGroup 1'})
  156. ).toBeInTheDocument();
  157. });
  158. it('displays exception group tree in first frame when there is no other context', function () {
  159. render(<Content {...defaultProps} />);
  160. const exceptions = screen.getAllByTestId('exception-value');
  161. const exceptionGroupWithNoContext = exceptions[2];
  162. expect(
  163. within(exceptionGroupWithNoContext).getByText('Related Exceptions')
  164. ).toBeInTheDocument();
  165. });
  166. it('collapses sub-groups by default', async function () {
  167. render(<Content {...defaultProps} />);
  168. // There are 4 values, but 1 should be hidden
  169. expect(screen.getAllByTestId('exception-value').length).toBe(3);
  170. expect(screen.queryByRole('heading', {name: 'ValueError'})).not.toBeInTheDocument();
  171. await userEvent.click(
  172. screen.getByRole('button', {name: /show 1 related exception/i})
  173. );
  174. // After expanding, ValueError should be visible
  175. expect(screen.getAllByTestId('exception-value').length).toBe(4);
  176. expect(screen.getByRole('heading', {name: 'ValueError'})).toBeInTheDocument();
  177. await userEvent.click(
  178. screen.getByRole('button', {name: /hide 1 related exception/i})
  179. );
  180. // After collapsing, ValueError should be gone again
  181. expect(screen.getAllByTestId('exception-value').length).toBe(3);
  182. expect(screen.queryByRole('heading', {name: 'ValueError'})).not.toBeInTheDocument();
  183. });
  184. it('auto-opens sub-groups when clicking link in tree', async function () {
  185. render(<Content {...defaultProps} />);
  186. expect(screen.queryByRole('heading', {name: 'ValueError'})).not.toBeInTheDocument();
  187. await userEvent.click(screen.getByRole('button', {name: /ValueError: test/i}));
  188. // After expanding, ValueError should be visible
  189. expect(screen.getByRole('heading', {name: 'ValueError'})).toBeInTheDocument();
  190. });
  191. });
  192. });