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