dashboard-transloadit.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. import Uppy from '@uppy/core'
  2. import Transloadit from '@uppy/transloadit'
  3. function getPlugin<M = any, B = any>(uppy: Uppy<M, B>) {
  4. return uppy.getPlugin<Transloadit<M, B>>('Transloadit')!
  5. }
  6. describe('Dashboard with Transloadit', () => {
  7. beforeEach(() => {
  8. cy.visit('/dashboard-transloadit')
  9. cy.get('.uppy-Dashboard-input:first').as('file-input')
  10. })
  11. it('should upload all files as a single assembly with UppyFile metadata in Upload-Metadata', () => {
  12. cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssembly')
  13. cy.get('@file-input').selectFile(
  14. [
  15. 'cypress/fixtures/images/cat.jpg',
  16. 'cypress/fixtures/images/traffic.jpg',
  17. ],
  18. { force: true },
  19. )
  20. cy.window().then(({ uppy }) => {
  21. // Set metadata on all files
  22. uppy.setMeta({ sharedMetaProperty: 'bar' })
  23. const [file1, file2] = uppy.getFiles()
  24. // Set unique metdata per file as before that's how we determined to create multiple assemblies
  25. uppy.setFileMeta(file1.id, { one: 'one' })
  26. uppy.setFileMeta(file2.id, { two: 'two' })
  27. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  28. cy.intercept('POST', '/resumable/*', (req) => {
  29. expect(req.headers['upload-metadata']).to.include('sharedMetaProperty')
  30. req.continue()
  31. })
  32. cy.wait(['@createAssembly']).then(() => {
  33. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  34. // should only create one assembly
  35. cy.get('@createAssembly.all').should('have.length', 1)
  36. })
  37. })
  38. })
  39. it('should close assembly when cancelled', () => {
  40. cy.intercept({ path: '/resumable/*', method: 'POST' }).as('tusCreate')
  41. cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies')
  42. cy.intercept({ path: '/assemblies/*', method: 'DELETE' }).as('delete')
  43. cy.intercept({ path: '/resumable/files/*', method: 'DELETE' }).as(
  44. 'tusDelete',
  45. )
  46. cy.window().then(({ uppy }) => {
  47. cy.get('@file-input').selectFile(
  48. [
  49. 'cypress/fixtures/images/cat.jpg',
  50. 'cypress/fixtures/images/traffic.jpg',
  51. 'cypress/fixtures/images/car.jpg',
  52. ],
  53. { force: true },
  54. )
  55. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  56. cy.wait(['@createAssemblies', '@tusCreate']).then(() => {
  57. const { assembly } = getPlugin(uppy)
  58. expect(assembly.closed).to.be.false
  59. uppy.cancelAll()
  60. cy.wait(['@delete', '@tusDelete']).then(() => {
  61. expect(assembly.closed).to.be.true
  62. })
  63. })
  64. })
  65. })
  66. it('should not emit error if upload is cancelled right away', () => {
  67. cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies')
  68. cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
  69. force: true,
  70. })
  71. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  72. const handler = cy.spy()
  73. cy.window().then(({ uppy }) => {
  74. const { files } = uppy.getState()
  75. uppy.on('upload-error', handler)
  76. const [fileID] = Object.keys(files)
  77. uppy.removeFile(fileID)
  78. uppy.removeFile(fileID)
  79. cy.wait('@createAssemblies').then(() => expect(handler).not.to.be.called)
  80. })
  81. })
  82. it('should not re-use erroneous tus keys', () => {
  83. function createAssemblyStatus({
  84. message,
  85. assembly_id,
  86. bytes_expected,
  87. ...other
  88. }) {
  89. return {
  90. message,
  91. assembly_id,
  92. parent_id: null,
  93. account_id: 'deadbeef',
  94. account_name: 'foo',
  95. account_slug: 'foo',
  96. template_id: null,
  97. template_name: null,
  98. instance: 'test.transloadit.com',
  99. assembly_url: `http://api2.test.transloadit.com/assemblies/${assembly_id}`,
  100. assembly_ssl_url: `https://api2-test.transloadit.com/assemblies/${assembly_id}`,
  101. uppyserver_url: 'https://api2-test.transloadit.com/companion/',
  102. companion_url: 'https://api2-test.transloadit.com/companion/',
  103. websocket_url: 'about:blank',
  104. tus_url: 'https://api2-test.transloadit.com/resumable/files/',
  105. bytes_received: 0,
  106. bytes_expected,
  107. upload_duration: 0.162,
  108. client_agent: null,
  109. client_ip: null,
  110. client_referer: null,
  111. transloadit_client:
  112. 'uppy-core:3.2.0,uppy-transloadit:3.1.3,uppy-tus:3.1.0,uppy-dropbox:3.1.1,uppy-box:2.1.1,uppy-facebook:3.1.1,uppy-google-drive:3.1.1,uppy-google-photos:0.0.1,uppy-instagram:3.1.1,uppy-onedrive:3.1.1,uppy-zoom:2.1.1,uppy-url:3.3.1',
  113. start_date: new Date().toISOString(),
  114. upload_meta_data_extracted: false,
  115. warnings: [],
  116. is_infinite: false,
  117. has_dupe_jobs: false,
  118. execution_start: null,
  119. execution_duration: null,
  120. queue_duration: 0.009,
  121. jobs_queue_duration: 0,
  122. notify_start: null,
  123. notify_url: null,
  124. notify_response_code: null,
  125. notify_response_data: null,
  126. notify_duration: null,
  127. last_job_completed: null,
  128. fields: {},
  129. running_jobs: [],
  130. bytes_usage: 0,
  131. executing_jobs: [],
  132. started_jobs: [],
  133. parent_assembly_status: null,
  134. params: '{}',
  135. template: null,
  136. merged_params: '{}',
  137. expected_tus_uploads: 1,
  138. started_tus_uploads: 0,
  139. finished_tus_uploads: 0,
  140. tus_uploads: [],
  141. uploads: [],
  142. results: {},
  143. build_id: '4765326956',
  144. status_endpoint: `https://api2-test.transloadit.com/assemblies/${assembly_id}`,
  145. ...other,
  146. }
  147. }
  148. cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg'], {
  149. force: true,
  150. })
  151. // SETUP for FIRST ATTEMPT (error response from Transloadit backend)
  152. const assemblyIDAttempt1 = '500e56004f4347a288194edd7c7a0ae1'
  153. const tusIDAttempt1 = 'a9daed4af4981880faf29b0e9596a14d'
  154. cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
  155. statusCode: 200,
  156. body: JSON.stringify(
  157. createAssemblyStatus({
  158. ok: 'ASSEMBLY_UPLOADING',
  159. message: 'The Assembly is still in the process of being uploaded.',
  160. assembly_id: assemblyIDAttempt1,
  161. bytes_expected: 263871,
  162. }),
  163. ),
  164. }).as('createAssembly')
  165. cy.intercept('POST', 'https://api2-test.transloadit.com/resumable/files/', {
  166. statusCode: 201,
  167. headers: {
  168. Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
  169. },
  170. times: 1,
  171. }).as('tusCall')
  172. cy.intercept(
  173. 'PATCH',
  174. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
  175. {
  176. statusCode: 204,
  177. headers: {
  178. 'Upload-Length': '263871',
  179. 'Upload-Offset': '263871',
  180. },
  181. times: 1,
  182. },
  183. )
  184. cy.intercept(
  185. 'HEAD',
  186. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
  187. { statusCode: 204 },
  188. )
  189. cy.intercept(
  190. 'GET',
  191. `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt1}`,
  192. {
  193. statusCode: 200,
  194. body: JSON.stringify(
  195. createAssemblyStatus({
  196. error: 'INVALID_FILE_META_DATA',
  197. http_code: 400,
  198. message: 'Whatever error message from Transloadit backend',
  199. reason: 'Whatever reason',
  200. msg: 'Whatever error from Transloadit backend',
  201. assembly_id: '500e56004f4347a288194edd7c7a0ae1',
  202. bytes_expected: 263871,
  203. }),
  204. ),
  205. },
  206. ).as('failureReported')
  207. cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
  208. statusCode: 200,
  209. body: '{}',
  210. })
  211. // FIRST ATTEMPT
  212. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  213. cy.wait(['@createAssembly', '@tusCall', '@failureReported'])
  214. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
  215. // SETUP for SECOND ATTEMPT
  216. const assemblyIDAttempt2 = '6a3fa40e527d4d73989fce678232a5e1'
  217. const tusIDAttempt2 = 'b8ebed4af4981880faf29b0e9596b25e'
  218. cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
  219. statusCode: 200,
  220. body: JSON.stringify(
  221. createAssemblyStatus({
  222. ok: 'ASSEMBLY_UPLOADING',
  223. message: 'The Assembly is still in the process of being uploaded.',
  224. assembly_id: assemblyIDAttempt2,
  225. tus_url: 'https://api2-test.transloadit.com/resumable/files/attempt2',
  226. bytes_expected: 263871,
  227. }),
  228. ),
  229. }).as('createAssembly-attempt2')
  230. cy.intercept(
  231. 'POST',
  232. 'https://api2-test.transloadit.com/resumable/files/attempt2',
  233. {
  234. statusCode: 201,
  235. headers: {
  236. 'Upload-Length': '263871',
  237. 'Upload-Offset': '0',
  238. Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
  239. },
  240. times: 1,
  241. },
  242. ).as('tusCall-attempt2')
  243. cy.intercept(
  244. 'PATCH',
  245. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
  246. {
  247. statusCode: 204,
  248. headers: {
  249. 'Upload-Length': '263871',
  250. 'Upload-Offset': '263871',
  251. 'Tus-Resumable': '1.0.0',
  252. },
  253. times: 1,
  254. },
  255. )
  256. cy.intercept(
  257. 'HEAD',
  258. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
  259. { statusCode: 204 },
  260. )
  261. cy.intercept(
  262. 'GET',
  263. `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt2}`,
  264. {
  265. statusCode: 200,
  266. body: JSON.stringify(
  267. createAssemblyStatus({
  268. ok: 'ASSEMBLY_COMPLETED',
  269. http_code: 200,
  270. message: 'The Assembly was successfully completed.',
  271. assembly_id: assemblyIDAttempt2,
  272. bytes_received: 263871,
  273. bytes_expected: 263871,
  274. }),
  275. ),
  276. },
  277. ).as('assemblyCompleted-attempt2')
  278. // SECOND ATTEMPT
  279. cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
  280. cy.wait([
  281. '@createAssembly-attempt2',
  282. '@tusCall-attempt2',
  283. '@assemblyCompleted-attempt2',
  284. ])
  285. })
  286. it('should complete on retry', () => {
  287. cy.intercept('/assemblies/*').as('assemblies')
  288. cy.intercept('/resumable/*').as('resumable')
  289. cy.get('@file-input').selectFile(
  290. [
  291. 'cypress/fixtures/images/cat.jpg',
  292. 'cypress/fixtures/images/traffic.jpg',
  293. ],
  294. { force: true },
  295. )
  296. cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
  297. statusCode: 200,
  298. body: '{}',
  299. })
  300. cy.intercept(
  301. { method: 'POST', pathname: '/assemblies', times: 1 },
  302. { statusCode: 500, body: {} },
  303. ).as('failedAssemblyCreation')
  304. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  305. cy.wait('@failedAssemblyCreation')
  306. cy.get('button[data-cy=retry]').click()
  307. cy.wait(['@assemblies', '@resumable'])
  308. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  309. })
  310. it('should complete when resuming after pause', () => {
  311. cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies')
  312. cy.intercept({ path: '/resumable/files/', method: 'POST' }).as(
  313. 'firstUpload',
  314. )
  315. cy.intercept({ path: '/resumable/files/*', method: 'PATCH' }).as(
  316. 'secondUpload',
  317. )
  318. cy.get('@file-input').selectFile(
  319. [
  320. 'cypress/fixtures/images/cat.jpg',
  321. 'cypress/fixtures/images/traffic.jpg',
  322. ],
  323. { force: true },
  324. )
  325. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  326. cy.wait('@createAssemblies')
  327. // wait for the upload to start, then pause
  328. cy.wait('@firstUpload')
  329. cy.get('button[data-cy=togglePauseResume]').click()
  330. // eslint-disable-next-line cypress/no-unnecessary-waiting
  331. cy.wait(300) // Wait an arbitrary amount of time as a user would do.
  332. cy.get('button[data-cy=togglePauseResume]').click()
  333. cy.wait('@secondUpload')
  334. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  335. })
  336. })