projectSourceMapsArtifacts.spec.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import {Organization} from 'sentry-fixture/organization';
  2. import {SourceMapArchive} from 'sentry-fixture/sourceMapArchive';
  3. import {SourceMapArtifact} from 'sentry-fixture/sourceMapArtifact';
  4. import {SourceMapsDebugIDBundlesArtifacts} from 'sentry-fixture/sourceMapsDebugIDBundlesArtifacts';
  5. import {initializeOrg} from 'sentry-test/initializeOrg';
  6. import {
  7. render,
  8. renderGlobalModal,
  9. screen,
  10. userEvent,
  11. waitFor,
  12. } from 'sentry-test/reactTestingLibrary';
  13. import {textWithMarkupMatcher} from 'sentry-test/utils';
  14. import ConfigStore from 'sentry/stores/configStore';
  15. import OrganizationStore from 'sentry/stores/organizationStore';
  16. import {ProjectSourceMapsArtifacts} from 'sentry/views/settings/projectSourceMaps/projectSourceMapsArtifacts';
  17. function renderReleaseBundlesMockRequests({
  18. orgSlug,
  19. projectSlug,
  20. empty,
  21. }: {
  22. orgSlug: string;
  23. projectSlug: string;
  24. empty?: boolean;
  25. }) {
  26. const sourceMaps = MockApiClient.addMockResponse({
  27. url: `/projects/${orgSlug}/${projectSlug}/files/source-maps/`,
  28. body: empty
  29. ? []
  30. : [
  31. SourceMapArchive(),
  32. SourceMapArchive({
  33. id: 2,
  34. name: 'abc',
  35. fileCount: 3,
  36. date: '2023-05-06T13:41:00Z',
  37. }),
  38. ],
  39. });
  40. const sourceMapsFiles = MockApiClient.addMockResponse({
  41. url: `/projects/${orgSlug}/${projectSlug}/releases/bea7335dfaebc0ca6e65a057/files/`,
  42. body: empty ? [] : [SourceMapArtifact()],
  43. });
  44. return {sourceMaps, sourceMapsFiles};
  45. }
  46. function renderDebugIdBundlesMockRequests({
  47. orgSlug,
  48. projectSlug,
  49. empty,
  50. }: {
  51. orgSlug: string;
  52. projectSlug: string;
  53. empty?: boolean;
  54. }) {
  55. const artifactBundlesFiles = MockApiClient.addMockResponse({
  56. url: `/projects/${orgSlug}/${projectSlug}/artifact-bundles/7227e105-744e-4066-8c69-3e5e344723fc/files/`,
  57. body: SourceMapsDebugIDBundlesArtifacts(
  58. empty
  59. ? {
  60. fileCount: 0,
  61. associations: [],
  62. files: [],
  63. }
  64. : {}
  65. ),
  66. });
  67. const artifactBundlesDeletion = MockApiClient.addMockResponse({
  68. url: `/projects/${orgSlug}/${projectSlug}/files/artifact-bundles/`,
  69. method: 'DELETE',
  70. });
  71. return {artifactBundlesFiles, artifactBundlesDeletion};
  72. }
  73. describe('ProjectSourceMapsArtifacts', function () {
  74. beforeEach(function () {
  75. OrganizationStore.init();
  76. });
  77. describe('Release Bundles', function () {
  78. it('renders default state', async function () {
  79. const {organization, routerContext, project, routerProps} = initializeOrg({
  80. organization: Organization({
  81. access: ['org:superuser'],
  82. }),
  83. router: {
  84. location: {
  85. query: {},
  86. },
  87. params: {},
  88. },
  89. });
  90. OrganizationStore.onUpdate(organization, {replace: true});
  91. ConfigStore.config = {
  92. ...ConfigStore.config,
  93. user: {...ConfigStore.config.user, isSuperuser: true},
  94. };
  95. renderReleaseBundlesMockRequests({
  96. orgSlug: organization.slug,
  97. projectSlug: project.slug,
  98. });
  99. render(
  100. <ProjectSourceMapsArtifacts
  101. {...routerProps}
  102. project={project}
  103. params={{
  104. orgId: organization.slug,
  105. projectId: project.slug,
  106. bundleId: 'bea7335dfaebc0ca6e65a057',
  107. }}
  108. />,
  109. {context: routerContext, organization}
  110. );
  111. // Title
  112. expect(screen.getByRole('heading')).toHaveTextContent('Release Bundle');
  113. // Subtitle
  114. expect(screen.getByText('bea7335dfaebc0ca6e65a057')).toBeInTheDocument();
  115. // Search bar
  116. expect(screen.getByPlaceholderText('Filter by Path')).toBeInTheDocument();
  117. // Path
  118. expect(
  119. await screen.findByText('https://example.com/AcceptOrganizationInvite.js')
  120. ).toBeInTheDocument();
  121. // Time
  122. expect(screen.getByText(/in 3 year/)).toBeInTheDocument();
  123. // File size
  124. expect(screen.getByText('8.1 KiB')).toBeInTheDocument();
  125. // Download button
  126. expect(screen.getByRole('button', {name: 'Download Artifact'})).toHaveAttribute(
  127. 'href',
  128. '/projects/org-slug/project-slug/releases/bea7335dfaebc0ca6e65a057/files/5678/?download=1'
  129. );
  130. });
  131. it('renders empty state', async function () {
  132. const {organization, routerProps, project, routerContext} = initializeOrg({
  133. router: {
  134. location: {
  135. query: {},
  136. },
  137. params: {},
  138. },
  139. });
  140. renderReleaseBundlesMockRequests({
  141. orgSlug: organization.slug,
  142. projectSlug: project.slug,
  143. empty: true,
  144. });
  145. render(
  146. <ProjectSourceMapsArtifacts
  147. {...routerProps}
  148. project={project}
  149. params={{
  150. orgId: organization.slug,
  151. projectId: project.slug,
  152. bundleId: 'bea7335dfaebc0ca6e65a057',
  153. }}
  154. />,
  155. {context: routerContext, organization}
  156. );
  157. expect(
  158. await screen.findByText('There are no artifacts in this archive.')
  159. ).toBeInTheDocument();
  160. });
  161. });
  162. describe('Artifact Bundles', function () {
  163. it('renders default state', async function () {
  164. const {organization, project, routerProps, routerContext} = initializeOrg({
  165. organization: Organization({
  166. access: ['org:superuser', 'project:releases'],
  167. }),
  168. router: {
  169. location: {
  170. pathname: `/settings/${initializeOrg().organization.slug}/projects/${
  171. initializeOrg().project.slug
  172. }/source-maps/artifact-bundles/7227e105-744e-4066-8c69-3e5e344723fc/`,
  173. query: {},
  174. },
  175. params: {},
  176. },
  177. });
  178. OrganizationStore.onUpdate(organization, {replace: true});
  179. ConfigStore.config = {
  180. ...ConfigStore.config,
  181. user: {...ConfigStore.config.user, isSuperuser: true},
  182. };
  183. const mockRequests = renderDebugIdBundlesMockRequests({
  184. orgSlug: organization.slug,
  185. projectSlug: project.slug,
  186. });
  187. render(
  188. <ProjectSourceMapsArtifacts
  189. {...routerProps}
  190. project={project}
  191. params={{
  192. orgId: organization.slug,
  193. projectId: project.slug,
  194. bundleId: '7227e105-744e-4066-8c69-3e5e344723fc',
  195. }}
  196. />,
  197. {context: routerContext, organization}
  198. );
  199. // Title
  200. expect(screen.getByRole('heading')).toHaveTextContent(
  201. '7227e105-744e-4066-8c69-3e5e344723fc'
  202. );
  203. // Details
  204. // Artifacts
  205. expect(await screen.findByText('Artifacts')).toBeInTheDocument();
  206. expect(await screen.findByText('22')).toBeInTheDocument();
  207. // Release information
  208. expect(await screen.findByText('Associated Releases')).toBeInTheDocument();
  209. expect(
  210. await screen.findByText(textWithMarkupMatcher('v2.0 (Dist: none)'))
  211. ).toBeInTheDocument();
  212. expect(
  213. await screen.findByText(
  214. textWithMarkupMatcher(
  215. 'frontend@2e318148eac9298ec04a662ae32b4b093b027f0a (Dist: android, iOS)'
  216. )
  217. )
  218. ).toBeInTheDocument();
  219. // Date Uploaded
  220. expect(await screen.findByText('Date Uploaded')).toBeInTheDocument();
  221. expect(await screen.findByText('Mar 8, 2023 9:53 AM UTC')).toBeInTheDocument();
  222. // Search bar
  223. expect(screen.getByPlaceholderText('Filter by Path or ID')).toBeInTheDocument();
  224. // Path
  225. expect(await screen.findByText('files/_/_/main.js')).toBeInTheDocument();
  226. // Debug Id
  227. expect(
  228. screen.getByText('69ac68eb-cc62-44c0-a5dc-b67f219a3696')
  229. ).toBeInTheDocument();
  230. // Type
  231. expect(screen.getByText('Minified')).toBeInTheDocument();
  232. // Download Button
  233. expect(screen.getByRole('button', {name: 'Download Artifact'})).toHaveAttribute(
  234. 'href',
  235. '/projects/org-slug/project-slug/artifact-bundles/7227e105-744e-4066-8c69-3e5e344723fc/files/ZmlsZXMvXy9fL21haW4uanM=/?download=1'
  236. );
  237. renderGlobalModal();
  238. // Delete item displays a confirmation modal
  239. await userEvent.click(screen.getByRole('button', {name: 'Delete Bundle'}));
  240. expect(
  241. await screen.findByText('Are you sure you want to delete this bundle?')
  242. ).toBeInTheDocument();
  243. // Close modal
  244. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  245. await waitFor(() => {
  246. expect(mockRequests.artifactBundlesDeletion).toHaveBeenLastCalledWith(
  247. '/projects/org-slug/project-slug/files/artifact-bundles/',
  248. expect.objectContaining({
  249. query: expect.objectContaining({
  250. bundleId: '7227e105-744e-4066-8c69-3e5e344723fc',
  251. }),
  252. })
  253. );
  254. });
  255. });
  256. it('renders empty state', async function () {
  257. const {organization, project, routerProps, routerContext} = initializeOrg({
  258. router: {
  259. location: {
  260. pathname: `/settings/${initializeOrg().organization.slug}/projects/${
  261. initializeOrg().project.slug
  262. }/source-maps/artifact-bundles/7227e105-744e-4066-8c69-3e5e344723fc/`,
  263. query: {},
  264. },
  265. params: {},
  266. },
  267. });
  268. renderDebugIdBundlesMockRequests({
  269. orgSlug: organization.slug,
  270. projectSlug: project.slug,
  271. empty: true,
  272. });
  273. render(
  274. <ProjectSourceMapsArtifacts
  275. {...routerProps}
  276. project={project}
  277. params={{
  278. orgId: organization.slug,
  279. projectId: project.slug,
  280. bundleId: '7227e105-744e-4066-8c69-3e5e344723fc',
  281. }}
  282. />,
  283. {context: routerContext, organization}
  284. );
  285. expect(
  286. await screen.findByText('There are no artifacts in this bundle.')
  287. ).toBeInTheDocument();
  288. expect(
  289. screen.getByText('No releases associated with this bundle')
  290. ).toBeInTheDocument();
  291. });
  292. });
  293. });