groupDetails.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. import {ConfigFixture} from 'sentry-fixture/config';
  2. import {EnvironmentsFixture} from 'sentry-fixture/environments';
  3. import {EventFixture} from 'sentry-fixture/event';
  4. import {GroupFixture} from 'sentry-fixture/group';
  5. import {LocationFixture} from 'sentry-fixture/locationFixture';
  6. import {ProjectFixture} from 'sentry-fixture/project';
  7. import {TeamFixture} from 'sentry-fixture/team';
  8. import {UserFixture} from 'sentry-fixture/user';
  9. import {initializeOrg} from 'sentry-test/initializeOrg';
  10. import {act, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  11. import ConfigStore from 'sentry/stores/configStore';
  12. import GroupStore from 'sentry/stores/groupStore';
  13. import OrganizationStore from 'sentry/stores/organizationStore';
  14. import PageFiltersStore from 'sentry/stores/pageFiltersStore';
  15. import ProjectsStore from 'sentry/stores/projectsStore';
  16. import type {Group} from 'sentry/types/group';
  17. import {IssueCategory} from 'sentry/types/group';
  18. import type {Environment} from 'sentry/types/project';
  19. import GroupDetails from 'sentry/views/issueDetails/groupDetails';
  20. jest.unmock('sentry/utils/recreateRoute');
  21. const SAMPLE_EVENT_ALERT_TEXT =
  22. 'You are viewing a sample error. Configure Sentry to start viewing real errors.';
  23. describe('groupDetails', () => {
  24. const group = GroupFixture({issueCategory: IssueCategory.ERROR});
  25. const event = EventFixture();
  26. const project = ProjectFixture({teams: [TeamFixture()]});
  27. const routes = [
  28. {path: '/', childRoutes: []},
  29. {childRoutes: []},
  30. {
  31. path: '/organizations/:orgId/issues/:groupId/',
  32. childRoutes: [],
  33. },
  34. {},
  35. ];
  36. const initRouter = {
  37. location: {
  38. pathname: `/organizations/org-slug/issues/${group.id}/`,
  39. query: {},
  40. search: '?foo=bar',
  41. hash: '#hash',
  42. },
  43. params: {
  44. groupId: group.id,
  45. },
  46. routes,
  47. };
  48. const defaultInit = initializeOrg<{groupId: string}>({
  49. router: initRouter,
  50. });
  51. const recommendedUser = UserFixture({
  52. options: {
  53. ...UserFixture().options,
  54. defaultIssueEvent: 'recommended',
  55. },
  56. });
  57. const latestUser = UserFixture({
  58. options: {
  59. ...UserFixture().options,
  60. defaultIssueEvent: 'latest',
  61. },
  62. });
  63. const oldestUser = UserFixture({
  64. options: {
  65. ...UserFixture().options,
  66. defaultIssueEvent: 'oldest',
  67. },
  68. });
  69. function MockComponent({
  70. group: groupProp,
  71. environments,
  72. eventError,
  73. }: {
  74. environments?: Environment[];
  75. eventError?: boolean;
  76. group?: Group;
  77. }) {
  78. return (
  79. <div>
  80. Group Details Mock
  81. <div>title: {groupProp?.title}</div>
  82. <div>environment: {environments?.join(' ')}</div>
  83. {eventError && <div>eventError</div>}
  84. </div>
  85. );
  86. }
  87. const createWrapper = (init = defaultInit) => {
  88. return render(
  89. <GroupDetails {...init.routerProps}>
  90. <MockComponent />
  91. </GroupDetails>,
  92. {organization: init.organization, router: init.router}
  93. );
  94. };
  95. beforeEach(() => {
  96. MockApiClient.clearMockResponses();
  97. OrganizationStore.onUpdate(defaultInit.organization);
  98. act(() => ProjectsStore.loadInitialData(defaultInit.projects));
  99. MockApiClient.addMockResponse({
  100. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
  101. body: {...group},
  102. });
  103. MockApiClient.addMockResponse({
  104. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
  105. statusCode: 200,
  106. body: {
  107. ...event,
  108. },
  109. });
  110. MockApiClient.addMockResponse({
  111. url: `/projects/org-slug/${project.slug}/issues/`,
  112. method: 'PUT',
  113. body: {
  114. hasSeen: false,
  115. },
  116. });
  117. MockApiClient.addMockResponse({
  118. url: '/organizations/org-slug/projects/',
  119. body: [project],
  120. });
  121. MockApiClient.addMockResponse({
  122. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/first-last-release/`,
  123. method: 'GET',
  124. });
  125. MockApiClient.addMockResponse({
  126. url: `/organizations/${defaultInit.organization.slug}/events/`,
  127. statusCode: 200,
  128. body: {
  129. data: [
  130. {
  131. 'count()': 1,
  132. },
  133. ],
  134. },
  135. });
  136. MockApiClient.addMockResponse({
  137. url: `/organizations/${defaultInit.organization.slug}/environments/`,
  138. body: EnvironmentsFixture(),
  139. });
  140. MockApiClient.addMockResponse({
  141. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/tags/`,
  142. body: [],
  143. });
  144. MockApiClient.addMockResponse({
  145. url: '/organizations/org-slug/replay-count/',
  146. body: {},
  147. });
  148. MockApiClient.addMockResponse({
  149. url: `/projects/${defaultInit.organization.slug}/${project.slug}/`,
  150. body: project,
  151. });
  152. });
  153. afterEach(() => {
  154. act(() => ProjectsStore.reset());
  155. GroupStore.reset();
  156. PageFiltersStore.reset();
  157. MockApiClient.clearMockResponses();
  158. });
  159. it('renders', async function () {
  160. act(() => ProjectsStore.reset());
  161. createWrapper();
  162. expect(screen.queryByText(group.title)).not.toBeInTheDocument();
  163. act(() => ProjectsStore.loadInitialData(defaultInit.projects));
  164. expect(await screen.findByText(group.title, {exact: false})).toBeInTheDocument();
  165. // Sample event alert should not show up
  166. expect(screen.queryByText(SAMPLE_EVENT_ALERT_TEXT)).not.toBeInTheDocument();
  167. });
  168. it('renders error when issue is not found', async function () {
  169. MockApiClient.addMockResponse({
  170. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
  171. statusCode: 404,
  172. });
  173. MockApiClient.addMockResponse({
  174. url: `/organization/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
  175. statusCode: 404,
  176. });
  177. createWrapper();
  178. await waitFor(() =>
  179. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
  180. );
  181. expect(
  182. await screen.findByText('The issue you were looking for was not found.')
  183. ).toBeInTheDocument();
  184. });
  185. it('renders MissingProjectMembership when trying to access issue in project the user does not belong to', async function () {
  186. MockApiClient.addMockResponse({
  187. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
  188. statusCode: 403,
  189. });
  190. MockApiClient.addMockResponse({
  191. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
  192. statusCode: 403,
  193. });
  194. createWrapper();
  195. await waitFor(() =>
  196. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
  197. );
  198. expect(
  199. await screen.findByText(
  200. 'No teams have access to this project yet. Ask an admin to add your team to this project.'
  201. )
  202. ).toBeInTheDocument();
  203. });
  204. it('fetches issue details for a given environment', async function () {
  205. const init = initializeOrg({
  206. router: {
  207. ...initRouter,
  208. location: LocationFixture({
  209. ...initRouter.location,
  210. query: {environment: 'staging'},
  211. }),
  212. },
  213. });
  214. createWrapper(init);
  215. await waitFor(() =>
  216. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
  217. );
  218. expect(await screen.findByText('environment: staging')).toBeInTheDocument();
  219. });
  220. it('renders issue event error', async function () {
  221. MockApiClient.addMockResponse({
  222. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
  223. statusCode: 404,
  224. });
  225. createWrapper();
  226. expect(await screen.findByText('eventError')).toBeInTheDocument();
  227. });
  228. it('renders substatus badge', async function () {
  229. MockApiClient.addMockResponse({
  230. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
  231. body: {
  232. ...group,
  233. inbox: null,
  234. status: 'unresolved',
  235. substatus: 'ongoing',
  236. },
  237. });
  238. createWrapper({
  239. ...defaultInit,
  240. organization: {...defaultInit.organization},
  241. });
  242. expect(await screen.findByText('Ongoing')).toBeInTheDocument();
  243. });
  244. it('renders alert for sample event', async function () {
  245. MockApiClient.addMockResponse({
  246. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/tags/`,
  247. body: [{key: 'sample_event'}],
  248. });
  249. createWrapper();
  250. expect(await screen.findByText(SAMPLE_EVENT_ALERT_TEXT)).toBeInTheDocument();
  251. });
  252. it('renders error when project does not exist', async function () {
  253. MockApiClient.addMockResponse({
  254. url: `/projects/org-slug/other-project-slug/issues/`,
  255. method: 'PUT',
  256. });
  257. MockApiClient.addMockResponse({
  258. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
  259. body: {...group, project: {slug: 'other-project-slug'}},
  260. });
  261. MockApiClient.addMockResponse({
  262. url: `/projects/${defaultInit.organization.slug}/other-project-slug/`,
  263. body: {},
  264. });
  265. createWrapper();
  266. expect(
  267. await screen.findByText('The project other-project-slug does not exist')
  268. ).toBeInTheDocument();
  269. });
  270. it('uses /recommended endpoint when feature flag is on and no event is provided', async function () {
  271. const recommendedMock = MockApiClient.addMockResponse({
  272. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
  273. statusCode: 200,
  274. body: event,
  275. });
  276. createWrapper();
  277. await waitFor(() => expect(recommendedMock).toHaveBeenCalledTimes(1));
  278. });
  279. it('uses /latest endpoint when default is set to latest', async function () {
  280. ConfigStore.loadInitialData(ConfigFixture({user: latestUser}));
  281. const latestMock = MockApiClient.addMockResponse({
  282. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/latest/`,
  283. statusCode: 200,
  284. body: event,
  285. });
  286. createWrapper();
  287. await waitFor(() => expect(latestMock).toHaveBeenCalledTimes(1));
  288. });
  289. it('uses /oldest endpoint when default is set to oldest', async function () {
  290. ConfigStore.loadInitialData(ConfigFixture({user: oldestUser}));
  291. const oldestMock = MockApiClient.addMockResponse({
  292. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/oldest/`,
  293. statusCode: 200,
  294. body: event,
  295. });
  296. createWrapper();
  297. await waitFor(() => expect(oldestMock).toHaveBeenCalledTimes(1));
  298. });
  299. it('uses /recommended endpoint when default is set to recommended', async function () {
  300. ConfigStore.loadInitialData(ConfigFixture({user: recommendedUser}));
  301. const recommendedMock = MockApiClient.addMockResponse({
  302. url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
  303. statusCode: 200,
  304. body: event,
  305. });
  306. createWrapper();
  307. await waitFor(() => expect(recommendedMock).toHaveBeenCalledTimes(1));
  308. });
  309. });