]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/tests/api/videos/resumable-upload.ts
Add missing job types to admin panel
[github/Chocobozzz/PeerTube.git] / server / tests / api / videos / resumable-upload.ts
index a2d60eeecff30ec0273dd75d5fdea23bde4be1c1..b6d70ed44b2ef85d9b3936d914f705baf0782788 100644 (file)
@@ -4,15 +4,10 @@ import 'mocha'
 import * as chai from 'chai'
 import { pathExists, readdir, stat } from 'fs-extra'
 import { join } from 'path'
-import {
-  buildAbsoluteFixturePath,
-  cleanupTests,
-  createSingleServer,
-  PeerTubeServer,
-  setAccessTokensToServers,
-  setDefaultVideoChannel
-} from '@shared/extra-utils'
+import { buildAbsoluteFixturePath } from '@shared/core-utils'
+import { sha1 } from '@shared/extra-utils'
 import { HttpStatusCode, VideoPrivacy } from '@shared/models'
+import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands'
 
 const expect = chai.expect
 
@@ -22,6 +17,8 @@ describe('Test resumable upload', function () {
   const defaultFixture = 'video_short.mp4'
   let server: PeerTubeServer
   let rootId: number
+  let userAccessToken: string
+  let userChannelId: number
 
   async function buildSize (fixture: string, size?: number) {
     if (size !== undefined) return size
@@ -30,42 +27,54 @@ describe('Test resumable upload', function () {
     return (await stat(baseFixture)).size
   }
 
-  async function prepareUpload (sizeArg?: number) {
-    const size = await buildSize(defaultFixture, sizeArg)
+  async function prepareUpload (options: {
+    channelId?: number
+    token?: string
+    size?: number
+    originalName?: string
+    lastModified?: number
+  } = {}) {
+    const { token, originalName, lastModified } = options
+
+    const size = await buildSize(defaultFixture, options.size)
 
     const attributes = {
       name: 'video',
-      channelId: server.store.channel.id,
+      channelId: options.channelId ?? server.store.channel.id,
       privacy: VideoPrivacy.PUBLIC,
       fixture: defaultFixture
     }
 
     const mimetype = 'video/mp4'
 
-    const res = await server.videos.prepareResumableUpload({ attributes, size, mimetype })
+    const res = await server.videos.prepareResumableUpload({ token, attributes, size, mimetype, originalName, lastModified })
 
     return res.header['location'].split('?')[1]
   }
 
   async function sendChunks (options: {
+    token?: string
     pathUploadId: string
     size?: number
     expectedStatus?: HttpStatusCode
     contentLength?: number
     contentRange?: string
     contentRangeBuilder?: (start: number, chunk: any) => string
+    digestBuilder?: (chunk: any) => string
   }) {
-    const { pathUploadId, expectedStatus, contentLength, contentRangeBuilder } = options
+    const { token, pathUploadId, expectedStatus, contentLength, contentRangeBuilder, digestBuilder } = options
 
     const size = await buildSize(defaultFixture, options.size)
     const absoluteFilePath = buildAbsoluteFixturePath(defaultFixture)
 
     return server.videos.sendResumableChunks({
+      token,
       pathUploadId,
       videoFilePath: absoluteFilePath,
       size,
       contentLength,
       contentRangeBuilder,
+      digestBuilder,
       expectedStatus
     })
   }
@@ -99,23 +108,31 @@ describe('Test resumable upload', function () {
     this.timeout(30000)
 
     server = await createSingleServer(1)
-    await setAccessTokensToServers([server])
-    await setDefaultVideoChannel([server])
+    await setAccessTokensToServers([ server ])
+    await setDefaultVideoChannel([ server ])
 
     const body = await server.users.getMyInfo()
     rootId = body.id
 
+    {
+      userAccessToken = await server.users.generateUserAndToken('user1')
+      const { videoChannels } = await server.users.getMyInfo({ token: userAccessToken })
+      userChannelId = videoChannels[0].id
+    }
+
     await server.users.update({ userId: rootId, videoQuota: 10_000_000 })
   })
 
   describe('Directory cleaning', function () {
 
-    it('Should correctly delete files after an upload', async function () {
-      const uploadId = await prepareUpload()
-      await sendChunks({ pathUploadId: uploadId })
+    // FIXME: https://github.com/kukhariev/node-uploadx/pull/524/files#r852989382
+    // it('Should correctly delete files after an upload', async function () {
+    //   const uploadId = await prepareUpload()
+    //   await sendChunks({ pathUploadId: uploadId })
+    //   await server.videos.endResumableUpload({ pathUploadId: uploadId })
 
-      expect(await countResumableUploads()).to.equal(0)
-    })
+    //   expect(await countResumableUploads()).to.equal(0)
+    // })
 
     it('Should not delete files after an unfinished upload', async function () {
       await prepareUpload()
@@ -146,14 +163,14 @@ describe('Test resumable upload', function () {
     })
 
     it('Should not accept more chunks than expected', async function () {
-      const uploadId = await prepareUpload(100)
+      const uploadId = await prepareUpload({ size: 100 })
 
       await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409 })
       await checkFileSize(uploadId, 0)
     })
 
     it('Should not accept more chunks than expected with an invalid content length/content range', async function () {
-      const uploadId = await prepareUpload(1500)
+      const uploadId = await prepareUpload({ size: 1500 })
 
       // Content length check seems to have changed in v16
       if (process.version.startsWith('v16')) {
@@ -166,17 +183,105 @@ describe('Test resumable upload', function () {
     })
 
     it('Should not accept more chunks than expected with an invalid content length', async function () {
-      const uploadId = await prepareUpload(500)
+      const uploadId = await prepareUpload({ size: 500 })
 
       const size = 1000
 
-      const contentRangeBuilder = start => `bytes ${start}-${start + size - 1}/${size}`
-      await sendChunks({ pathUploadId: uploadId, expectedStatus: HttpStatusCode.CONFLICT_409, contentRangeBuilder, contentLength: size })
+      // Content length check seems to have changed in v16
+      const expectedStatus = process.version.startsWith('v16')
+        ? HttpStatusCode.CONFLICT_409
+        : HttpStatusCode.BAD_REQUEST_400
+
+      const contentRangeBuilder = (start: number) => `bytes ${start}-${start + size - 1}/${size}`
+      await sendChunks({ pathUploadId: uploadId, expectedStatus, contentRangeBuilder, contentLength: size })
       await checkFileSize(uploadId, 0)
     })
+
+    it('Should be able to accept 2 PUT requests', async function () {
+      const uploadId = await prepareUpload()
+
+      const result1 = await sendChunks({ pathUploadId: uploadId })
+      const result2 = await sendChunks({ pathUploadId: uploadId })
+
+      expect(result1.body.video.uuid).to.exist
+      expect(result1.body.video.uuid).to.equal(result2.body.video.uuid)
+
+      expect(result1.headers['x-resumable-upload-cached']).to.not.exist
+      expect(result2.headers['x-resumable-upload-cached']).to.equal('true')
+
+      await checkFileSize(uploadId, null)
+    })
+
+    it('Should not have the same upload id with 2 different users', async function () {
+      const originalName = 'toto.mp4'
+      const lastModified = new Date().getTime()
+
+      const uploadId1 = await prepareUpload({ originalName, lastModified, token: server.accessToken })
+      const uploadId2 = await prepareUpload({ originalName, lastModified, channelId: userChannelId, token: userAccessToken })
+
+      expect(uploadId1).to.not.equal(uploadId2)
+    })
+
+    it('Should have the same upload id with the same user', async function () {
+      const originalName = 'toto.mp4'
+      const lastModified = new Date().getTime()
+
+      const uploadId1 = await prepareUpload({ originalName, lastModified })
+      const uploadId2 = await prepareUpload({ originalName, lastModified })
+
+      expect(uploadId1).to.equal(uploadId2)
+    })
+
+    it('Should not cache a request with 2 different users', async function () {
+      const originalName = 'toto.mp4'
+      const lastModified = new Date().getTime()
+
+      const uploadId = await prepareUpload({ originalName, lastModified, token: server.accessToken })
+
+      await sendChunks({ pathUploadId: uploadId, token: server.accessToken })
+      await sendChunks({ pathUploadId: uploadId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+    })
+
+    it('Should not cache a request after a delete', async function () {
+      const originalName = 'toto.mp4'
+      const lastModified = new Date().getTime()
+      const uploadId1 = await prepareUpload({ originalName, lastModified, token: server.accessToken })
+
+      await sendChunks({ pathUploadId: uploadId1 })
+      await server.videos.endResumableUpload({ pathUploadId: uploadId1 })
+
+      const uploadId2 = await prepareUpload({ originalName, lastModified, token: server.accessToken })
+      expect(uploadId1).to.equal(uploadId2)
+
+      const result2 = await sendChunks({ pathUploadId: uploadId1 })
+      expect(result2.headers['x-resumable-upload-cached']).to.not.exist
+    })
+
+    it('Should refuse an invalid digest', async function () {
+      const uploadId = await prepareUpload({ token: server.accessToken })
+
+      await sendChunks({
+        pathUploadId: uploadId,
+        token: server.accessToken,
+        digestBuilder: () => 'sha=' + 'a'.repeat(40),
+        expectedStatus: 460
+      })
+    })
+
+    it('Should accept an appropriate digest', async function () {
+      const uploadId = await prepareUpload({ token: server.accessToken })
+
+      await sendChunks({
+        pathUploadId: uploadId,
+        token: server.accessToken,
+        digestBuilder: (chunk: Buffer) => {
+          return 'sha1=' + sha1(chunk, 'base64')
+        }
+      })
+    })
   })
 
   after(async function () {
-    await cleanupTests([server])
+    await cleanupTests([ server ])
   })
 })