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