]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/utils/videos/videos.ts
Precisions and security enhancements to the production guide (#287)
[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, 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('nsfw', JSON.stringify(attributes.nsfw))
253 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
254 .field('privacy', attributes.privacy.toString())
255 .field('channelId', attributes.channelId)
256
257 if (attributes.description !== undefined) {
258 req.field('description', attributes.description)
259 }
260 if (attributes.language !== undefined) {
261 req.field('language', attributes.language.toString())
262 }
263 if (attributes.category !== undefined) {
264 req.field('category', attributes.category.toString())
265 }
266 if (attributes.licence !== undefined) {
267 req.field('licence', attributes.licence.toString())
268 }
269
270 for (let i = 0; i < attributes.tags.length; i++) {
271 req.field('tags[' + i + ']', attributes.tags[i])
272 }
273
274 let filePath = ''
275 if (isAbsolute(attributes.fixture)) {
276 filePath = attributes.fixture
277 } else {
278 filePath = join(__dirname, '..', '..', 'api', 'fixtures', attributes.fixture)
279 }
280
281 return req.attach('videofile', filePath)
282 .expect(specialStatus)
283 }
284
285 function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) {
286 const path = '/api/v1/videos/' + id
287 const body = {}
288
289 if (attributes.name) body['name'] = attributes.name
290 if (attributes.category) body['category'] = attributes.category
291 if (attributes.licence) body['licence'] = attributes.licence
292 if (attributes.language) body['language'] = attributes.language
293 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
294 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
295 if (attributes.description) body['description'] = attributes.description
296 if (attributes.tags) body['tags'] = attributes.tags
297 if (attributes.privacy) body['privacy'] = attributes.privacy
298
299 return request(url)
300 .put(path)
301 .send(body)
302 .set('Accept', 'application/json')
303 .set('Authorization', 'Bearer ' + accessToken)
304 .expect(specialStatus)
305 }
306
307 function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
308 const path = '/api/v1/videos/' + id + '/rate'
309
310 return request(url)
311 .put(path)
312 .set('Accept', 'application/json')
313 .set('Authorization', 'Bearer ' + accessToken)
314 .send({ rating })
315 .expect(specialStatus)
316 }
317
318 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
319 return new Promise<any>((res, rej) => {
320 const torrentName = videoUUID + '-' + resolution + '.torrent'
321 const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
322 readFile(torrentPath, (err, data) => {
323 if (err) return rej(err)
324
325 return res(parseTorrent(data))
326 })
327 })
328 }
329
330 async function completeVideoCheck (
331 url: string,
332 video: any,
333 attributes: {
334 name: string
335 category: number
336 licence: number
337 language: number
338 nsfw: boolean
339 commentsEnabled: boolean
340 description: string
341 host: string
342 account: string
343 isLocal: boolean,
344 tags: string[],
345 privacy: number,
346 likes?: number,
347 dislikes?: number,
348 duration: number,
349 channel: {
350 name: string,
351 description
352 isLocal: boolean
353 }
354 fixture: string,
355 files: {
356 resolution: number
357 size: number
358 }[]
359 }
360 ) {
361 if (!attributes.likes) attributes.likes = 0
362 if (!attributes.dislikes) attributes.dislikes = 0
363
364 expect(video.name).to.equal(attributes.name)
365 expect(video.category).to.equal(attributes.category)
366 expect(video.categoryLabel).to.equal(VIDEO_CATEGORIES[attributes.category] || 'Misc')
367 expect(video.licence).to.equal(attributes.licence)
368 expect(video.licenceLabel).to.equal(VIDEO_LICENCES[attributes.licence] || 'Unknown')
369 expect(video.language).to.equal(attributes.language)
370 expect(video.languageLabel).to.equal(VIDEO_LANGUAGES[attributes.language] || 'Unknown')
371 expect(video.nsfw).to.equal(attributes.nsfw)
372 expect(video.description).to.equal(attributes.description)
373 expect(video.serverHost).to.equal(attributes.host)
374 expect(video.accountName).to.equal(attributes.account)
375 expect(video.likes).to.equal(attributes.likes)
376 expect(video.dislikes).to.equal(attributes.dislikes)
377 expect(video.isLocal).to.equal(attributes.isLocal)
378 expect(video.duration).to.equal(attributes.duration)
379 expect(dateIsValid(video.createdAt)).to.be.true
380 expect(dateIsValid(video.updatedAt)).to.be.true
381
382 const res = await getVideo(url, video.uuid)
383 const videoDetails = res.body
384
385 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
386 expect(videoDetails.tags).to.deep.equal(attributes.tags)
387 expect(videoDetails.privacy).to.deep.equal(attributes.privacy)
388 expect(videoDetails.privacyLabel).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
389 expect(videoDetails.account.name).to.equal(attributes.account)
390 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
391
392 expect(videoDetails.channel.displayName).to.equal(attributes.channel.name)
393 expect(videoDetails.channel.name).to.have.lengthOf(36)
394 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
395 expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true
396 expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true
397
398 for (const attributeFile of attributes.files) {
399 const file = videoDetails.files.find(f => f.resolution === attributeFile.resolution)
400 expect(file).not.to.be.undefined
401
402 let extension = extname(attributes.fixture)
403 // Transcoding enabled on server 2, extension will always be .mp4
404 if (attributes.host === 'localhost:9002') extension = '.mp4'
405
406 const magnetUri = file.magnetUri
407 expect(file.magnetUri).to.have.lengthOf.above(2)
408 expect(file.torrentUrl).to.equal(`http://${attributes.host}/static/torrents/${videoDetails.uuid}-${file.resolution}.torrent`)
409 expect(file.fileUrl).to.equal(`http://${attributes.host}/static/webseed/${videoDetails.uuid}-${file.resolution}${extension}`)
410 expect(file.resolution).to.equal(attributeFile.resolution)
411 expect(file.resolutionLabel).to.equal(attributeFile.resolution + 'p')
412
413 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
414 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
415 expect(file.size).to.be.above(minSize).and.below(maxSize)
416
417 const test = await testImage(url, attributes.fixture, videoDetails.thumbnailPath)
418 expect(test).to.equal(true)
419
420 const torrent = await webtorrentAdd(magnetUri, true)
421 expect(torrent.files).to.be.an('array')
422 expect(torrent.files.length).to.equal(1)
423 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
424 }
425 }
426
427 // ---------------------------------------------------------------------------
428
429 export {
430 getVideoDescription,
431 getVideoCategories,
432 getVideoLicences,
433 getVideoPrivacies,
434 getVideoLanguages,
435 getMyVideos,
436 getVideo,
437 getVideoWithToken,
438 getVideosList,
439 getVideosListPagination,
440 getVideosListSort,
441 removeVideo,
442 searchVideo,
443 searchVideoWithPagination,
444 searchVideoWithSort,
445 uploadVideo,
446 updateVideo,
447 rateVideo,
448 viewVideo,
449 parseTorrentVideo,
450 completeVideoCheck,
451 checkVideoFilesWereRemoved
452 }