diff options
Diffstat (limited to 'server/tests/api/check-params/video-studio.ts')
-rw-r--r-- | server/tests/api/check-params/video-studio.ts | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/server/tests/api/check-params/video-studio.ts b/server/tests/api/check-params/video-studio.ts new file mode 100644 index 000000000..9ca55d095 --- /dev/null +++ b/server/tests/api/check-params/video-studio.ts | |||
@@ -0,0 +1,389 @@ | |||
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 | }) | ||