projectSourceMapsArtifacts.spec.tsx 9.2 KB

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