]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/utils/videos/videos.ts
Add ability to choose what policy we have for NSFW videos
[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?: number
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 getVideosListPagination (url: string, start: number, count: number, sort?: string) {
171 const path = '/api/v1/videos'
172
173 const req = request(url)
174 .get(path)
175 .query({ start: start })
176 .query({ count: count })
177
178 if (sort) req.query({ sort })
179
180 return req.set('Accept', 'application/json')
181 .expect(200)
182 .expect('Content-Type', /json/)
183 }
184
185 function getVideosListSort (url: string, sort: string) {
186 const path = '/api/v1/videos'
187
188 return request(url)
189 .get(path)
190 .query({ sort: sort })
191 .set('Accept', 'application/json')
192 .expect(200)
193 .expect('Content-Type', /json/)
194 }
195
196 function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
197 const path = '/api/v1/videos'
198
199 return request(url)
200 .delete(path + '/' + id)
201 .set('Accept', 'application/json')
202 .set('Authorization', 'Bearer ' + token)
203 .expect(expectedStatus)
204 }
205
206 function searchVideo (url: string, search: string) {
207 const path = '/api/v1/videos'
208 const req = request(url)
209 .get(path + '/search')
210 .query({ search })
211 .set('Accept', 'application/json')
212
213 return req.expect(200)
214 .expect('Content-Type', /json/)
215 }
216
217 function searchVideoWithToken (url: string, search: string, token: string) {
218 const path = '/api/v1/videos'
219 const req = request(url)
220 .get(path + '/search')
221 .set('Authorization', 'Bearer ' + token)
222 .query({ search })
223 .set('Accept', 'application/json')
224
225 return req.expect(200)
226 .expect('Content-Type', /json/)
227 }
228
229 function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
230 const path = '/api/v1/videos'
231
232 const req = request(url)
233 .get(path + '/search')
234 .query({ start })
235 .query({ search })
236 .query({ count })
237
238 if (sort) req.query({ sort })
239
240 return req.set('Accept', 'application/json')
241 .expect(200)
242 .expect('Content-Type', /json/)
243 }
244
245 function searchVideoWithSort (url: string, search: string, sort: string) {
246 const path = '/api/v1/videos'
247
248 return request(url)
249 .get(path + '/search')
250 .query({ search })
251 .query({ sort })
252 .set('Accept', 'application/json')
253 .expect(200)
254 .expect('Content-Type', /json/)
255 }
256
257 async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) {
258 const testDirectory = 'test' + serverNumber
259
260 for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews' ]) {
261 const directoryPath = join(root(), testDirectory, directory)
262
263 const directoryExists = existsSync(directoryPath)
264 expect(directoryExists).to.be.true
265
266 const files = await readdirPromise(directoryPath)
267 for (const file of files) {
268 expect(file).to.not.contain(videoUUID)
269 }
270 }
271 }
272
273 async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
274 const path = '/api/v1/videos/upload'
275 let defaultChannelId = '1'
276
277 try {
278 const res = await getMyUserInformation(url, accessToken)
279 defaultChannelId = res.body.videoChannels[0].id
280 } catch (e) { /* empty */ }
281
282 // Override default attributes
283 const attributes = Object.assign({
284 name: 'my super video',
285 category: 5,
286 licence: 4,
287 language: 3,
288 channelId: defaultChannelId,
289 nsfw: true,
290 description: 'my super description',
291 support: 'my super support text',
292 tags: [ 'tag' ],
293 privacy: VideoPrivacy.PUBLIC,
294 commentsEnabled: true,
295 fixture: 'video_short.webm'
296 }, videoAttributesArg)
297
298 const req = request(url)
299 .post(path)
300 .set('Accept', 'application/json')
301 .set('Authorization', 'Bearer ' + accessToken)
302 .field('name', attributes.name)
303 .field('nsfw', JSON.stringify(attributes.nsfw))
304 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
305 .field('privacy', attributes.privacy.toString())
306 .field('channelId', attributes.channelId)
307
308 if (attributes.description !== undefined) {
309 req.field('description', attributes.description)
310 }
311 if (attributes.language !== undefined) {
312 req.field('language', attributes.language.toString())
313 }
314 if (attributes.category !== undefined) {
315 req.field('category', attributes.category.toString())
316 }
317 if (attributes.licence !== undefined) {
318 req.field('licence', attributes.licence.toString())
319 }
320
321 for (let i = 0; i < attributes.tags.length; i++) {
322 req.field('tags[' + i + ']', attributes.tags[i])
323 }
324
325 if (attributes.thumbnailfile !== undefined) {
326 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
327 }
328 if (attributes.previewfile !== undefined) {
329 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
330 }
331
332 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
333 .expect(specialStatus)
334 }
335
336 function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
337 const path = '/api/v1/videos/' + id
338 const body = {}
339
340 if (attributes.name) body['name'] = attributes.name
341 if (attributes.category) body['category'] = attributes.category
342 if (attributes.licence) body['licence'] = attributes.licence
343 if (attributes.language) body['language'] = attributes.language
344 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
345 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
346 if (attributes.description) body['description'] = attributes.description
347 if (attributes.tags) body['tags'] = attributes.tags
348 if (attributes.privacy) body['privacy'] = attributes.privacy
349
350 // Upload request
351 if (attributes.thumbnailfile || attributes.previewfile) {
352 const attaches: any = {}
353 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
354 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
355
356 return makeUploadRequest({
357 url,
358 method: 'PUT',
359 path,
360 token: accessToken,
361 fields: body,
362 attaches,
363 statusCodeExpected
364 })
365 }
366
367 return makePutBodyRequest({
368 url,
369 path,
370 fields: body,
371 token: accessToken,
372 statusCodeExpected
373 })
374 }
375
376 function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
377 const path = '/api/v1/videos/' + id + '/rate'
378
379 return request(url)
380 .put(path)
381 .set('Accept', 'application/json')
382 .set('Authorization', 'Bearer ' + accessToken)
383 .send({ rating })
384 .expect(specialStatus)
385 }
386
387 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
388 return new Promise<any>((res, rej) => {
389 const torrentName = videoUUID + '-' + resolution + '.torrent'
390 const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
391 readFile(torrentPath, (err, data) => {
392 if (err) return rej(err)
393
394 return res(parseTorrent(data))
395 })
396 })
397 }
398
399 async function completeVideoCheck (
400 url: string,
401 video: any,
402 attributes: {
403 name: string
404 category: number
405 licence: number
406 language: number
407 nsfw: boolean
408 commentsEnabled: boolean
409 description: string
410 support: string
411 account: {
412 name: string
413 host: string
414 }
415 isLocal: boolean,
416 tags: string[],
417 privacy: number,
418 likes?: number,
419 dislikes?: number,
420 duration: number,
421 channel: {
422 name: string,
423 description
424 isLocal: boolean
425 }
426 fixture: string,
427 files: {
428 resolution: number
429 size: number
430 }[],
431 thumbnailfile?: string
432 previewfile?: string
433 }
434 ) {
435 if (!attributes.likes) attributes.likes = 0
436 if (!attributes.dislikes) attributes.dislikes = 0
437
438 expect(video.name).to.equal(attributes.name)
439 expect(video.category.id).to.equal(attributes.category)
440 expect(video.category.label).to.equal(VIDEO_CATEGORIES[attributes.category] || 'Misc')
441 expect(video.licence.id).to.equal(attributes.licence)
442 expect(video.licence.label).to.equal(VIDEO_LICENCES[attributes.licence] || 'Unknown')
443 expect(video.language.id).to.equal(attributes.language)
444 expect(video.language.label).to.equal(VIDEO_LANGUAGES[attributes.language] || 'Unknown')
445 expect(video.nsfw).to.equal(attributes.nsfw)
446 expect(video.description).to.equal(attributes.description)
447 expect(video.account.host).to.equal(attributes.account.host)
448 expect(video.account.name).to.equal(attributes.account.name)
449 expect(video.likes).to.equal(attributes.likes)
450 expect(video.dislikes).to.equal(attributes.dislikes)
451 expect(video.isLocal).to.equal(attributes.isLocal)
452 expect(video.duration).to.equal(attributes.duration)
453 expect(dateIsValid(video.createdAt)).to.be.true
454 expect(dateIsValid(video.publishedAt)).to.be.true
455 expect(dateIsValid(video.updatedAt)).to.be.true
456
457 const res = await getVideo(url, video.uuid)
458 const videoDetails = res.body
459
460 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
461 expect(videoDetails.tags).to.deep.equal(attributes.tags)
462 expect(videoDetails.privacy.id).to.deep.equal(attributes.privacy)
463 expect(videoDetails.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
464 expect(videoDetails.account.name).to.equal(attributes.account.name)
465 expect(videoDetails.account.host).to.equal(attributes.account.host)
466 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
467
468 expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
469 expect(videoDetails.channel.name).to.have.lengthOf(36)
470 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
471 expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true
472 expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true
473
474 for (const attributeFile of attributes.files) {
475 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
476 expect(file).not.to.be.undefined
477
478 let extension = extname(attributes.fixture)
479 // Transcoding enabled on server 2, extension will always be .mp4
480 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
481
482 const magnetUri = file.magnetUri
483 expect(file.magnetUri).to.have.lengthOf.above(2)
484 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
485 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
486 expect(file.resolution.id).to.equal(attributeFile.resolution)
487 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
488
489 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
490 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
491 expect(file.size).to.be.above(minSize).and.below(maxSize)
492
493 {
494 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
495 }
496
497 if (attributes.previewfile) {
498 await testImage(url, attributes.previewfile, videoDetails.previewPath)
499 }
500
501 const torrent = await webtorrentAdd(magnetUri, true)
502 expect(torrent.files).to.be.an('array')
503 expect(torrent.files.length).to.equal(1)
504 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
505 }
506 }
507
508 // ---------------------------------------------------------------------------
509
510 export {
511 getVideoDescription,
512 getVideoCategories,
513 getVideoLicences,
514 getVideoPrivacies,
515 getVideoLanguages,
516 getMyVideos,
517 searchVideoWithToken,
518 getVideo,
519 getVideoWithToken,
520 getVideosList,
521 getVideosListPagination,
522 getVideosListSort,
523 removeVideo,
524 searchVideo,
525 searchVideoWithPagination,
526 searchVideoWithSort,
527 getVideosListWithToken,
528 uploadVideo,
529 updateVideo,
530 rateVideo,
531 viewVideo,
532 parseTorrentVideo,
533 getLocalVideos,
534 completeVideoCheck,
535 checkVideoFilesWereRemoved
536 }