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