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