]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/utils/videos/videos.ts
ffmpeg auto thread
[github/Chocobozzz/PeerTube.git] / server / tests / utils / videos / videos.ts
CommitLineData
a20399c9
C
1/* tslint:disable:no-unused-expression */
2
3import { expect } from 'chai'
f05a1c30 4import { existsSync, readFile } from 'fs'
fdbda9e3 5import * as parseTorrent from 'parse-torrent'
1d791a26 6import { extname, join } from 'path'
c5d31dba 7import * as request from 'supertest'
ac81d1a0
C
8import {
9 buildAbsoluteFixturePath,
d525fc39 10 getMyUserInformation, immutableAssign,
ac81d1a0
C
11 makeGetRequest,
12 makePutBodyRequest,
13 makeUploadRequest,
14 root,
15 ServerInfo,
16 testImage
17} from '../'
0f320037 18import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos'
5e1c08eb 19import { readdirPromise } from '../../../helpers/core-utils'
a20399c9
C
20import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
21import { dateIsValid, webtorrentAdd } from '../index'
0e1dc3e7
C
22
23type VideoAttributes = {
24 name?: string
25 category?: number
26 licence?: number
9d3ef9fe 27 language?: string
0e1dc3e7 28 nsfw?: boolean
47564bbe 29 commentsEnabled?: boolean
2186386c 30 waitTranscoding?: boolean
0e1dc3e7
C
31 description?: string
32 tags?: string[]
5f04dd2f 33 channelId?: number
11474c3c 34 privacy?: VideoPrivacy
0e1dc3e7 35 fixture?: string
ac81d1a0
C
36 thumbnailfile?: string
37 previewfile?: string
2baea0c7
C
38 scheduleUpdate?: {
39 updateAt: string
40 privacy?: VideoPrivacy
41 }
0e1dc3e7
C
42}
43
44function getVideoCategories (url: string) {
45 const path = '/api/v1/videos/categories'
46
eec63bbc
C
47 return makeGetRequest({
48 url,
59651eee
C
49 path,
50 statusCodeExpected: 200
eec63bbc 51 })
0e1dc3e7
C
52}
53
54function getVideoLicences (url: string) {
55 const path = '/api/v1/videos/licences'
56
eec63bbc
C
57 return makeGetRequest({
58 url,
59651eee
C
59 path,
60 statusCodeExpected: 200
eec63bbc 61 })
0e1dc3e7
C
62}
63
64function getVideoLanguages (url: string) {
65 const path = '/api/v1/videos/languages'
66
eec63bbc
C
67 return makeGetRequest({
68 url,
59651eee
C
69 path,
70 statusCodeExpected: 200
eec63bbc 71 })
0e1dc3e7
C
72}
73
11474c3c
C
74function getVideoPrivacies (url: string) {
75 const path = '/api/v1/videos/privacies'
76
eec63bbc
C
77 return makeGetRequest({
78 url,
59651eee
C
79 path,
80 statusCodeExpected: 200
eec63bbc 81 })
11474c3c
C
82}
83
11474c3c 84function getVideo (url: string, id: number | string, expectedStatus = 200) {
0e1dc3e7
C
85 const path = '/api/v1/videos/' + id
86
87 return request(url)
88 .get(path)
89 .set('Accept', 'application/json')
11474c3c
C
90 .expect(expectedStatus)
91}
92
490b595a 93function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
1f3e9fec
C
94 const path = '/api/v1/videos/' + id + '/views'
95
490b595a 96 const req = request(url)
1f3e9fec
C
97 .post(path)
98 .set('Accept', 'application/json')
490b595a
C
99
100 if (xForwardedFor) {
101 req.set('X-Forwarded-For', xForwardedFor)
102 }
103
104 return req.expect(expectedStatus)
1f3e9fec
C
105}
106
11474c3c
C
107function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) {
108 const path = '/api/v1/videos/' + id
109
110 return request(url)
111 .get(path)
112 .set('Authorization', 'Bearer ' + token)
113 .set('Accept', 'application/json')
114 .expect(expectedStatus)
0e1dc3e7
C
115}
116
9567011b
C
117function getVideoDescription (url: string, descriptionPath: string) {
118 return request(url)
119 .get(descriptionPath)
120 .set('Accept', 'application/json')
121 .expect(200)
122 .expect('Content-Type', /json/)
123}
124
0e1dc3e7
C
125function getVideosList (url: string) {
126 const path = '/api/v1/videos'
127
128 return request(url)
129 .get(path)
130 .query({ sort: 'name' })
131 .set('Accept', 'application/json')
132 .expect(200)
133 .expect('Content-Type', /json/)
134}
135
d525fc39 136function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
0883b324
C
137 const path = '/api/v1/videos'
138
139 return request(url)
140 .get(path)
141 .set('Authorization', 'Bearer ' + token)
d525fc39 142 .query(immutableAssign(query, { sort: 'name' }))
0883b324
C
143 .set('Accept', 'application/json')
144 .expect(200)
145 .expect('Content-Type', /json/)
146}
147
066e94c5
C
148function getLocalVideos (url: string) {
149 const path = '/api/v1/videos'
150
151 return request(url)
152 .get(path)
153 .query({ sort: 'name', filter: 'local' })
154 .set('Accept', 'application/json')
155 .expect(200)
156 .expect('Content-Type', /json/)
157}
158
11474c3c
C
159function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
160 const path = '/api/v1/users/me/videos'
161
162 const req = request(url)
163 .get(path)
164 .query({ start: start })
165 .query({ count: count })
166
167 if (sort) req.query({ sort })
168
169 return req.set('Accept', 'application/json')
170 .set('Authorization', 'Bearer ' + accessToken)
171 .expect(200)
172 .expect('Content-Type', /json/)
173}
174
d525fc39
C
175function getAccountVideos (
176 url: string,
177 accessToken: string,
178 accountName: string,
179 start: number,
180 count: number,
181 sort?: string,
182 query: { nsfw?: boolean } = {}
183) {
ad9e39fb 184 const path = '/api/v1/accounts/' + accountName + '/videos'
6b738c7a
C
185
186 return makeGetRequest({
187 url,
188 path,
d525fc39 189 query: immutableAssign(query, {
6b738c7a
C
190 start,
191 count,
192 sort
d525fc39 193 }),
6b738c7a
C
194 token: accessToken,
195 statusCodeExpected: 200
196 })
197}
198
199function getVideoChannelVideos (
200 url: string,
201 accessToken: string,
6b738c7a
C
202 videoChannelId: number | string,
203 start: number,
204 count: number,
d525fc39
C
205 sort?: string,
206 query: { nsfw?: boolean } = {}
6b738c7a 207) {
cc918ac3 208 const path = '/api/v1/video-channels/' + videoChannelId + '/videos'
6b738c7a
C
209
210 return makeGetRequest({
211 url,
212 path,
d525fc39 213 query: immutableAssign(query, {
6b738c7a
C
214 start,
215 count,
216 sort
d525fc39 217 }),
6b738c7a
C
218 token: accessToken,
219 statusCodeExpected: 200
220 })
221}
222
0e1dc3e7
C
223function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
224 const path = '/api/v1/videos'
225
226 const req = request(url)
227 .get(path)
228 .query({ start: start })
229 .query({ count: count })
230
231 if (sort) req.query({ sort })
232
233 return req.set('Accept', 'application/json')
234 .expect(200)
235 .expect('Content-Type', /json/)
236}
237
238function getVideosListSort (url: string, sort: string) {
239 const path = '/api/v1/videos'
240
241 return request(url)
242 .get(path)
243 .query({ sort: sort })
244 .set('Accept', 'application/json')
245 .expect(200)
246 .expect('Content-Type', /json/)
247}
248
d525fc39 249function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
0e1dc3e7
C
250 const path = '/api/v1/videos'
251
252 return request(url)
57c36b27 253 .get(path)
d525fc39 254 .query(query)
f3aaa9a9 255 .set('Accept', 'application/json')
d525fc39 256 .expect(200)
f3aaa9a9 257 .expect('Content-Type', /json/)
0e1dc3e7
C
258}
259
d525fc39 260function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
0883b324 261 const path = '/api/v1/videos'
0e1dc3e7
C
262
263 return request(url)
d525fc39 264 .delete(path + '/' + id)
0e1dc3e7 265 .set('Accept', 'application/json')
d525fc39
C
266 .set('Authorization', 'Bearer ' + token)
267 .expect(expectedStatus)
0e1dc3e7
C
268}
269
f05a1c30
C
270async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) {
271 const testDirectory = 'test' + serverNumber
272
f4001cf4 273 for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]) {
f05a1c30
C
274 const directoryPath = join(root(), testDirectory, directory)
275
276 const directoryExists = existsSync(directoryPath)
277 expect(directoryExists).to.be.true
278
279 const files = await readdirPromise(directoryPath)
280 for (const file of files) {
281 expect(file).to.not.contain(videoUUID)
282 }
283 }
284}
285
e11f68a3 286async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
e95561cd 287 const path = '/api/v1/videos/upload'
5f04dd2f
C
288 let defaultChannelId = '1'
289
290 try {
291 const res = await getMyUserInformation(url, accessToken)
292 defaultChannelId = res.body.videoChannels[0].id
293 } catch (e) { /* empty */ }
0e1dc3e7 294
ac81d1a0
C
295 // Override default attributes
296 const attributes = Object.assign({
0e1dc3e7
C
297 name: 'my super video',
298 category: 5,
299 licence: 4,
9d3ef9fe 300 language: 'zh',
5f04dd2f 301 channelId: defaultChannelId,
0e1dc3e7 302 nsfw: true,
2186386c 303 waitTranscoding: false,
0e1dc3e7 304 description: 'my super description',
2422c46b 305 support: 'my super support text',
0e1dc3e7 306 tags: [ 'tag' ],
11474c3c 307 privacy: VideoPrivacy.PUBLIC,
47564bbe 308 commentsEnabled: true,
0e1dc3e7 309 fixture: 'video_short.webm'
ac81d1a0 310 }, videoAttributesArg)
0e1dc3e7
C
311
312 const req = request(url)
313 .post(path)
314 .set('Accept', 'application/json')
315 .set('Authorization', 'Bearer ' + accessToken)
316 .field('name', attributes.name)
0e1dc3e7 317 .field('nsfw', JSON.stringify(attributes.nsfw))
47564bbe 318 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
2186386c 319 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
11474c3c 320 .field('privacy', attributes.privacy.toString())
5f04dd2f 321 .field('channelId', attributes.channelId)
0e1dc3e7 322
a87d467a
C
323 if (attributes.description !== undefined) {
324 req.field('description', attributes.description)
325 }
8df87ce7
C
326 if (attributes.language !== undefined) {
327 req.field('language', attributes.language.toString())
328 }
a7fea183
C
329 if (attributes.category !== undefined) {
330 req.field('category', attributes.category.toString())
331 }
332 if (attributes.licence !== undefined) {
333 req.field('licence', attributes.licence.toString())
334 }
8df87ce7 335
2422c46b
C
336 for (let i = 0; i < attributes.tags.length; i++) {
337 req.field('tags[' + i + ']', attributes.tags[i])
338 }
339
ac81d1a0
C
340 if (attributes.thumbnailfile !== undefined) {
341 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
342 }
343 if (attributes.previewfile !== undefined) {
344 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
0e1dc3e7
C
345 }
346
2baea0c7
C
347 if (attributes.scheduleUpdate) {
348 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
349
350 if (attributes.scheduleUpdate.privacy) {
351 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
352 }
353 }
354
ac81d1a0 355 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
0e1dc3e7
C
356 .expect(specialStatus)
357}
358
ac81d1a0 359function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
0e1dc3e7
C
360 const path = '/api/v1/videos/' + id
361 const body = {}
362
363 if (attributes.name) body['name'] = attributes.name
364 if (attributes.category) body['category'] = attributes.category
365 if (attributes.licence) body['licence'] = attributes.licence
366 if (attributes.language) body['language'] = attributes.language
47564bbe
C
367 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
368 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
0e1dc3e7
C
369 if (attributes.description) body['description'] = attributes.description
370 if (attributes.tags) body['tags'] = attributes.tags
11474c3c 371 if (attributes.privacy) body['privacy'] = attributes.privacy
0f320037 372 if (attributes.channelId) body['channelId'] = attributes.channelId
2baea0c7 373 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
0e1dc3e7 374
ac81d1a0
C
375 // Upload request
376 if (attributes.thumbnailfile || attributes.previewfile) {
377 const attaches: any = {}
378 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
379 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
380
381 return makeUploadRequest({
382 url,
383 method: 'PUT',
384 path,
385 token: accessToken,
386 fields: body,
387 attaches,
388 statusCodeExpected
389 })
390 }
391
392 return makePutBodyRequest({
393 url,
394 path,
395 fields: body,
396 token: accessToken,
397 statusCodeExpected
398 })
0e1dc3e7
C
399}
400
401function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
402 const path = '/api/v1/videos/' + id + '/rate'
403
404 return request(url)
405 .put(path)
406 .set('Accept', 'application/json')
407 .set('Authorization', 'Bearer ' + accessToken)
408 .send({ rating })
409 .expect(specialStatus)
410}
411
14d3270f 412function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
fdbda9e3 413 return new Promise<any>((res, rej) => {
14d3270f 414 const torrentName = videoUUID + '-' + resolution + '.torrent'
331128ed 415 const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
fdbda9e3
C
416 readFile(torrentPath, (err, data) => {
417 if (err) return rej(err)
418
419 return res(parseTorrent(data))
420 })
421 })
422}
423
a20399c9
C
424async function completeVideoCheck (
425 url: string,
426 video: any,
427 attributes: {
428 name: string
429 category: number
430 licence: number
9d3ef9fe 431 language: string
a20399c9 432 nsfw: boolean
47564bbe 433 commentsEnabled: boolean
a20399c9 434 description: string
53a61317 435 publishedAt?: string
2422c46b 436 support: string
b64c950a
C
437 account: {
438 name: string
439 host: string
440 }
a20399c9
C
441 isLocal: boolean,
442 tags: string[],
443 privacy: number,
b1f5b93e
C
444 likes?: number,
445 dislikes?: number,
446 duration: number,
a20399c9
C
447 channel: {
448 name: string,
b1f5b93e 449 description
a20399c9
C
450 isLocal: boolean
451 }
452 fixture: string,
453 files: {
454 resolution: number
455 size: number
ac81d1a0
C
456 }[],
457 thumbnailfile?: string
458 previewfile?: string
a20399c9
C
459 }
460) {
b1f5b93e
C
461 if (!attributes.likes) attributes.likes = 0
462 if (!attributes.dislikes) attributes.dislikes = 0
463
a20399c9 464 expect(video.name).to.equal(attributes.name)
09700934 465 expect(video.category.id).to.equal(attributes.category)
9d3ef9fe 466 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
09700934 467 expect(video.licence.id).to.equal(attributes.licence)
9d3ef9fe 468 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
09700934 469 expect(video.language.id).to.equal(attributes.language)
9d3ef9fe 470 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
2243730c
C
471 expect(video.privacy.id).to.deep.equal(attributes.privacy)
472 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
a20399c9
C
473 expect(video.nsfw).to.equal(attributes.nsfw)
474 expect(video.description).to.equal(attributes.description)
03e12d7c
C
475 expect(video.account.id).to.be.a('number')
476 expect(video.account.uuid).to.be.a('string')
b64c950a
C
477 expect(video.account.host).to.equal(attributes.account.host)
478 expect(video.account.name).to.equal(attributes.account.name)
0f320037
C
479 expect(video.channel.displayName).to.equal(attributes.channel.name)
480 expect(video.channel.name).to.have.lengthOf(36)
b1f5b93e
C
481 expect(video.likes).to.equal(attributes.likes)
482 expect(video.dislikes).to.equal(attributes.dislikes)
a20399c9 483 expect(video.isLocal).to.equal(attributes.isLocal)
b1f5b93e 484 expect(video.duration).to.equal(attributes.duration)
a20399c9 485 expect(dateIsValid(video.createdAt)).to.be.true
c49db162 486 expect(dateIsValid(video.publishedAt)).to.be.true
a20399c9
C
487 expect(dateIsValid(video.updatedAt)).to.be.true
488
53a61317 489 if (attributes.publishedAt) {
53a61317
C
490 expect(video.publishedAt).to.equal(attributes.publishedAt)
491 }
492
66b16caf 493 const res = await getVideo(url, video.uuid)
0f320037 494 const videoDetails: VideoDetails = res.body
a20399c9
C
495
496 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
497 expect(videoDetails.tags).to.deep.equal(attributes.tags)
19a3b914
C
498 expect(videoDetails.account.name).to.equal(attributes.account.name)
499 expect(videoDetails.account.host).to.equal(attributes.account.host)
7bc29171
C
500 expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
501 expect(videoDetails.channel.name).to.have.lengthOf(36)
0f320037 502 expect(videoDetails.channel.host).to.equal(attributes.account.host)
a20399c9 503 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
0f320037
C
504 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
505 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
506 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
a20399c9
C
507
508 for (const attributeFile of attributes.files) {
5d00a3d7 509 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
a20399c9
C
510 expect(file).not.to.be.undefined
511
b1f5b93e
C
512 let extension = extname(attributes.fixture)
513 // Transcoding enabled on server 2, extension will always be .mp4
b64c950a 514 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
b1f5b93e 515
a20399c9
C
516 const magnetUri = file.magnetUri
517 expect(file.magnetUri).to.have.lengthOf.above(2)
5d00a3d7
C
518 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
519 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
09700934
C
520 expect(file.resolution.id).to.equal(attributeFile.resolution)
521 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
b1f5b93e
C
522
523 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
524 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
4176e227
RK
525 expect(file.size,
526 'File size for resolution ' + file.resolution.label + ' outside confidence interval.')
527 .to.be.above(minSize).and.below(maxSize)
a20399c9 528
ac81d1a0 529 {
7b0956ec 530 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
ac81d1a0
C
531 }
532
533 if (attributes.previewfile) {
7b0956ec 534 await testImage(url, attributes.previewfile, videoDetails.previewPath)
ac81d1a0 535 }
a20399c9
C
536
537 const torrent = await webtorrentAdd(magnetUri, true)
538 expect(torrent.files).to.be.an('array')
539 expect(torrent.files.length).to.equal(1)
540 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
541 }
542}
543
0e1dc3e7
C
544// ---------------------------------------------------------------------------
545
546export {
9567011b 547 getVideoDescription,
0e1dc3e7
C
548 getVideoCategories,
549 getVideoLicences,
11474c3c 550 getVideoPrivacies,
0e1dc3e7 551 getVideoLanguages,
11474c3c 552 getMyVideos,
6b738c7a
C
553 getAccountVideos,
554 getVideoChannelVideos,
0e1dc3e7 555 getVideo,
11474c3c 556 getVideoWithToken,
0e1dc3e7
C
557 getVideosList,
558 getVideosListPagination,
559 getVideosListSort,
560 removeVideo,
0883b324 561 getVideosListWithToken,
0e1dc3e7 562 uploadVideo,
d525fc39 563 getVideosWithFilters,
0e1dc3e7 564 updateVideo,
fdbda9e3 565 rateVideo,
1f3e9fec 566 viewVideo,
a20399c9 567 parseTorrentVideo,
066e94c5 568 getLocalVideos,
f05a1c30
C
569 completeVideoCheck,
570 checkVideoFilesWereRemoved
0e1dc3e7 571}