]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/utils/videos/videos.ts
Move fixtures in tests/
[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 { VideoDetails, 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, accountName: string, start: number, count: number, sort?: string) {
171 const path = '/api/v1/accounts/' + accountName + '/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 videoChannelId: number | string,
190 start: number,
191 count: number,
192 sort?: string
193 ) {
194 const path = '/api/v1/video-channels/' + videoChannelId + '/videos'
195
196 return makeGetRequest({
197 url,
198 path,
199 query: {
200 start,
201 count,
202 sort
203 },
204 token: accessToken,
205 statusCodeExpected: 200
206 })
207 }
208
209 function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
210 const path = '/api/v1/videos'
211
212 const req = request(url)
213 .get(path)
214 .query({ start: start })
215 .query({ count: count })
216
217 if (sort) req.query({ sort })
218
219 return req.set('Accept', 'application/json')
220 .expect(200)
221 .expect('Content-Type', /json/)
222 }
223
224 function getVideosListSort (url: string, sort: string) {
225 const path = '/api/v1/videos'
226
227 return request(url)
228 .get(path)
229 .query({ sort: sort })
230 .set('Accept', 'application/json')
231 .expect(200)
232 .expect('Content-Type', /json/)
233 }
234
235 function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
236 const path = '/api/v1/videos'
237
238 return request(url)
239 .delete(path + '/' + id)
240 .set('Accept', 'application/json')
241 .set('Authorization', 'Bearer ' + token)
242 .expect(expectedStatus)
243 }
244
245 function searchVideo (url: string, search: string) {
246 const path = '/api/v1/videos'
247 const req = request(url)
248 .get(path + '/search')
249 .query({ search })
250 .set('Accept', 'application/json')
251
252 return req.expect(200)
253 .expect('Content-Type', /json/)
254 }
255
256 function searchVideoWithToken (url: string, search: string, token: string) {
257 const path = '/api/v1/videos'
258 const req = request(url)
259 .get(path + '/search')
260 .set('Authorization', 'Bearer ' + token)
261 .query({ search })
262 .set('Accept', 'application/json')
263
264 return req.expect(200)
265 .expect('Content-Type', /json/)
266 }
267
268 function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
269 const path = '/api/v1/videos'
270
271 const req = request(url)
272 .get(path + '/search')
273 .query({ start })
274 .query({ search })
275 .query({ count })
276
277 if (sort) req.query({ sort })
278
279 return req.set('Accept', 'application/json')
280 .expect(200)
281 .expect('Content-Type', /json/)
282 }
283
284 function searchVideoWithSort (url: string, search: string, sort: string) {
285 const path = '/api/v1/videos'
286
287 return request(url)
288 .get(path + '/search')
289 .query({ search })
290 .query({ sort })
291 .set('Accept', 'application/json')
292 .expect(200)
293 .expect('Content-Type', /json/)
294 }
295
296 async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) {
297 const testDirectory = 'test' + serverNumber
298
299 for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews' ]) {
300 const directoryPath = join(root(), testDirectory, directory)
301
302 const directoryExists = existsSync(directoryPath)
303 expect(directoryExists).to.be.true
304
305 const files = await readdirPromise(directoryPath)
306 for (const file of files) {
307 expect(file).to.not.contain(videoUUID)
308 }
309 }
310 }
311
312 async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
313 const path = '/api/v1/videos/upload'
314 let defaultChannelId = '1'
315
316 try {
317 const res = await getMyUserInformation(url, accessToken)
318 defaultChannelId = res.body.videoChannels[0].id
319 } catch (e) { /* empty */ }
320
321 // Override default attributes
322 const attributes = Object.assign({
323 name: 'my super video',
324 category: 5,
325 licence: 4,
326 language: 'zh',
327 channelId: defaultChannelId,
328 nsfw: true,
329 description: 'my super description',
330 support: 'my super support text',
331 tags: [ 'tag' ],
332 privacy: VideoPrivacy.PUBLIC,
333 commentsEnabled: true,
334 fixture: 'video_short.webm'
335 }, videoAttributesArg)
336
337 const req = request(url)
338 .post(path)
339 .set('Accept', 'application/json')
340 .set('Authorization', 'Bearer ' + accessToken)
341 .field('name', attributes.name)
342 .field('nsfw', JSON.stringify(attributes.nsfw))
343 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
344 .field('privacy', attributes.privacy.toString())
345 .field('channelId', attributes.channelId)
346
347 if (attributes.description !== undefined) {
348 req.field('description', attributes.description)
349 }
350 if (attributes.language !== undefined) {
351 req.field('language', attributes.language.toString())
352 }
353 if (attributes.category !== undefined) {
354 req.field('category', attributes.category.toString())
355 }
356 if (attributes.licence !== undefined) {
357 req.field('licence', attributes.licence.toString())
358 }
359
360 for (let i = 0; i < attributes.tags.length; i++) {
361 req.field('tags[' + i + ']', attributes.tags[i])
362 }
363
364 if (attributes.thumbnailfile !== undefined) {
365 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
366 }
367 if (attributes.previewfile !== undefined) {
368 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
369 }
370
371 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
372 .expect(specialStatus)
373 }
374
375 function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
376 const path = '/api/v1/videos/' + id
377 const body = {}
378
379 if (attributes.name) body['name'] = attributes.name
380 if (attributes.category) body['category'] = attributes.category
381 if (attributes.licence) body['licence'] = attributes.licence
382 if (attributes.language) body['language'] = attributes.language
383 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
384 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
385 if (attributes.description) body['description'] = attributes.description
386 if (attributes.tags) body['tags'] = attributes.tags
387 if (attributes.privacy) body['privacy'] = attributes.privacy
388 if (attributes.channelId) body['channelId'] = attributes.channelId
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 publishedAt?: string
451 support: string
452 account: {
453 name: string
454 host: string
455 }
456 isLocal: boolean,
457 tags: string[],
458 privacy: number,
459 likes?: number,
460 dislikes?: number,
461 duration: number,
462 channel: {
463 name: string,
464 description
465 isLocal: boolean
466 }
467 fixture: string,
468 files: {
469 resolution: number
470 size: number
471 }[],
472 thumbnailfile?: string
473 previewfile?: string
474 }
475 ) {
476 if (!attributes.likes) attributes.likes = 0
477 if (!attributes.dislikes) attributes.dislikes = 0
478
479 expect(video.name).to.equal(attributes.name)
480 expect(video.category.id).to.equal(attributes.category)
481 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
482 expect(video.licence.id).to.equal(attributes.licence)
483 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
484 expect(video.language.id).to.equal(attributes.language)
485 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
486 expect(video.privacy.id).to.deep.equal(attributes.privacy)
487 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
488 expect(video.nsfw).to.equal(attributes.nsfw)
489 expect(video.description).to.equal(attributes.description)
490 expect(video.account.id).to.be.a('number')
491 expect(video.account.uuid).to.be.a('string')
492 expect(video.account.host).to.equal(attributes.account.host)
493 expect(video.account.name).to.equal(attributes.account.name)
494 expect(video.channel.displayName).to.equal(attributes.channel.name)
495 expect(video.channel.name).to.have.lengthOf(36)
496 expect(video.likes).to.equal(attributes.likes)
497 expect(video.dislikes).to.equal(attributes.dislikes)
498 expect(video.isLocal).to.equal(attributes.isLocal)
499 expect(video.duration).to.equal(attributes.duration)
500 expect(dateIsValid(video.createdAt)).to.be.true
501 expect(dateIsValid(video.publishedAt)).to.be.true
502 expect(dateIsValid(video.updatedAt)).to.be.true
503
504 if (attributes.publishedAt) {
505 expect(video.publishedAt).to.equal(attributes.publishedAt)
506 }
507
508 const res = await getVideo(url, video.uuid)
509 const videoDetails: VideoDetails = res.body
510
511 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
512 expect(videoDetails.tags).to.deep.equal(attributes.tags)
513 expect(videoDetails.account.name).to.equal(attributes.account.name)
514 expect(videoDetails.account.host).to.equal(attributes.account.host)
515 expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
516 expect(videoDetails.channel.name).to.have.lengthOf(36)
517 expect(videoDetails.channel.host).to.equal(attributes.account.host)
518 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
519 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
520 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
521 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
522
523 for (const attributeFile of attributes.files) {
524 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
525 expect(file).not.to.be.undefined
526
527 let extension = extname(attributes.fixture)
528 // Transcoding enabled on server 2, extension will always be .mp4
529 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
530
531 const magnetUri = file.magnetUri
532 expect(file.magnetUri).to.have.lengthOf.above(2)
533 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
534 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
535 expect(file.resolution.id).to.equal(attributeFile.resolution)
536 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
537
538 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
539 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
540 expect(file.size).to.be.above(minSize).and.below(maxSize)
541
542 {
543 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
544 }
545
546 if (attributes.previewfile) {
547 await testImage(url, attributes.previewfile, videoDetails.previewPath)
548 }
549
550 const torrent = await webtorrentAdd(magnetUri, true)
551 expect(torrent.files).to.be.an('array')
552 expect(torrent.files.length).to.equal(1)
553 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
554 }
555 }
556
557 // ---------------------------------------------------------------------------
558
559 export {
560 getVideoDescription,
561 getVideoCategories,
562 getVideoLicences,
563 getVideoPrivacies,
564 getVideoLanguages,
565 getMyVideos,
566 getAccountVideos,
567 getVideoChannelVideos,
568 searchVideoWithToken,
569 getVideo,
570 getVideoWithToken,
571 getVideosList,
572 getVideosListPagination,
573 getVideosListSort,
574 removeVideo,
575 searchVideo,
576 searchVideoWithPagination,
577 searchVideoWithSort,
578 getVideosListWithToken,
579 uploadVideo,
580 updateVideo,
581 rateVideo,
582 viewVideo,
583 parseTorrentVideo,
584 getLocalVideos,
585 completeVideoCheck,
586 checkVideoFilesWereRemoved
587 }