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