/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, HttpStatusCodeType, VideoStudioTask } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
PeerTubeServer,
setAccessTokensToServers,
VideoStudioCommand,
waitJobs
} from '@peertube/peertube-server-commands'
describe('Test video studio API validator', function () {
let server: PeerTubeServer
let command: VideoStudioCommand
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.videoStudio
await waitJobs([ server ])
})
describe('Task creation', function () {
describe('Config settings', function () {
it('Should fail if studio is disabled', async function () {
await server.config.updateExistingSubConfig({
newConfig: {
videoStudio: {
enabled: false
}
}
})
await command.createEditionTasks({
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail to enable studio if transcoding is disabled', async function () {
await server.config.updateExistingSubConfig({
newConfig: {
videoStudio: {
enabled: true
},
transcoding: {
enabled: false
}
},
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should succeed to enable video studio', async function () {
await server.config.updateExistingSubConfig({
newConfig: {
videoStudio: {
enabled: true
},
transcoding: {
enabled: true
}
}
})
})
})
describe('Common tasks', function () {
it('Should fail without token', async function () {
await command.createEditionTasks({
token: null,
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
await command.createEditionTasks({
token: userAccessToken,
videoId: videoUUID,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should fail with an invalid video', async function () {
await command.createEditionTasks({
videoId: 'tintin',
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
})
it('Should fail with an unknown video', async function () {
await command.createEditionTasks({
videoId: 42,
tasks: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.NOT_FOUND_404
})
})
it('Should fail with an already in transcoding state video', async function () {
this.timeout(60000)
const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
await waitJobs([ server ])
await server.jobs.pauseJobQueue()
await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })
await command.createEditionTasks({
videoId: uuid,
tasks: VideoStudioCommand.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: VideoStudioTask[] = []
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: VideoStudioCommand.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: VideoStudioCommand.getComplexTask(),
expectedStatus: HttpStatusCode.CONFLICT_409
})
await server.jobs.resumeJobQueue()
await waitJobs([ server ])
})
})
describe('Cut task', function () {
async function cut (start: number, end: number, expectedStatus: HttpStatusCodeType = 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: HttpStatusCodeType = 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('custom-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: HttpStatusCodeType = 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', 'custom-thumbnail.jpg')
await addIntroOutro('add-outro', 'custom-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: HttpStatusCodeType) => {
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 ])
})
})