projectSourceMapsArtifacts.spec.tsx 9.6 KB

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