Add about page
[oweals/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, isAbsolute, join } from 'path'
7 import * as request from 'supertest'
8 import { getMyUserInformation, makeGetRequest, root, ServerInfo, testImage } from '../'
9 import { VideoPrivacy } from '../../../../shared/models/videos'
10 import { readdirPromise } from '../../../helpers/core-utils'
11 import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
12 import { dateIsValid, webtorrentAdd } from '../index'
13
14 type VideoAttributes = {
15   name?: string
16   category?: number
17   licence?: number
18   language?: number
19   nsfw?: boolean
20   commentsEnabled?: boolean
21   description?: string
22   tags?: string[]
23   channelId?: number
24   privacy?: VideoPrivacy
25   fixture?: string
26 }
27
28 function getVideoCategories (url: string) {
29   const path = '/api/v1/videos/categories'
30
31   return makeGetRequest({
32     url,
33     path,
34     statusCodeExpected: 200
35   })
36 }
37
38 function getVideoLicences (url: string) {
39   const path = '/api/v1/videos/licences'
40
41   return makeGetRequest({
42     url,
43     path,
44     statusCodeExpected: 200
45   })
46 }
47
48 function getVideoLanguages (url: string) {
49   const path = '/api/v1/videos/languages'
50
51   return makeGetRequest({
52     url,
53     path,
54     statusCodeExpected: 200
55   })
56 }
57
58 function getVideoPrivacies (url: string) {
59   const path = '/api/v1/videos/privacies'
60
61   return makeGetRequest({
62     url,
63     path,
64     statusCodeExpected: 200
65   })
66 }
67
68 function getVideo (url: string, id: number | string, expectedStatus = 200) {
69   const path = '/api/v1/videos/' + id
70
71   return request(url)
72           .get(path)
73           .set('Accept', 'application/json')
74           .expect(expectedStatus)
75 }
76
77 function viewVideo (url: string, id: number | string, expectedStatus = 204) {
78   const path = '/api/v1/videos/' + id + '/views'
79
80   return request(url)
81     .post(path)
82     .set('Accept', 'application/json')
83     .expect(expectedStatus)
84 }
85
86 function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) {
87   const path = '/api/v1/videos/' + id
88
89   return request(url)
90     .get(path)
91     .set('Authorization', 'Bearer ' + token)
92     .set('Accept', 'application/json')
93     .expect(expectedStatus)
94 }
95
96 function getVideoDescription (url: string, descriptionPath: string) {
97   return request(url)
98     .get(descriptionPath)
99     .set('Accept', 'application/json')
100     .expect(200)
101     .expect('Content-Type', /json/)
102 }
103
104 function getVideosList (url: string) {
105   const path = '/api/v1/videos'
106
107   return request(url)
108           .get(path)
109           .query({ sort: 'name' })
110           .set('Accept', 'application/json')
111           .expect(200)
112           .expect('Content-Type', /json/)
113 }
114
115 function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
116   const path = '/api/v1/users/me/videos'
117
118   const req = request(url)
119     .get(path)
120     .query({ start: start })
121     .query({ count: count })
122
123   if (sort) req.query({ sort })
124
125   return req.set('Accept', 'application/json')
126     .set('Authorization', 'Bearer ' + accessToken)
127     .expect(200)
128     .expect('Content-Type', /json/)
129 }
130
131 function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
132   const path = '/api/v1/videos'
133
134   const req = request(url)
135               .get(path)
136               .query({ start: start })
137               .query({ count: count })
138
139   if (sort) req.query({ sort })
140
141   return req.set('Accept', 'application/json')
142            .expect(200)
143            .expect('Content-Type', /json/)
144 }
145
146 function getVideosListSort (url: string, sort: string) {
147   const path = '/api/v1/videos'
148
149   return request(url)
150           .get(path)
151           .query({ sort: sort })
152           .set('Accept', 'application/json')
153           .expect(200)
154           .expect('Content-Type', /json/)
155 }
156
157 function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
158   const path = '/api/v1/videos'
159
160   return request(url)
161           .delete(path + '/' + id)
162           .set('Accept', 'application/json')
163           .set('Authorization', 'Bearer ' + token)
164           .expect(expectedStatus)
165 }
166
167 function searchVideo (url: string, search: string) {
168   const path = '/api/v1/videos'
169   const req = request(url)
170     .get(path + '/search')
171     .query({ search })
172     .set('Accept', 'application/json')
173
174   return req.expect(200)
175     .expect('Content-Type', /json/)
176 }
177
178 function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
179   const path = '/api/v1/videos'
180
181   const req = request(url)
182                 .get(path + '/search')
183                 .query({ start })
184                 .query({ search })
185                 .query({ count })
186
187   if (sort) req.query({ sort })
188
189   return req.set('Accept', 'application/json')
190             .expect(200)
191             .expect('Content-Type', /json/)
192 }
193
194 function searchVideoWithSort (url: string, search: string, sort: string) {
195   const path = '/api/v1/videos'
196
197   return request(url)
198           .get(path + '/search')
199           .query({ search })
200           .query({ sort })
201           .set('Accept', 'application/json')
202           .expect(200)
203           .expect('Content-Type', /json/)
204 }
205
206 async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) {
207   const testDirectory = 'test' + serverNumber
208
209   for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews' ]) {
210     const directoryPath = join(root(), testDirectory, directory)
211
212     const directoryExists = existsSync(directoryPath)
213     expect(directoryExists).to.be.true
214
215     const files = await readdirPromise(directoryPath)
216     for (const file of files) {
217       expect(file).to.not.contain(videoUUID)
218     }
219   }
220 }
221
222 async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
223   const path = '/api/v1/videos/upload'
224   let defaultChannelId = '1'
225
226   try {
227     const res = await getMyUserInformation(url, accessToken)
228     defaultChannelId = res.body.videoChannels[0].id
229   } catch (e) { /* empty */ }
230
231   // Default attributes
232   let attributes = {
233     name: 'my super video',
234     category: 5,
235     licence: 4,
236     language: 3,
237     channelId: defaultChannelId,
238     nsfw: true,
239     description: 'my super description',
240     tags: [ 'tag' ],
241     privacy: VideoPrivacy.PUBLIC,
242     commentsEnabled: true,
243     fixture: 'video_short.webm'
244   }
245   attributes = Object.assign(attributes, videoAttributesArg)
246
247   const req = request(url)
248               .post(path)
249               .set('Accept', 'application/json')
250               .set('Authorization', 'Bearer ' + accessToken)
251               .field('name', attributes.name)
252               .field('category', attributes.category.toString())
253               .field('licence', attributes.licence.toString())
254               .field('nsfw', JSON.stringify(attributes.nsfw))
255               .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
256               .field('description', attributes.description)
257               .field('privacy', attributes.privacy.toString())
258               .field('channelId', attributes.channelId)
259
260   if (attributes.language !== undefined) {
261     req.field('language', attributes.language.toString())
262   }
263
264   for (let i = 0; i < attributes.tags.length; i++) {
265     req.field('tags[' + i + ']', attributes.tags[i])
266   }
267
268   let filePath = ''
269   if (isAbsolute(attributes.fixture)) {
270     filePath = attributes.fixture
271   } else {
272     filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture)
273   }
274
275   return req.attach('videofile', filePath)
276             .expect(specialStatus)
277 }
278
279 function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) {
280   const path = '/api/v1/videos/' + id
281   const body = {}
282
283   if (attributes.name) body['name'] = attributes.name
284   if (attributes.category) body['category'] = attributes.category
285   if (attributes.licence) body['licence'] = attributes.licence
286   if (attributes.language) body['language'] = attributes.language
287   if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
288   if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
289   if (attributes.description) body['description'] = attributes.description
290   if (attributes.tags) body['tags'] = attributes.tags
291   if (attributes.privacy) body['privacy'] = attributes.privacy
292
293   return request(url)
294           .put(path)
295           .send(body)
296           .set('Accept', 'application/json')
297           .set('Authorization', 'Bearer ' + accessToken)
298           .expect(specialStatus)
299 }
300
301 function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
302   const path = '/api/v1/videos/' + id + '/rate'
303
304   return request(url)
305           .put(path)
306           .set('Accept', 'application/json')
307           .set('Authorization', 'Bearer ' + accessToken)
308           .send({ rating })
309           .expect(specialStatus)
310 }
311
312 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
313   return new Promise<any>((res, rej) => {
314     const torrentName = videoUUID + '-' + resolution + '.torrent'
315     const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
316     readFile(torrentPath, (err, data) => {
317       if (err) return rej(err)
318
319       return res(parseTorrent(data))
320     })
321   })
322 }
323
324 async function completeVideoCheck (
325   url: string,
326   video: any,
327   attributes: {
328     name: string
329     category: number
330     licence: number
331     language: number
332     nsfw: boolean
333     commentsEnabled: boolean
334     description: string
335     host: string
336     account: string
337     isLocal: boolean,
338     tags: string[],
339     privacy: number,
340     likes?: number,
341     dislikes?: number,
342     duration: number,
343     channel: {
344       name: string,
345       description
346       isLocal: boolean
347     }
348     fixture: string,
349     files: {
350       resolution: number
351       size: number
352     }[]
353   }
354 ) {
355   if (!attributes.likes) attributes.likes = 0
356   if (!attributes.dislikes) attributes.dislikes = 0
357
358   expect(video.name).to.equal(attributes.name)
359   expect(video.category).to.equal(attributes.category)
360   expect(video.categoryLabel).to.equal(VIDEO_CATEGORIES[attributes.category] || 'Misc')
361   expect(video.licence).to.equal(attributes.licence)
362   expect(video.licenceLabel).to.equal(VIDEO_LICENCES[attributes.licence] || 'Unknown')
363   expect(video.language).to.equal(attributes.language)
364   expect(video.languageLabel).to.equal(VIDEO_LANGUAGES[attributes.language] || 'Unknown')
365   expect(video.nsfw).to.equal(attributes.nsfw)
366   expect(video.description).to.equal(attributes.description)
367   expect(video.serverHost).to.equal(attributes.host)
368   expect(video.accountName).to.equal(attributes.account)
369   expect(video.likes).to.equal(attributes.likes)
370   expect(video.dislikes).to.equal(attributes.dislikes)
371   expect(video.isLocal).to.equal(attributes.isLocal)
372   expect(video.duration).to.equal(attributes.duration)
373   expect(dateIsValid(video.createdAt)).to.be.true
374   expect(dateIsValid(video.updatedAt)).to.be.true
375
376   const res = await getVideo(url, video.uuid)
377   const videoDetails = res.body
378
379   expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
380   expect(videoDetails.tags).to.deep.equal(attributes.tags)
381   expect(videoDetails.privacy).to.deep.equal(attributes.privacy)
382   expect(videoDetails.privacyLabel).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
383   expect(videoDetails.account.name).to.equal(attributes.account)
384   expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
385
386   expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
387   expect(videoDetails.channel.name).to.have.lengthOf(36)
388   expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
389   expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true
390   expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true
391
392   for (const attributeFile of attributes.files) {
393     const file = videoDetails.files.find(f => f.resolution === attributeFile.resolution)
394     expect(file).not.to.be.undefined
395
396     let extension = extname(attributes.fixture)
397     // Transcoding enabled on server 2, extension will always be .mp4
398     if (attributes.host === 'localhost:9002') extension = '.mp4'
399
400     const magnetUri = file.magnetUri
401     expect(file.magnetUri).to.have.lengthOf.above(2)
402     expect(file.torrentUrl).to.equal(`http://${attributes.host}/static/torrents/${videoDetails.uuid}-${file.resolution}.torrent`)
403     expect(file.fileUrl).to.equal(`http://${attributes.host}/static/webseed/${videoDetails.uuid}-${file.resolution}${extension}`)
404     expect(file.resolution).to.equal(attributeFile.resolution)
405     expect(file.resolutionLabel).to.equal(attributeFile.resolution + 'p')
406
407     const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
408     const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
409     expect(file.size).to.be.above(minSize).and.below(maxSize)
410
411     const test = await testImage(url, attributes.fixture, videoDetails.thumbnailPath)
412     expect(test).to.equal(true)
413
414     const torrent = await webtorrentAdd(magnetUri, true)
415     expect(torrent.files).to.be.an('array')
416     expect(torrent.files.length).to.equal(1)
417     expect(torrent.files[0].path).to.exist.and.to.not.equal('')
418   }
419 }
420
421 // ---------------------------------------------------------------------------
422
423 export {
424   getVideoDescription,
425   getVideoCategories,
426   getVideoLicences,
427   getVideoPrivacies,
428   getVideoLanguages,
429   getMyVideos,
430   getVideo,
431   getVideoWithToken,
432   getVideosList,
433   getVideosListPagination,
434   getVideosListSort,
435   removeVideo,
436   searchVideo,
437   searchVideoWithPagination,
438   searchVideoWithSort,
439   uploadVideo,
440   updateVideo,
441   rateVideo,
442   viewVideo,
443   parseTorrentVideo,
444   completeVideoCheck,
445   checkVideoFilesWereRemoved
446 }