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