aboutsummaryrefslogblamecommitdiffhomepage
path: root/server/tests/api/check-params/video-editor.ts
blob: 1fd22c497fd4d5bec8490b2b5d9d81c4cdb40bbc (plain) (tree)



























































































































                                                                                              

                                                                                      


                                                                                     
































































































































































































































































                                                                                                                                     
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */

import 'mocha'
import { HttpStatusCode, VideoEditorTask } from '@shared/models'
import {
  cleanupTests,
  createSingleServer,
  PeerTubeServer,
  setAccessTokensToServers,
  VideoEditorCommand,
  waitJobs
} from '@shared/server-commands'

describe('Test video editor API validator', function () {
  let server: PeerTubeServer
  let command: VideoEditorCommand
  let userAccessToken: string
  let videoUUID: string

  // ---------------------------------------------------------------

  before(async function () {
    this.timeout(120_000)

    server = await createSingleServer(1)

    await setAccessTokensToServers([ server ])
    userAccessToken = await server.users.generateUserAndToken('user1')

    await server.config.enableMinimumTranscoding()

    const { uuid } = await server.videos.quickUpload({ name: 'video' })
    videoUUID = uuid

    command = server.videoEditor

    await waitJobs([ server ])
  })

  describe('Task creation', function () {

    describe('Config settings', function () {

      it('Should fail if editor is disabled', async function () {
        await server.config.updateExistingSubConfig({
          newConfig: {
            videoEditor: {
              enabled: false
            }
          }
        })

        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })

      it('Should fail to enable editor if transcoding is disabled', async function () {
        await server.config.updateExistingSubConfig({
          newConfig: {
            videoEditor: {
              enabled: true
            },
            transcoding: {
              enabled: false
            }
          },
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })

      it('Should succeed to enable video editor', async function () {
        await server.config.updateExistingSubConfig({
          newConfig: {
            videoEditor: {
              enabled: true
            },
            transcoding: {
              enabled: true
            }
          }
        })
      })
    })

    describe('Common tasks', function () {

      it('Should fail without token', async function () {
        await command.createEditionTasks({
          token: null,
          videoId: videoUUID,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.UNAUTHORIZED_401
        })
      })

      it('Should fail with another user token', async function () {
        await command.createEditionTasks({
          token: userAccessToken,
          videoId: videoUUID,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.FORBIDDEN_403
        })
      })

      it('Should fail with an invalid video', async function () {
        await command.createEditionTasks({
          videoId: 'tintin',
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })

      it('Should fail with an unknown video', async function () {
        await command.createEditionTasks({
          videoId: 42,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.NOT_FOUND_404
        })
      })

      it('Should fail with an already in transcoding state video', async function () {
        const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })

        await server.jobs.pauseJobQueue()
        await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })

        await command.createEditionTasks({
          videoId: uuid,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.CONFLICT_409
        })

        await server.jobs.resumeJobQueue()
      })

      it('Should fail with a bad complex task', async function () {
        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: [
            {
              name: 'cut',
              options: {
                start: 1,
                end: 2
              }
            },
            {
              name: 'hadock',
              options: {
                start: 1,
                end: 2
              }
            }
          ] as any,
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })

      it('Should fail without task', async function () {
        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: [],
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })

      it('Should fail with too many tasks', async function () {
        const tasks: VideoEditorTask[] = []

        for (let i = 0; i < 110; i++) {
          tasks.push({
            name: 'cut',
            options: {
              start: 1
            }
          })
        }

        await command.createEditionTasks({
          videoId: videoUUID,
          tasks,
          expectedStatus: HttpStatusCode.BAD_REQUEST_400
        })
      })

      it('Should succeed with correct parameters', async function () {
        await server.jobs.pauseJobQueue()

        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.NO_CONTENT_204
        })
      })

      it('Should fail with a video that is already waiting for edition', async function () {
        this.timeout(120000)

        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: VideoEditorCommand.getComplexTask(),
          expectedStatus: HttpStatusCode.CONFLICT_409
        })

        await server.jobs.resumeJobQueue()

        await waitJobs([ server ])
      })
    })

    describe('Cut task', function () {

      async function cut (start: number, end: number, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: [
            {
              name: 'cut',
              options: {
                start,
                end
              }
            }
          ],
          expectedStatus
        })
      }

      it('Should fail with bad start/end', async function () {
        const invalid = [
          'tintin',
          -1,
          undefined
        ]

        for (const value of invalid) {
          await cut(value as any, undefined)
          await cut(undefined, value as any)
        }
      })

      it('Should fail with the same start/end', async function () {
        await cut(2, 2)
      })

      it('Should fail with inconsistents start/end', async function () {
        await cut(2, 1)
      })

      it('Should fail without start and end', async function () {
        await cut(undefined, undefined)
      })

      it('Should succeed with the correct params', async function () {
        this.timeout(120000)

        await cut(0, 2, HttpStatusCode.NO_CONTENT_204)

        await waitJobs([ server ])
      })
    })

    describe('Watermark task', function () {

      async function addWatermark (file: string, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: [
            {
              name: 'add-watermark',
              options: {
                file
              }
            }
          ],
          expectedStatus
        })
      }

      it('Should fail without waterkmark', async function () {
        await addWatermark(undefined)
      })

      it('Should fail with an invalid watermark', async function () {
        await addWatermark('video_short.mp4')
      })

      it('Should succeed with the correct params', async function () {
        this.timeout(120000)

        await addWatermark('thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)

        await waitJobs([ server ])
      })
    })

    describe('Intro/Outro task', function () {

      async function addIntroOutro (type: 'add-intro' | 'add-outro', file: string, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
        await command.createEditionTasks({
          videoId: videoUUID,
          tasks: [
            {
              name: type,
              options: {
                file
              }
            }
          ],
          expectedStatus
        })
      }

      it('Should fail without file', async function () {
        await addIntroOutro('add-intro', undefined)
        await addIntroOutro('add-outro', undefined)
      })

      it('Should fail with an invalid file', async function () {
        await addIntroOutro('add-intro', 'thumbnail.jpg')
        await addIntroOutro('add-outro', 'thumbnail.jpg')
      })

      it('Should fail with a file that does not contain video stream', async function () {
        await addIntroOutro('add-intro', 'sample.ogg')
        await addIntroOutro('add-outro', 'sample.ogg')

      })

      it('Should succeed with the correct params', async function () {
        this.timeout(120000)

        await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
        await waitJobs([ server ])

        await addIntroOutro('add-outro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
        await waitJobs([ server ])
      })

      it('Should check total quota when creating the task', async function () {
        this.timeout(120000)

        const user = await server.users.create({ username: 'user_quota_1' })
        const token = await server.login.getAccessToken('user_quota_1')
        const { uuid } = await server.videos.quickUpload({ token, name: 'video_quota_1', fixture: 'video_short.mp4' })

        const addIntroOutroByUser = (type: 'add-intro' | 'add-outro', expectedStatus: HttpStatusCode) => {
          return command.createEditionTasks({
            token,
            videoId: uuid,
            tasks: [
              {
                name: type,
                options: {
                  file: 'video_short.mp4'
                }
              }
            ],
            expectedStatus
          })
        }

        await waitJobs([ server ])

        const { videoQuotaUsed } = await server.users.getMyQuotaUsed({ token })
        await server.users.update({ userId: user.id, videoQuota: Math.round(videoQuotaUsed * 2.5) })

        // Still valid
        await addIntroOutroByUser('add-intro', HttpStatusCode.NO_CONTENT_204)

        await waitJobs([ server ])

        // Too much quota
        await addIntroOutroByUser('add-intro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
        await addIntroOutroByUser('add-outro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
      })
    })
  })

  after(async function () {
    await cleanupTests([ server ])
  })
})