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