index.spec.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  3. import {textWithMarkupMatcher} from 'sentry-test/utils';
  4. import ContextSummary from 'sentry/components/events/contextSummary';
  5. import {ContextSummaryGPU} from 'sentry/components/events/contextSummary/contextSummaryGPU';
  6. import {ContextSummaryOS} from 'sentry/components/events/contextSummary/contextSummaryOS';
  7. import {ContextSummaryUser} from 'sentry/components/events/contextSummary/contextSummaryUser';
  8. import {FILTER_MASK} from 'sentry/constants';
  9. import {OrganizationContext} from 'sentry/views/organizationContext';
  10. import {RouteContext} from 'sentry/views/routeContext';
  11. const CONTEXT_USER = {
  12. email: 'mail@example.org',
  13. id: '1',
  14. };
  15. const CONTEXT_DEVICE = {
  16. arch: 'x86',
  17. family: 'iOS',
  18. model: 'iPhone10,5',
  19. type: 'device',
  20. };
  21. const CONTEXT_OS = {
  22. kernel_version: '17.5.0',
  23. version: '10.13.4',
  24. type: 'os',
  25. build: '17E199',
  26. name: 'Mac OS X',
  27. };
  28. const CONTEXT_OS_SERVER = {
  29. kernel_version: '4.3.0',
  30. version: '4.3.0',
  31. type: 'os',
  32. build: '123123123',
  33. name: 'Linux',
  34. };
  35. const CONTEXT_RUNTIME = {
  36. version: '1.7.13',
  37. type: 'runtime',
  38. name: 'Electron',
  39. };
  40. const CONTEXT_BROWSER = {
  41. version: '65.0.3325',
  42. name: 'Chrome',
  43. };
  44. function TestComponent({children}: {children: React.ReactNode}) {
  45. const {organization, router} = initializeOrg();
  46. return (
  47. <OrganizationContext.Provider value={organization}>
  48. <RouteContext.Provider
  49. value={{
  50. router,
  51. location: router.location,
  52. params: {},
  53. routes: [],
  54. }}
  55. >
  56. {children}
  57. </RouteContext.Provider>
  58. </OrganizationContext.Provider>
  59. );
  60. }
  61. describe('ContextSummary', function () {
  62. describe('render()', function () {
  63. it('renders nothing without contexts', function () {
  64. const event = {
  65. ...TestStubs.Event(),
  66. id: '',
  67. contexts: {},
  68. };
  69. const {container} = render(
  70. <TestComponent>
  71. <ContextSummary event={event} />
  72. </TestComponent>
  73. );
  74. expect(container).toSnapshot();
  75. });
  76. it('renders nothing with a single user context', function () {
  77. const event = {
  78. ...TestStubs.Event(),
  79. id: '',
  80. user: CONTEXT_USER,
  81. contexts: {},
  82. };
  83. const {container} = render(
  84. <TestComponent>
  85. <ContextSummary event={event} />
  86. </TestComponent>
  87. );
  88. expect(container).toSnapshot();
  89. });
  90. it('should bail out with empty contexts', function () {
  91. const event = {
  92. ...TestStubs.Event(),
  93. id: '',
  94. user: CONTEXT_USER,
  95. contexts: {
  96. device: {},
  97. os: {},
  98. },
  99. };
  100. const {container} = render(
  101. <TestComponent>
  102. <ContextSummary event={event} />
  103. </TestComponent>
  104. );
  105. expect(container).toSnapshot();
  106. });
  107. it('renders at least three contexts', function () {
  108. const event = {
  109. ...TestStubs.Event(),
  110. id: '',
  111. user: CONTEXT_USER,
  112. contexts: {
  113. device: CONTEXT_DEVICE,
  114. },
  115. };
  116. const {container} = render(
  117. <TestComponent>
  118. <ContextSummary event={event} />
  119. </TestComponent>
  120. );
  121. expect(container).toSnapshot();
  122. });
  123. it('renders up to four contexts', function () {
  124. const event = {
  125. ...TestStubs.Event(),
  126. id: '',
  127. user: CONTEXT_USER,
  128. contexts: {
  129. os: CONTEXT_OS,
  130. browser: CONTEXT_BROWSER,
  131. runtime: CONTEXT_RUNTIME,
  132. device: CONTEXT_DEVICE, // must be omitted
  133. },
  134. };
  135. const {container} = render(
  136. <TestComponent>
  137. <ContextSummary event={event} />
  138. </TestComponent>
  139. );
  140. expect(container).toSnapshot();
  141. });
  142. it('should prefer client_os over os', function () {
  143. const event = {
  144. ...TestStubs.Event(),
  145. id: '',
  146. user: CONTEXT_USER,
  147. contexts: {
  148. client_os: CONTEXT_OS,
  149. os: CONTEXT_OS_SERVER,
  150. browser: CONTEXT_BROWSER,
  151. runtime: CONTEXT_RUNTIME,
  152. },
  153. };
  154. const {container} = render(
  155. <TestComponent>
  156. <ContextSummary event={event} />
  157. </TestComponent>
  158. );
  159. expect(container).toSnapshot();
  160. });
  161. it('renders client_os too', function () {
  162. const event = {
  163. ...TestStubs.Event(),
  164. id: '',
  165. user: CONTEXT_USER,
  166. contexts: {
  167. client_os: CONTEXT_OS,
  168. browser: CONTEXT_BROWSER,
  169. runtime: CONTEXT_RUNTIME,
  170. },
  171. };
  172. const {container} = render(
  173. <TestComponent>
  174. <ContextSummary event={event} />
  175. </TestComponent>
  176. );
  177. expect(container).toSnapshot();
  178. });
  179. it('should skip non-default named contexts', function () {
  180. const event = {
  181. ...TestStubs.Event(),
  182. id: '',
  183. user: CONTEXT_USER,
  184. contexts: {
  185. os: CONTEXT_OS,
  186. chrome: CONTEXT_BROWSER, // non-standard context
  187. runtime: CONTEXT_RUNTIME,
  188. device: CONTEXT_DEVICE,
  189. },
  190. };
  191. const {container} = render(
  192. <TestComponent>
  193. <ContextSummary event={event} />
  194. </TestComponent>
  195. );
  196. expect(container).toSnapshot();
  197. });
  198. it('should skip a missing user context', function () {
  199. const event = {
  200. ...TestStubs.Event(),
  201. id: '',
  202. contexts: {
  203. os: CONTEXT_OS,
  204. chrome: CONTEXT_BROWSER, // non-standard context
  205. runtime: CONTEXT_RUNTIME,
  206. device: CONTEXT_DEVICE,
  207. },
  208. };
  209. const {container} = render(
  210. <TestComponent>
  211. <ContextSummary event={event} />
  212. </TestComponent>
  213. );
  214. expect(container).toSnapshot();
  215. });
  216. });
  217. });
  218. describe('OsSummary', function () {
  219. describe('render()', function () {
  220. it('renders the version string', function () {
  221. const {container} = render(
  222. <TestComponent>
  223. <ContextSummaryOS
  224. data={{
  225. kernel_version: '17.5.0',
  226. version: '10.13.4',
  227. name: 'Mac OS X',
  228. }}
  229. meta={{}}
  230. />
  231. </TestComponent>
  232. );
  233. expect(container).toSnapshot();
  234. });
  235. it('renders the kernel version when no version', function () {
  236. const {container} = render(
  237. <TestComponent>
  238. <ContextSummaryOS
  239. data={{
  240. kernel_version: '17.5.0',
  241. name: 'Mac OS X',
  242. }}
  243. meta={{}}
  244. />
  245. </TestComponent>
  246. );
  247. expect(container).toSnapshot();
  248. });
  249. it('renders unknown when no version', function () {
  250. const {container} = render(
  251. <TestComponent>
  252. <ContextSummaryOS
  253. data={{
  254. name: 'Mac OS X',
  255. }}
  256. meta={{}}
  257. />
  258. </TestComponent>
  259. );
  260. expect(container).toSnapshot();
  261. });
  262. it('display redacted name', async function () {
  263. render(
  264. <TestComponent>
  265. <ContextSummaryOS
  266. data={{
  267. name: '',
  268. version: '10',
  269. }}
  270. meta={{
  271. name: {
  272. '': {
  273. rem: [['project:0', 's', 0, 0]],
  274. len: 19,
  275. },
  276. },
  277. }}
  278. />
  279. </TestComponent>
  280. );
  281. userEvent.hover(screen.getByText(/redacted/));
  282. expect(
  283. await screen.findByText(
  284. textWithMarkupMatcher('Replaced because of the PII rule project:0') // Fall back case
  285. )
  286. ).toBeInTheDocument(); // tooltip description
  287. });
  288. it('handles invalid data', async function () {
  289. render(
  290. <TestComponent>
  291. <ContextSummaryOS
  292. data={{
  293. name: false,
  294. version: false,
  295. }}
  296. meta={{
  297. name: {
  298. '': {
  299. rem: [['project:0', 's', 0, 0]],
  300. len: 19,
  301. },
  302. },
  303. }}
  304. />
  305. </TestComponent>
  306. );
  307. userEvent.hover(screen.getByText(/redacted/));
  308. expect(
  309. await screen.findByText(
  310. textWithMarkupMatcher('Replaced because of the PII rule project:0') // Fall back case
  311. )
  312. ).toBeInTheDocument(); // tooltip description
  313. });
  314. });
  315. });
  316. describe('GpuSummary', function () {
  317. describe('render()', function () {
  318. it('renders name and vendor', function () {
  319. const {container} = render(
  320. <TestComponent>
  321. <ContextSummaryGPU
  322. data={{
  323. name: 'Mali-T880',
  324. vendor_name: 'ARM',
  325. }}
  326. meta={{}}
  327. />
  328. </TestComponent>
  329. );
  330. expect(container).toSnapshot();
  331. });
  332. it('renders unknown when no vendor', function () {
  333. const {container} = render(
  334. <TestComponent>
  335. <ContextSummaryGPU
  336. data={{
  337. name: 'Apple A8 GPU',
  338. }}
  339. meta={{}}
  340. />
  341. </TestComponent>
  342. );
  343. expect(container).toSnapshot();
  344. });
  345. it('display redacted name', async function () {
  346. render(
  347. <TestComponent>
  348. <ContextSummaryGPU
  349. data={{
  350. name: '',
  351. }}
  352. meta={{
  353. name: {
  354. '': {
  355. rem: [['project:0', 's', 0, 0]],
  356. len: 19,
  357. },
  358. },
  359. }}
  360. />
  361. </TestComponent>
  362. );
  363. userEvent.hover(screen.getByText(/redacted/));
  364. expect(
  365. await screen.findByText(
  366. textWithMarkupMatcher('Replaced because of the PII rule project:0')
  367. ) // Fall back case)
  368. ).toBeInTheDocument(); // tooltip description
  369. });
  370. });
  371. });
  372. describe('UserSummary', function () {
  373. describe('render', function () {
  374. it('prefers email, then IP, then id, then username for title', function () {
  375. const user1 = {
  376. email: 'maisey@dogsrule.com',
  377. ip_address: '12.31.20.12',
  378. id: '26',
  379. username: 'maiseythedog',
  380. name: 'Maisey Dog',
  381. };
  382. const {rerender} = render(
  383. <TestComponent>
  384. <ContextSummaryUser data={user1} meta={{}} />
  385. </TestComponent>
  386. );
  387. expect(screen.getByText(user1.email)).toBeInTheDocument();
  388. const user2 = {
  389. ip_address: '12.31.20.12',
  390. id: '26',
  391. username: 'maiseythedog',
  392. name: 'Maisey Dog',
  393. };
  394. rerender(
  395. <TestComponent>
  396. <ContextSummaryUser data={user2} meta={{}} />
  397. </TestComponent>
  398. );
  399. expect(screen.getByTestId('user-title')?.textContent).toEqual(user2.ip_address);
  400. const user3 = {
  401. id: '26',
  402. username: 'maiseythedog',
  403. name: 'Maisey Dog',
  404. };
  405. rerender(
  406. <TestComponent>
  407. <ContextSummaryUser
  408. data={{
  409. id: '26',
  410. username: 'maiseythedog',
  411. name: 'Maisey Dog',
  412. }}
  413. meta={{}}
  414. />
  415. </TestComponent>
  416. );
  417. expect(screen.getByTestId('user-title')?.textContent).toEqual(user3.id);
  418. const user4 = {
  419. username: 'maiseythedog',
  420. name: 'Maisey Dog',
  421. };
  422. rerender(
  423. <TestComponent>
  424. <ContextSummaryUser data={user4} meta={{}} />
  425. </TestComponent>
  426. );
  427. expect(screen.getByTestId('user-title')).toHaveTextContent(user4.username);
  428. });
  429. it('renders NoSummary if no email, IP, id, or username', function () {
  430. render(
  431. <TestComponent>
  432. <ContextSummaryUser
  433. data={{
  434. name: 'Maisey Dog',
  435. }}
  436. meta={{}}
  437. />
  438. </TestComponent>
  439. );
  440. expect(screen.queryByTestId('user-title')).not.toBeInTheDocument();
  441. expect(screen.getByTestId('no-summary-title')).toHaveTextContent('Unknown User');
  442. });
  443. it('does not use filtered values for title', function () {
  444. const {rerender} = render(
  445. <TestComponent>
  446. <ContextSummaryUser
  447. data={{
  448. email: FILTER_MASK,
  449. }}
  450. meta={{}}
  451. />
  452. </TestComponent>
  453. );
  454. expect(screen.queryByTestId('user-title')).not.toBeInTheDocument();
  455. expect(screen.getByTestId('no-summary-title')).toHaveTextContent('Unknown User');
  456. // TODO: currently, the IP filter just eliminates IP addresses rather than
  457. // filtering them like other user data, so here, where you'd expect a filtered
  458. // IP address, there isn't one. Add a similar entry to the above and below
  459. // if/when that changes.
  460. rerender(
  461. <TestComponent>
  462. <ContextSummaryUser
  463. data={{
  464. id: FILTER_MASK,
  465. }}
  466. meta={{}}
  467. />
  468. </TestComponent>
  469. );
  470. expect(screen.queryByTestId('user-title')).not.toBeInTheDocument();
  471. expect(screen.getByTestId('no-summary-title')).toHaveTextContent('Unknown User');
  472. rerender(
  473. <TestComponent>
  474. <ContextSummaryUser
  475. data={{
  476. username: FILTER_MASK,
  477. }}
  478. meta={{}}
  479. />
  480. </TestComponent>
  481. );
  482. expect(screen.queryByTestId('user-title')).not.toBeInTheDocument();
  483. expect(screen.getByTestId('no-summary-title')).toHaveTextContent('Unknown User');
  484. });
  485. it('does not use filtered values for avatar', function () {
  486. // id is never used for avatar purposes, but is enough to keep us from
  487. // ending up with a NoSummary component where the UserSummary component
  488. // should be
  489. const {rerender} = render(
  490. <TestComponent>
  491. <ContextSummaryUser
  492. data={{
  493. id: '26',
  494. name: FILTER_MASK,
  495. }}
  496. meta={{}}
  497. />
  498. </TestComponent>
  499. );
  500. expect(screen.getByText('?')).toBeInTheDocument();
  501. rerender(
  502. <TestComponent>
  503. <ContextSummaryUser
  504. data={{
  505. id: '26',
  506. email: FILTER_MASK,
  507. }}
  508. meta={{}}
  509. />
  510. </TestComponent>
  511. );
  512. expect(screen.getByText('?')).toBeInTheDocument();
  513. rerender(
  514. <TestComponent>
  515. <ContextSummaryUser
  516. data={{
  517. id: '26',
  518. username: FILTER_MASK,
  519. }}
  520. meta={{}}
  521. />
  522. </TestComponent>
  523. );
  524. expect(screen.getByText('?')).toBeInTheDocument();
  525. });
  526. it('display redacted email', async function () {
  527. render(
  528. <TestComponent>
  529. <ContextSummaryUser
  530. data={{
  531. name: 'Maisey Dog',
  532. email: '',
  533. }}
  534. meta={{
  535. email: {
  536. '': {
  537. rem: [['project:0', 's', 0, 0]],
  538. len: 19,
  539. },
  540. },
  541. }}
  542. />
  543. </TestComponent>
  544. );
  545. userEvent.hover(screen.getByText(/redacted/));
  546. expect(
  547. await screen.findByText(
  548. textWithMarkupMatcher('Replaced because of the PII rule project:0') // Fall back case
  549. )
  550. ).toBeInTheDocument(); // tooltip description
  551. });
  552. });
  553. });