]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/utils/videos/videos.ts
Add ability to search a video with an URL
[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, immutableAssign,
11 makeGetRequest,
12 makePutBodyRequest,
13 makeUploadRequest,
14 root,
15 ServerInfo,
16 testImage
17 } from '../'
18 import { VideoDetails, 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?: 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 (videoUUID: string, serverNumber: number) {
271 const testDirectory = 'test' + serverNumber
272
273 for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]) {
274 const directoryPath = join(root(), testDirectory, directory)
275
276 const directoryExists = existsSync(directoryPath)
277 expect(directoryExists).to.be.true
278
279 const files = await readdirPromise(directoryPath)
280 for (const file of files) {
281 expect(file).to.not.contain(videoUUID)
282 }
283 }
284 }
285
286 async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
287 const path = '/api/v1/videos/upload'
288 let defaultChannelId = '1'
289
290 try {
291 const res = await getMyUserInformation(url, accessToken)
292 defaultChannelId = res.body.videoChannels[0].id
293 } catch (e) { /* empty */ }
294
295 // Override default attributes
296 const attributes = Object.assign({
297 name: 'my super video',
298 category: 5,
299 licence: 4,
300 language: 'zh',
301 channelId: defaultChannelId,
302 nsfw: true,
303 waitTranscoding: false,
304 description: 'my super description',
305 support: 'my super support text',
306 tags: [ 'tag' ],
307 privacy: VideoPrivacy.PUBLIC,
308 commentsEnabled: true,
309 fixture: 'video_short.webm'
310 }, videoAttributesArg)
311
312 const req = request(url)
313 .post(path)
314 .set('Accept', 'application/json')
315 .set('Authorization', 'Bearer ' + accessToken)
316 .field('name', attributes.name)
317 .field('nsfw', JSON.stringify(attributes.nsfw))
318 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
319 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
320 .field('privacy', attributes.privacy.toString())
321 .field('channelId', attributes.channelId)
322
323 if (attributes.description !== undefined) {
324 req.field('description', attributes.description)
325 }
326 if (attributes.language !== undefined) {
327 req.field('language', attributes.language.toString())
328 }
329 if (attributes.category !== undefined) {
330 req.field('category', attributes.category.toString())
331 }
332 if (attributes.licence !== undefined) {
333 req.field('licence', attributes.licence.toString())
334 }
335
336 for (let i = 0; i < attributes.tags.length; i++) {
337 req.field('tags[' + i + ']', attributes.tags[i])
338 }
339
340 if (attributes.thumbnailfile !== undefined) {
341 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
342 }
343 if (attributes.previewfile !== undefined) {
344 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
345 }
346
347 if (attributes.scheduleUpdate) {
348 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
349
350 if (attributes.scheduleUpdate.privacy) {
351 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
352 }
353 }
354
355 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
356 .expect(specialStatus)
357 }
358
359 function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
360 const path = '/api/v1/videos/' + id
361 const body = {}
362
363 if (attributes.name) body['name'] = attributes.name
364 if (attributes.category) body['category'] = attributes.category
365 if (attributes.licence) body['licence'] = attributes.licence
366 if (attributes.language) body['language'] = attributes.language
367 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
368 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
369 if (attributes.description) body['description'] = attributes.description
370 if (attributes.tags) body['tags'] = attributes.tags
371 if (attributes.privacy) body['privacy'] = attributes.privacy
372 if (attributes.channelId) body['channelId'] = attributes.channelId
373 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
374
375 // Upload request
376 if (attributes.thumbnailfile || attributes.previewfile) {
377 const attaches: any = {}
378 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
379 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
380
381 return makeUploadRequest({
382 url,
383 method: 'PUT',
384 path,
385 token: accessToken,
386 fields: body,
387 attaches,
388 statusCodeExpected
389 })
390 }
391
392 return makePutBodyRequest({
393 url,
394 path,
395 fields: body,
396 token: accessToken,
397 statusCodeExpected
398 })
399 }
400
401 function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
402 const path = '/api/v1/videos/' + id + '/rate'
403
404 return request(url)
405 .put(path)
406 .set('Accept', 'application/json')
407 .set('Authorization', 'Bearer ' + accessToken)
408 .send({ rating })
409 .expect(specialStatus)
410 }
411
412 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
413 return new Promise<any>((res, rej) => {
414 const torrentName = videoUUID + '-' + resolution + '.torrent'
415 const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
416 readFile(torrentPath, (err, data) => {
417 if (err) return rej(err)
418
419 return res(parseTorrent(data))
420 })
421 })
422 }
423
424 async function completeVideoCheck (
425 url: string,
426 video: any,
427 attributes: {
428 name: string
429 category: number
430 licence: number
431 language: string
432 nsfw: boolean
433 commentsEnabled: boolean
434 description: string
435 publishedAt?: string
436 support: string
437 account: {
438 name: string
439 host: string
440 }
441 isLocal: boolean
442 tags: string[]
443 privacy: number
444 likes?: number
445 dislikes?: number
446 duration: number
447 channel: {
448 displayName: string
449 name: string
450 description
451 isLocal: boolean
452 }
453 fixture: string
454 files: {
455 resolution: number
456 size: number
457 }[],
458 thumbnailfile?: string
459 previewfile?: string
460 }
461 ) {
462 if (!attributes.likes) attributes.likes = 0
463 if (!attributes.dislikes) attributes.dislikes = 0
464
465 expect(video.name).to.equal(attributes.name)
466 expect(video.category.id).to.equal(attributes.category)
467 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
468 expect(video.licence.id).to.equal(attributes.licence)
469 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
470 expect(video.language.id).to.equal(attributes.language)
471 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
472 expect(video.privacy.id).to.deep.equal(attributes.privacy)
473 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
474 expect(video.nsfw).to.equal(attributes.nsfw)
475 expect(video.description).to.equal(attributes.description)
476 expect(video.account.id).to.be.a('number')
477 expect(video.account.uuid).to.be.a('string')
478 expect(video.account.host).to.equal(attributes.account.host)
479 expect(video.account.name).to.equal(attributes.account.name)
480 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
481 expect(video.channel.name).to.equal(attributes.channel.name)
482 expect(video.likes).to.equal(attributes.likes)
483 expect(video.dislikes).to.equal(attributes.dislikes)
484 expect(video.isLocal).to.equal(attributes.isLocal)
485 expect(video.duration).to.equal(attributes.duration)
486 expect(dateIsValid(video.createdAt)).to.be.true
487 expect(dateIsValid(video.publishedAt)).to.be.true
488 expect(dateIsValid(video.updatedAt)).to.be.true
489
490 if (attributes.publishedAt) {
491 expect(video.publishedAt).to.equal(attributes.publishedAt)
492 }
493
494 const res = await getVideo(url, video.uuid)
495 const videoDetails: VideoDetails = res.body
496
497 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
498 expect(videoDetails.tags).to.deep.equal(attributes.tags)
499 expect(videoDetails.account.name).to.equal(attributes.account.name)
500 expect(videoDetails.account.host).to.equal(attributes.account.host)
501 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
502 expect(video.channel.name).to.equal(attributes.channel.name)
503 expect(videoDetails.channel.host).to.equal(attributes.account.host)
504 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
505 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
506 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
507 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
508
509 for (const attributeFile of attributes.files) {
510 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
511 expect(file).not.to.be.undefined
512
513 let extension = extname(attributes.fixture)
514 // Transcoding enabled on server 2, extension will always be .mp4
515 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
516
517 const magnetUri = file.magnetUri
518 expect(file.magnetUri).to.have.lengthOf.above(2)
519 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
520 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
521 expect(file.resolution.id).to.equal(attributeFile.resolution)
522 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
523
524 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
525 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
526 expect(file.size,
527 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')')
528 .to.be.above(minSize).and.below(maxSize)
529
530 {
531 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
532 }
533
534 if (attributes.previewfile) {
535 await testImage(url, attributes.previewfile, videoDetails.previewPath)
536 }
537
538 const torrent = await webtorrentAdd(magnetUri, true)
539 expect(torrent.files).to.be.an('array')
540 expect(torrent.files.length).to.equal(1)
541 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
542 }
543 }
544
545 // ---------------------------------------------------------------------------
546
547 export {
548 getVideoDescription,
549 getVideoCategories,
550 getVideoLicences,
551 getVideoPrivacies,
552 getVideoLanguages,
553 getMyVideos,
554 getAccountVideos,
555 getVideoChannelVideos,
556 getVideo,
557 getVideoWithToken,
558 getVideosList,
559 getVideosListPagination,
560 getVideosListSort,
561 removeVideo,
562 getVideosListWithToken,
563 uploadVideo,
564 getVideosWithFilters,
565 updateVideo,
566 rateVideo,
567 viewVideo,
568 parseTorrentVideo,
569 getLocalVideos,
570 completeVideoCheck,
571 checkVideoFilesWereRemoved
572 }