2022-02-11 03:51:33 -06:00
|
|
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
|
|
|
|
2022-03-22 10:58:49 -05:00
|
|
|
import { HttpStatusCode, VideoStudioTask } from '@shared/models'
|
2022-02-11 03:51:33 -06:00
|
|
|
import {
|
|
|
|
cleanupTests,
|
|
|
|
createSingleServer,
|
|
|
|
PeerTubeServer,
|
|
|
|
setAccessTokensToServers,
|
2022-03-22 10:58:49 -05:00
|
|
|
VideoStudioCommand,
|
2022-02-11 03:51:33 -06:00
|
|
|
waitJobs
|
|
|
|
} from '@shared/server-commands'
|
|
|
|
|
2022-03-22 10:58:49 -05:00
|
|
|
describe('Test video studio API validator', function () {
|
2022-02-11 03:51:33 -06:00
|
|
|
let server: PeerTubeServer
|
2022-03-22 10:58:49 -05:00
|
|
|
let command: VideoStudioCommand
|
2022-02-11 03:51:33 -06:00
|
|
|
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
|
|
|
|
|
2022-03-22 10:58:49 -05:00
|
|
|
command = server.videoStudio
|
2022-02-11 03:51:33 -06:00
|
|
|
|
|
|
|
await waitJobs([ server ])
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('Task creation', function () {
|
|
|
|
|
|
|
|
describe('Config settings', function () {
|
|
|
|
|
2022-03-22 10:58:49 -05:00
|
|
|
it('Should fail if studio is disabled', async function () {
|
2022-02-11 03:51:33 -06:00
|
|
|
await server.config.updateExistingSubConfig({
|
|
|
|
newConfig: {
|
2022-03-22 10:58:49 -05:00
|
|
|
videoStudio: {
|
2022-02-11 03:51:33 -06:00
|
|
|
enabled: false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
await command.createEditionTasks({
|
|
|
|
videoId: videoUUID,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-03-22 10:58:49 -05:00
|
|
|
it('Should fail to enable studio if transcoding is disabled', async function () {
|
2022-02-11 03:51:33 -06:00
|
|
|
await server.config.updateExistingSubConfig({
|
|
|
|
newConfig: {
|
2022-03-22 10:58:49 -05:00
|
|
|
videoStudio: {
|
2022-02-11 03:51:33 -06:00
|
|
|
enabled: true
|
|
|
|
},
|
|
|
|
transcoding: {
|
|
|
|
enabled: false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2022-03-22 10:58:49 -05:00
|
|
|
it('Should succeed to enable video studio', async function () {
|
2022-02-11 03:51:33 -06:00
|
|
|
await server.config.updateExistingSubConfig({
|
|
|
|
newConfig: {
|
2022-03-22 10:58:49 -05:00
|
|
|
videoStudio: {
|
2022-02-11 03:51:33 -06:00
|
|
|
enabled: true
|
|
|
|
},
|
|
|
|
transcoding: {
|
|
|
|
enabled: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('Common tasks', function () {
|
|
|
|
|
|
|
|
it('Should fail without token', async function () {
|
|
|
|
await command.createEditionTasks({
|
|
|
|
token: null,
|
|
|
|
videoId: videoUUID,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Should fail with another user token', async function () {
|
|
|
|
await command.createEditionTasks({
|
|
|
|
token: userAccessToken,
|
|
|
|
videoId: videoUUID,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
expectedStatus: HttpStatusCode.FORBIDDEN_403
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Should fail with an invalid video', async function () {
|
|
|
|
await command.createEditionTasks({
|
|
|
|
videoId: 'tintin',
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Should fail with an unknown video', async function () {
|
|
|
|
await command.createEditionTasks({
|
|
|
|
videoId: 42,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
expectedStatus: HttpStatusCode.NOT_FOUND_404
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Should fail with an already in transcoding state video', async function () {
|
2023-05-11 09:33:30 -05:00
|
|
|
this.timeout(60000)
|
2022-03-17 03:09:06 -05:00
|
|
|
|
2022-02-11 03:51:33 -06:00
|
|
|
const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
|
2022-03-17 03:09:06 -05:00
|
|
|
await waitJobs([ server ])
|
2022-02-11 03:51:33 -06:00
|
|
|
|
2022-03-16 12:21:36 -05:00
|
|
|
await server.jobs.pauseJobQueue()
|
|
|
|
await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })
|
|
|
|
|
2022-02-11 03:51:33 -06:00
|
|
|
await command.createEditionTasks({
|
|
|
|
videoId: uuid,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
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 () {
|
2022-03-22 10:58:49 -05:00
|
|
|
const tasks: VideoStudioTask[] = []
|
2022-02-11 03:51:33 -06:00
|
|
|
|
|
|
|
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,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
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,
|
2022-03-22 10:58:49 -05:00
|
|
|
tasks: VideoStudioCommand.getComplexTask(),
|
2022-02-11 03:51:33 -06:00
|
|
|
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)
|
|
|
|
|
2023-06-06 04:14:13 -05:00
|
|
|
await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
|
2022-02-11 03:51:33 -06:00
|
|
|
|
|
|
|
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 () {
|
2023-06-06 04:14:13 -05:00
|
|
|
await addIntroOutro('add-intro', 'custom-thumbnail.jpg')
|
|
|
|
await addIntroOutro('add-outro', 'custom-thumbnail.jpg')
|
2022-02-11 03:51:33 -06:00
|
|
|
})
|
|
|
|
|
|
|
|
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 ])
|
|
|
|
})
|
|
|
|
})
|