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