]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/check-params/video-studio.ts
Add video edition finished notification
[github/Chocobozzz/PeerTube.git] / server / tests / api / check-params / video-studio.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import 'mocha'
4 import { HttpStatusCode, VideoStudioTask } from '@shared/models'
5 import {
6 cleanupTests,
7 createSingleServer,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 VideoStudioCommand,
11 waitJobs
12 } from '@shared/server-commands'
13
14 describe('Test video studio API validator', function () {
15 let server: PeerTubeServer
16 let command: VideoStudioCommand
17 let userAccessToken: string
18 let videoUUID: string
19
20 // ---------------------------------------------------------------
21
22 before(async function () {
23 this.timeout(120_000)
24
25 server = await createSingleServer(1)
26
27 await setAccessTokensToServers([ server ])
28 userAccessToken = await server.users.generateUserAndToken('user1')
29
30 await server.config.enableMinimumTranscoding()
31
32 const { uuid } = await server.videos.quickUpload({ name: 'video' })
33 videoUUID = uuid
34
35 command = server.videoStudio
36
37 await waitJobs([ server ])
38 })
39
40 describe('Task creation', function () {
41
42 describe('Config settings', function () {
43
44 it('Should fail if studio is disabled', async function () {
45 await server.config.updateExistingSubConfig({
46 newConfig: {
47 videoStudio: {
48 enabled: false
49 }
50 }
51 })
52
53 await command.createEditionTasks({
54 videoId: videoUUID,
55 tasks: VideoStudioCommand.getComplexTask(),
56 expectedStatus: HttpStatusCode.BAD_REQUEST_400
57 })
58 })
59
60 it('Should fail to enable studio if transcoding is disabled', async function () {
61 await server.config.updateExistingSubConfig({
62 newConfig: {
63 videoStudio: {
64 enabled: true
65 },
66 transcoding: {
67 enabled: false
68 }
69 },
70 expectedStatus: HttpStatusCode.BAD_REQUEST_400
71 })
72 })
73
74 it('Should succeed to enable video studio', async function () {
75 await server.config.updateExistingSubConfig({
76 newConfig: {
77 videoStudio: {
78 enabled: true
79 },
80 transcoding: {
81 enabled: true
82 }
83 }
84 })
85 })
86 })
87
88 describe('Common tasks', function () {
89
90 it('Should fail without token', async function () {
91 await command.createEditionTasks({
92 token: null,
93 videoId: videoUUID,
94 tasks: VideoStudioCommand.getComplexTask(),
95 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
96 })
97 })
98
99 it('Should fail with another user token', async function () {
100 await command.createEditionTasks({
101 token: userAccessToken,
102 videoId: videoUUID,
103 tasks: VideoStudioCommand.getComplexTask(),
104 expectedStatus: HttpStatusCode.FORBIDDEN_403
105 })
106 })
107
108 it('Should fail with an invalid video', async function () {
109 await command.createEditionTasks({
110 videoId: 'tintin',
111 tasks: VideoStudioCommand.getComplexTask(),
112 expectedStatus: HttpStatusCode.BAD_REQUEST_400
113 })
114 })
115
116 it('Should fail with an unknown video', async function () {
117 await command.createEditionTasks({
118 videoId: 42,
119 tasks: VideoStudioCommand.getComplexTask(),
120 expectedStatus: HttpStatusCode.NOT_FOUND_404
121 })
122 })
123
124 it('Should fail with an already in transcoding state video', async function () {
125 this.timeout(30000)
126
127 const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
128 await waitJobs([ server ])
129
130 await server.jobs.pauseJobQueue()
131 await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })
132
133 await command.createEditionTasks({
134 videoId: uuid,
135 tasks: VideoStudioCommand.getComplexTask(),
136 expectedStatus: HttpStatusCode.CONFLICT_409
137 })
138
139 await server.jobs.resumeJobQueue()
140 })
141
142 it('Should fail with a bad complex task', async function () {
143 await command.createEditionTasks({
144 videoId: videoUUID,
145 tasks: [
146 {
147 name: 'cut',
148 options: {
149 start: 1,
150 end: 2
151 }
152 },
153 {
154 name: 'hadock',
155 options: {
156 start: 1,
157 end: 2
158 }
159 }
160 ] as any,
161 expectedStatus: HttpStatusCode.BAD_REQUEST_400
162 })
163 })
164
165 it('Should fail without task', async function () {
166 await command.createEditionTasks({
167 videoId: videoUUID,
168 tasks: [],
169 expectedStatus: HttpStatusCode.BAD_REQUEST_400
170 })
171 })
172
173 it('Should fail with too many tasks', async function () {
174 const tasks: VideoStudioTask[] = []
175
176 for (let i = 0; i < 110; i++) {
177 tasks.push({
178 name: 'cut',
179 options: {
180 start: 1
181 }
182 })
183 }
184
185 await command.createEditionTasks({
186 videoId: videoUUID,
187 tasks,
188 expectedStatus: HttpStatusCode.BAD_REQUEST_400
189 })
190 })
191
192 it('Should succeed with correct parameters', async function () {
193 await server.jobs.pauseJobQueue()
194
195 await command.createEditionTasks({
196 videoId: videoUUID,
197 tasks: VideoStudioCommand.getComplexTask(),
198 expectedStatus: HttpStatusCode.NO_CONTENT_204
199 })
200 })
201
202 it('Should fail with a video that is already waiting for edition', async function () {
203 this.timeout(120000)
204
205 await command.createEditionTasks({
206 videoId: videoUUID,
207 tasks: VideoStudioCommand.getComplexTask(),
208 expectedStatus: HttpStatusCode.CONFLICT_409
209 })
210
211 await server.jobs.resumeJobQueue()
212
213 await waitJobs([ server ])
214 })
215 })
216
217 describe('Cut task', function () {
218
219 async function cut (start: number, end: number, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
220 await command.createEditionTasks({
221 videoId: videoUUID,
222 tasks: [
223 {
224 name: 'cut',
225 options: {
226 start,
227 end
228 }
229 }
230 ],
231 expectedStatus
232 })
233 }
234
235 it('Should fail with bad start/end', async function () {
236 const invalid = [
237 'tintin',
238 -1,
239 undefined
240 ]
241
242 for (const value of invalid) {
243 await cut(value as any, undefined)
244 await cut(undefined, value as any)
245 }
246 })
247
248 it('Should fail with the same start/end', async function () {
249 await cut(2, 2)
250 })
251
252 it('Should fail with inconsistents start/end', async function () {
253 await cut(2, 1)
254 })
255
256 it('Should fail without start and end', async function () {
257 await cut(undefined, undefined)
258 })
259
260 it('Should succeed with the correct params', async function () {
261 this.timeout(120000)
262
263 await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
264
265 await waitJobs([ server ])
266 })
267 })
268
269 describe('Watermark task', function () {
270
271 async function addWatermark (file: string, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
272 await command.createEditionTasks({
273 videoId: videoUUID,
274 tasks: [
275 {
276 name: 'add-watermark',
277 options: {
278 file
279 }
280 }
281 ],
282 expectedStatus
283 })
284 }
285
286 it('Should fail without waterkmark', async function () {
287 await addWatermark(undefined)
288 })
289
290 it('Should fail with an invalid watermark', async function () {
291 await addWatermark('video_short.mp4')
292 })
293
294 it('Should succeed with the correct params', async function () {
295 this.timeout(120000)
296
297 await addWatermark('thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
298
299 await waitJobs([ server ])
300 })
301 })
302
303 describe('Intro/Outro task', function () {
304
305 async function addIntroOutro (type: 'add-intro' | 'add-outro', file: string, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
306 await command.createEditionTasks({
307 videoId: videoUUID,
308 tasks: [
309 {
310 name: type,
311 options: {
312 file
313 }
314 }
315 ],
316 expectedStatus
317 })
318 }
319
320 it('Should fail without file', async function () {
321 await addIntroOutro('add-intro', undefined)
322 await addIntroOutro('add-outro', undefined)
323 })
324
325 it('Should fail with an invalid file', async function () {
326 await addIntroOutro('add-intro', 'thumbnail.jpg')
327 await addIntroOutro('add-outro', 'thumbnail.jpg')
328 })
329
330 it('Should fail with a file that does not contain video stream', async function () {
331 await addIntroOutro('add-intro', 'sample.ogg')
332 await addIntroOutro('add-outro', 'sample.ogg')
333
334 })
335
336 it('Should succeed with the correct params', async function () {
337 this.timeout(120000)
338
339 await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
340 await waitJobs([ server ])
341
342 await addIntroOutro('add-outro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
343 await waitJobs([ server ])
344 })
345
346 it('Should check total quota when creating the task', async function () {
347 this.timeout(120000)
348
349 const user = await server.users.create({ username: 'user_quota_1' })
350 const token = await server.login.getAccessToken('user_quota_1')
351 const { uuid } = await server.videos.quickUpload({ token, name: 'video_quota_1', fixture: 'video_short.mp4' })
352
353 const addIntroOutroByUser = (type: 'add-intro' | 'add-outro', expectedStatus: HttpStatusCode) => {
354 return command.createEditionTasks({
355 token,
356 videoId: uuid,
357 tasks: [
358 {
359 name: type,
360 options: {
361 file: 'video_short.mp4'
362 }
363 }
364 ],
365 expectedStatus
366 })
367 }
368
369 await waitJobs([ server ])
370
371 const { videoQuotaUsed } = await server.users.getMyQuotaUsed({ token })
372 await server.users.update({ userId: user.id, videoQuota: Math.round(videoQuotaUsed * 2.5) })
373
374 // Still valid
375 await addIntroOutroByUser('add-intro', HttpStatusCode.NO_CONTENT_204)
376
377 await waitJobs([ server ])
378
379 // Too much quota
380 await addIntroOutroByUser('add-intro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
381 await addIntroOutroByUser('add-outro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
382 })
383 })
384 })
385
386 after(async function () {
387 await cleanupTests([ server ])
388 })
389 })