dashboard-transloadit.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. describe('Dashboard with Transloadit', () => {
  2. beforeEach(() => {
  3. cy.visit('/dashboard-transloadit')
  4. cy.get('.uppy-Dashboard-input:first').as('file-input')
  5. cy.intercept('/assemblies').as('createAssemblies')
  6. cy.intercept('/assemblies/*').as('assemblies')
  7. cy.intercept('/resumable/*').as('resumable')
  8. })
  9. it('should upload cat image successfully', () => {
  10. cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
  11. force: true,
  12. })
  13. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  14. cy.wait(['@assemblies', '@resumable']).then(() => {
  15. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  16. })
  17. })
  18. it('should close assembly polling when cancelled', () => {
  19. cy.intercept({
  20. method: 'GET',
  21. url: '/assemblies/*',
  22. }).as('assemblyPolling')
  23. cy.intercept(
  24. { method: 'DELETE', pathname: '/assemblies/*', times: 1 },
  25. { statusCode: 204, body: {} },
  26. ).as('delete')
  27. cy.window().then(({ uppy }) => {
  28. cy.get('@file-input').selectFile(
  29. [
  30. 'cypress/fixtures/images/cat.jpg',
  31. 'cypress/fixtures/images/traffic.jpg',
  32. 'cypress/fixtures/images/car.jpg',
  33. ],
  34. { force: true },
  35. )
  36. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  37. cy.wait(['@createAssemblies']).then(() => {
  38. // eslint-disable-next-line
  39. // @ts-ignore fix me
  40. expect(
  41. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).every(
  42. (a: any) => a.pollInterval,
  43. ),
  44. ).to.equal(true)
  45. uppy.cancelAll()
  46. cy.wait(['@delete']).then(() => {
  47. // eslint-disable-next-line
  48. // @ts-ignore fix me
  49. expect(
  50. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some(
  51. (a: any) => a.pollInterval,
  52. ),
  53. ).to.equal(false)
  54. })
  55. })
  56. })
  57. })
  58. // Too flaky at the moment. Arguably, this is not the right place
  59. // as this is doing white box testing (testing internal state).
  60. // But E2e is more about black box testing, you don’t care about the internals, only the result.
  61. // May make more sense to turn this into a unit test.
  62. it.skip('should emit one assembly-cancelled event when cancelled', () => {
  63. const spy = cy.spy()
  64. cy.window().then(({ uppy }) => {
  65. // eslint-disable-next-line
  66. // @ts-ignore fix me
  67. uppy.on('transloadit:assembly-cancelled', spy)
  68. cy.get('@file-input').selectFile(
  69. [
  70. 'cypress/fixtures/images/cat.jpg',
  71. 'cypress/fixtures/images/traffic.jpg',
  72. ],
  73. { force: true },
  74. )
  75. cy.intercept({
  76. method: 'GET',
  77. url: '/assemblies/*',
  78. }).as('assemblyPolling')
  79. cy.intercept(
  80. { method: 'PATCH', pathname: '/files/*', times: 1 },
  81. { statusCode: 204, body: {} },
  82. )
  83. cy.intercept(
  84. { method: 'DELETE', pathname: '/resumable/files/*', times: 2 },
  85. { statusCode: 204, body: {} },
  86. ).as('fileDeletion')
  87. cy.intercept({
  88. method: 'DELETE',
  89. pathname: '/assemblies/*',
  90. times: 1,
  91. }).as('assemblyDeletion')
  92. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  93. cy.wait('@assemblyPolling')
  94. cy.get('button[data-cy=cancel]').click()
  95. cy.wait('@assemblyDeletion')
  96. // Unfortunately, waiting on a network request somehow often results in a race condition.
  97. // We just want to know wether this is called or not, so waiting for 2 sec to be sure.
  98. // eslint-disable-next-line cypress/no-unnecessary-waiting
  99. cy.wait(2000)
  100. expect(spy).to.be.calledOnce
  101. })
  102. })
  103. it.skip('should close assembly polling when all files are removed', () => {
  104. const spy = cy.spy()
  105. cy.window().then(({ uppy }) => {
  106. // eslint-disable-next-line
  107. // @ts-ignore fix me
  108. uppy.on('transloadit:assembly-cancelled', spy)
  109. cy.get('@file-input').selectFile(
  110. [
  111. 'cypress/fixtures/images/cat.jpg',
  112. 'cypress/fixtures/images/traffic.jpg',
  113. ],
  114. { force: true },
  115. )
  116. cy.intercept({
  117. method: 'GET',
  118. url: '/assemblies/*',
  119. }).as('assemblyPolling')
  120. cy.intercept(
  121. { method: 'PATCH', pathname: '/files/*', times: 1 },
  122. { statusCode: 204, body: {} },
  123. )
  124. cy.intercept(
  125. { method: 'DELETE', pathname: '/resumable/files/*', times: 2 },
  126. { statusCode: 204, body: {} },
  127. ).as('fileDeletion')
  128. cy.intercept({
  129. method: 'DELETE',
  130. pathname: '/assemblies/*',
  131. times: 1,
  132. }).as('assemblyDeletion')
  133. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  134. cy.wait('@assemblyPolling')
  135. // eslint-disable-next-line
  136. // @ts-ignore fix me
  137. expect(
  138. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).every(
  139. (a: any) => a.pollInterval,
  140. ),
  141. ).to.equal(true)
  142. const { files } = uppy.getState()
  143. // eslint-disable-next-line
  144. // @ts-ignore fix me
  145. uppy.removeFiles(Object.keys(files))
  146. cy.wait('@assemblyDeletion').then(() => {
  147. // eslint-disable-next-line
  148. // @ts-ignore fix me
  149. expect(
  150. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some(
  151. (a: any) => a.pollInterval,
  152. ),
  153. ).to.equal(false)
  154. expect(spy).to.be.calledOnce
  155. })
  156. })
  157. })
  158. it('should not create assembly when all individual files have been cancelled', () => {
  159. cy.window().then(({ uppy }) => {
  160. cy.get('@file-input').selectFile(
  161. [
  162. 'cypress/fixtures/images/cat.jpg',
  163. 'cypress/fixtures/images/traffic.jpg',
  164. ],
  165. { force: true },
  166. )
  167. // eslint-disable-next-line
  168. // @ts-ignore fix me
  169. expect(
  170. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).length,
  171. ).to.equal(0)
  172. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  173. const { files } = uppy.getState()
  174. // eslint-disable-next-line
  175. // @ts-ignore fix me
  176. uppy.removeFiles(Object.keys(files))
  177. cy.wait('@createAssemblies').then(() => {
  178. // eslint-disable-next-line
  179. // @ts-ignore fix me
  180. expect(
  181. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some(
  182. (a: any) => a.pollInterval,
  183. ),
  184. ).to.equal(false)
  185. })
  186. })
  187. })
  188. // Not working, the upstream changes have not landed yet.
  189. it.skip('should create assembly if there is still one file to upload', () => {
  190. cy.get('@file-input').selectFile(
  191. [
  192. 'cypress/fixtures/images/cat.jpg',
  193. 'cypress/fixtures/images/traffic.jpg',
  194. ],
  195. { force: true },
  196. )
  197. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  198. cy.window().then(({ uppy }) => {
  199. // eslint-disable-next-line
  200. // @ts-ignore fix me
  201. expect(
  202. Object.values(uppy.getPlugin('Transloadit').activeAssemblies).length,
  203. ).to.equal(0)
  204. const { files } = uppy.getState()
  205. const [fileID] = Object.keys(files)
  206. uppy.removeFile(fileID)
  207. cy.wait('@createAssemblies').then(() => {
  208. cy.wait('@resumable')
  209. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  210. })
  211. })
  212. })
  213. // Not working, the upstream changes have not landed yet.
  214. it.skip('should complete upload if one gets cancelled mid-flight', () => {
  215. cy.get('@file-input').selectFile(
  216. [
  217. 'cypress/fixtures/images/cat.jpg',
  218. 'cypress/fixtures/images/traffic.jpg',
  219. ],
  220. { force: true },
  221. )
  222. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  223. cy.wait('@createAssemblies')
  224. cy.wait('@resumable')
  225. cy.window().then(({ uppy }) => {
  226. const { files } = uppy.getState()
  227. const [fileID] = Object.keys(files)
  228. uppy.removeFile(fileID)
  229. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  230. })
  231. })
  232. it('should not emit error if upload is cancelled right away', () => {
  233. cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
  234. force: true,
  235. })
  236. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  237. const handler = cy.spy()
  238. cy.window().then(({ uppy }) => {
  239. const { files } = uppy.getState()
  240. uppy.on('upload-error', handler)
  241. const [fileID] = Object.keys(files)
  242. uppy.removeFile(fileID)
  243. uppy.removeFile(fileID)
  244. cy.wait('@createAssemblies').then(() => expect(handler).not.to.be.called)
  245. })
  246. })
  247. it('should not re-use erroneous tus keys', () => {
  248. function createAssemblyStatus({
  249. message,
  250. assembly_id,
  251. bytes_expected,
  252. ...other
  253. }) {
  254. return {
  255. message,
  256. assembly_id,
  257. parent_id: null,
  258. account_id: 'deadbeef',
  259. account_name: 'foo',
  260. account_slug: 'foo',
  261. template_id: null,
  262. template_name: null,
  263. instance: 'test.transloadit.com',
  264. assembly_url: `http://api2.test.transloadit.com/assemblies/${assembly_id}`,
  265. assembly_ssl_url: `https://api2-test.transloadit.com/assemblies/${assembly_id}`,
  266. uppyserver_url: 'https://api2-test.transloadit.com/companion/',
  267. companion_url: 'https://api2-test.transloadit.com/companion/',
  268. websocket_url: 'about:blank',
  269. tus_url: 'https://api2-test.transloadit.com/resumable/files/',
  270. bytes_received: 0,
  271. bytes_expected,
  272. upload_duration: 0.162,
  273. client_agent: null,
  274. client_ip: null,
  275. client_referer: null,
  276. transloadit_client:
  277. '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-instagram:3.1.1,uppy-onedrive:3.1.1,uppy-zoom:2.1.1,uppy-url:3.3.1',
  278. start_date: new Date().toISOString(),
  279. upload_meta_data_extracted: false,
  280. warnings: [],
  281. is_infinite: false,
  282. has_dupe_jobs: false,
  283. execution_start: null,
  284. execution_duration: null,
  285. queue_duration: 0.009,
  286. jobs_queue_duration: 0,
  287. notify_start: null,
  288. notify_url: null,
  289. notify_response_code: null,
  290. notify_response_data: null,
  291. notify_duration: null,
  292. last_job_completed: null,
  293. fields: {},
  294. running_jobs: [],
  295. bytes_usage: 0,
  296. executing_jobs: [],
  297. started_jobs: [],
  298. parent_assembly_status: null,
  299. params: '{}',
  300. template: null,
  301. merged_params: '{}',
  302. expected_tus_uploads: 1,
  303. started_tus_uploads: 0,
  304. finished_tus_uploads: 0,
  305. tus_uploads: [],
  306. uploads: [],
  307. results: {},
  308. build_id: '4765326956',
  309. status_endpoint: `https://api2-test.transloadit.com/assemblies/${assembly_id}`,
  310. ...other,
  311. }
  312. }
  313. cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg'], {
  314. force: true,
  315. })
  316. // SETUP for FIRST ATTEMPT (error response from Transloadit backend)
  317. const assemblyIDAttempt1 = '500e56004f4347a288194edd7c7a0ae1'
  318. const tusIDAttempt1 = 'a9daed4af4981880faf29b0e9596a14d'
  319. cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
  320. statusCode: 200,
  321. body: JSON.stringify(
  322. createAssemblyStatus({
  323. ok: 'ASSEMBLY_UPLOADING',
  324. message: 'The Assembly is still in the process of being uploaded.',
  325. assembly_id: assemblyIDAttempt1,
  326. bytes_expected: 263871,
  327. }),
  328. ),
  329. }).as('createAssembly')
  330. cy.intercept('POST', 'https://api2-test.transloadit.com/resumable/files/', {
  331. statusCode: 201,
  332. headers: {
  333. Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
  334. },
  335. times: 1,
  336. }).as('tusCall')
  337. cy.intercept(
  338. 'PATCH',
  339. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
  340. {
  341. statusCode: 204,
  342. headers: {
  343. 'Upload-Length': '263871',
  344. 'Upload-Offset': '263871',
  345. },
  346. times: 1,
  347. },
  348. )
  349. cy.intercept(
  350. 'HEAD',
  351. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
  352. { statusCode: 204 },
  353. )
  354. cy.intercept(
  355. 'GET',
  356. `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt1}`,
  357. {
  358. statusCode: 200,
  359. body: JSON.stringify(
  360. createAssemblyStatus({
  361. error: 'INVALID_FILE_META_DATA',
  362. http_code: 400,
  363. message: 'Whatever error message from Transloadit backend',
  364. reason: 'Whatever reason',
  365. msg: 'Whatever error from Transloadit backend',
  366. assembly_id: '500e56004f4347a288194edd7c7a0ae1',
  367. bytes_expected: 263871,
  368. }),
  369. ),
  370. },
  371. ).as('failureReported')
  372. cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
  373. statusCode: 200,
  374. body: '{}',
  375. })
  376. // FIRST ATTEMPT
  377. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  378. cy.wait(['@createAssembly', '@tusCall', '@failureReported'])
  379. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
  380. // SETUP for SECOND ATTEMPT
  381. const assemblyIDAttempt2 = '6a3fa40e527d4d73989fce678232a5e1'
  382. const tusIDAttempt2 = 'b8ebed4af4981880faf29b0e9596b25e'
  383. cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
  384. statusCode: 200,
  385. body: JSON.stringify(
  386. createAssemblyStatus({
  387. ok: 'ASSEMBLY_UPLOADING',
  388. message: 'The Assembly is still in the process of being uploaded.',
  389. assembly_id: assemblyIDAttempt2,
  390. tus_url: 'https://api2-test.transloadit.com/resumable/files/attempt2',
  391. bytes_expected: 263871,
  392. }),
  393. ),
  394. }).as('createAssembly-attempt2')
  395. cy.intercept(
  396. 'POST',
  397. 'https://api2-test.transloadit.com/resumable/files/attempt2',
  398. {
  399. statusCode: 201,
  400. headers: {
  401. 'Upload-Length': '263871',
  402. 'Upload-Offset': '0',
  403. Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
  404. },
  405. times: 1,
  406. },
  407. ).as('tusCall-attempt2')
  408. cy.intercept(
  409. 'PATCH',
  410. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
  411. {
  412. statusCode: 204,
  413. headers: {
  414. 'Upload-Length': '263871',
  415. 'Upload-Offset': '263871',
  416. 'Tus-Resumable': '1.0.0',
  417. },
  418. times: 1,
  419. },
  420. )
  421. cy.intercept(
  422. 'HEAD',
  423. `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
  424. { statusCode: 204 },
  425. )
  426. cy.intercept(
  427. 'GET',
  428. `https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt2}`,
  429. {
  430. statusCode: 200,
  431. body: JSON.stringify(
  432. createAssemblyStatus({
  433. ok: 'ASSEMBLY_COMPLETED',
  434. http_code: 200,
  435. message: 'The Assembly was successfully completed.',
  436. assembly_id: assemblyIDAttempt2,
  437. bytes_received: 263871,
  438. bytes_expected: 263871,
  439. }),
  440. ),
  441. },
  442. ).as('assemblyCompleted-attempt2')
  443. // SECOND ATTEMPT
  444. cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
  445. cy.wait([
  446. '@createAssembly-attempt2',
  447. '@tusCall-attempt2',
  448. '@assemblyCompleted-attempt2',
  449. ])
  450. })
  451. it('should complete on retry', () => {
  452. cy.get('@file-input').selectFile(
  453. [
  454. 'cypress/fixtures/images/cat.jpg',
  455. 'cypress/fixtures/images/traffic.jpg',
  456. ],
  457. { force: true },
  458. )
  459. cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
  460. statusCode: 200,
  461. body: '{}',
  462. })
  463. cy.intercept(
  464. { method: 'POST', pathname: '/assemblies', times: 1 },
  465. { statusCode: 500, body: {} },
  466. ).as('failedAssemblyCreation')
  467. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  468. cy.wait('@failedAssemblyCreation')
  469. cy.get('button[data-cy=retry]').click()
  470. cy.wait(['@assemblies', '@resumable'])
  471. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  472. })
  473. it('should complete when resuming after pause', () => {
  474. cy.get('@file-input').selectFile(
  475. [
  476. 'cypress/fixtures/images/cat.jpg',
  477. 'cypress/fixtures/images/traffic.jpg',
  478. ],
  479. { force: true },
  480. )
  481. cy.get('.uppy-StatusBar-actionBtn--upload').click()
  482. cy.wait('@createAssemblies')
  483. cy.get('button[data-cy=togglePauseResume]').click()
  484. // eslint-disable-next-line cypress/no-unnecessary-waiting
  485. cy.wait(300) // Wait an arbitrary amount of time as a user would do.
  486. cy.get('button[data-cy=togglePauseResume]').click()
  487. cy.wait('@resumable')
  488. cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
  489. })
  490. })