]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/utils/videos/videos.ts
Check video exists before extending its expiration
[github/Chocobozzz/PeerTube.git] / server / tests / utils / videos / videos.ts
CommitLineData
a20399c9
C
1/* tslint:disable:no-unused-expression */
2
3import { expect } from 'chai'
62689b94 4import { existsSync, readdir, readFile } from 'fs-extra'
fdbda9e3 5import * as parseTorrent from 'parse-torrent'
1d791a26 6import { extname, join } from 'path'
c5d31dba 7import * as request from 'supertest'
ac81d1a0
C
8import {
9 buildAbsoluteFixturePath,
62689b94
C
10 getMyUserInformation,
11 immutableAssign,
ac81d1a0
C
12 makeGetRequest,
13 makePutBodyRequest,
14 makeUploadRequest,
15 root,
16 ServerInfo,
17 testImage
18} from '../'
0f320037 19import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos'
a20399c9
C
20import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
21import { dateIsValid, webtorrentAdd } from '../index'
0e1dc3e7
C
22
23type VideoAttributes = {
24 name?: string
25 category?: number
26 licence?: number
9d3ef9fe 27 language?: string
0e1dc3e7 28 nsfw?: boolean
47564bbe 29 commentsEnabled?: boolean
2186386c 30 waitTranscoding?: boolean
0e1dc3e7
C
31 description?: string
32 tags?: string[]
5f04dd2f 33 channelId?: number
11474c3c 34 privacy?: VideoPrivacy
0e1dc3e7 35 fixture?: string
ac81d1a0
C
36 thumbnailfile?: string
37 previewfile?: string
2baea0c7
C
38 scheduleUpdate?: {
39 updateAt: string
40 privacy?: VideoPrivacy
41 }
0e1dc3e7
C
42}
43
44function getVideoCategories (url: string) {
45 const path = '/api/v1/videos/categories'
46
eec63bbc
C
47 return makeGetRequest({
48 url,
59651eee
C
49 path,
50 statusCodeExpected: 200
eec63bbc 51 })
0e1dc3e7
C
52}
53
54function getVideoLicences (url: string) {
55 const path = '/api/v1/videos/licences'
56
eec63bbc
C
57 return makeGetRequest({
58 url,
59651eee
C
59 path,
60 statusCodeExpected: 200
eec63bbc 61 })
0e1dc3e7
C
62}
63
64function getVideoLanguages (url: string) {
65 const path = '/api/v1/videos/languages'
66
eec63bbc
C
67 return makeGetRequest({
68 url,
59651eee
C
69 path,
70 statusCodeExpected: 200
eec63bbc 71 })
0e1dc3e7
C
72}
73
11474c3c
C
74function getVideoPrivacies (url: string) {
75 const path = '/api/v1/videos/privacies'
76
eec63bbc
C
77 return makeGetRequest({
78 url,
59651eee
C
79 path,
80 statusCodeExpected: 200
eec63bbc 81 })
11474c3c
C
82}
83
11474c3c 84function getVideo (url: string, id: number | string, expectedStatus = 200) {
0e1dc3e7
C
85 const path = '/api/v1/videos/' + id
86
87 return request(url)
88 .get(path)
89 .set('Accept', 'application/json')
11474c3c
C
90 .expect(expectedStatus)
91}
92
490b595a 93function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
1f3e9fec
C
94 const path = '/api/v1/videos/' + id + '/views'
95
490b595a 96 const req = request(url)
1f3e9fec
C
97 .post(path)
98 .set('Accept', 'application/json')
490b595a
C
99
100 if (xForwardedFor) {
101 req.set('X-Forwarded-For', xForwardedFor)
102 }
103
104 return req.expect(expectedStatus)
1f3e9fec
C
105}
106
11474c3c
C
107function 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)
0e1dc3e7
C
115}
116
9567011b
C
117function 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
0e1dc3e7
C
125function 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
d525fc39 136function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
0883b324
C
137 const path = '/api/v1/videos'
138
139 return request(url)
140 .get(path)
141 .set('Authorization', 'Bearer ' + token)
d525fc39 142 .query(immutableAssign(query, { sort: 'name' }))
0883b324
C
143 .set('Accept', 'application/json')
144 .expect(200)
145 .expect('Content-Type', /json/)
146}
147
066e94c5
C
148function 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
11474c3c
C
159function 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
d525fc39
C
175function getAccountVideos (
176 url: string,
177 accessToken: string,
178 accountName: string,
179 start: number,
180 count: number,
181 sort?: string,
182 query: { nsfw?: boolean } = {}
183) {
ad9e39fb 184 const path = '/api/v1/accounts/' + accountName + '/videos'
6b738c7a
C
185
186 return makeGetRequest({
187 url,
188 path,
d525fc39 189 query: immutableAssign(query, {
6b738c7a
C
190 start,
191 count,
192 sort
d525fc39 193 }),
6b738c7a
C
194 token: accessToken,
195 statusCodeExpected: 200
196 })
197}
198
199function getVideoChannelVideos (
200 url: string,
201 accessToken: string,
8a19bee1 202 videoChannelName: string,
6b738c7a
C
203 start: number,
204 count: number,
d525fc39
C
205 sort?: string,
206 query: { nsfw?: boolean } = {}
6b738c7a 207) {
8a19bee1 208 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
6b738c7a
C
209
210 return makeGetRequest({
211 url,
212 path,
d525fc39 213 query: immutableAssign(query, {
6b738c7a
C
214 start,
215 count,
216 sort
d525fc39 217 }),
6b738c7a
C
218 token: accessToken,
219 statusCodeExpected: 200
220 })
221}
222
0e1dc3e7
C
223function 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
238function 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
d525fc39 249function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
0e1dc3e7
C
250 const path = '/api/v1/videos'
251
252 return request(url)
57c36b27 253 .get(path)
d525fc39 254 .query(query)
f3aaa9a9 255 .set('Accept', 'application/json')
d525fc39 256 .expect(200)
f3aaa9a9 257 .expect('Content-Type', /json/)
0e1dc3e7
C
258}
259
d525fc39 260function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
0883b324 261 const path = '/api/v1/videos'
0e1dc3e7
C
262
263 return request(url)
d525fc39 264 .delete(path + '/' + id)
0e1dc3e7 265 .set('Accept', 'application/json')
d525fc39
C
266 .set('Authorization', 'Bearer ' + token)
267 .expect(expectedStatus)
0e1dc3e7
C
268}
269
f05a1c30
C
270async function checkVideoFilesWereRemoved (videoUUID: string, serverNumber: number) {
271 const testDirectory = 'test' + serverNumber
272
f4001cf4 273 for (const directory of [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]) {
f05a1c30
C
274 const directoryPath = join(root(), testDirectory, directory)
275
276 const directoryExists = existsSync(directoryPath)
277 expect(directoryExists).to.be.true
278
62689b94 279 const files = await readdir(directoryPath)
f05a1c30
C
280 for (const file of files) {
281 expect(file).to.not.contain(videoUUID)
282 }
283 }
284}
285
e11f68a3 286async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
e95561cd 287 const path = '/api/v1/videos/upload'
5f04dd2f
C
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 */ }
0e1dc3e7 294
ac81d1a0
C
295 // Override default attributes
296 const attributes = Object.assign({
0e1dc3e7
C
297 name: 'my super video',
298 category: 5,
299 licence: 4,
9d3ef9fe 300 language: 'zh',
5f04dd2f 301 channelId: defaultChannelId,
0e1dc3e7 302 nsfw: true,
2186386c 303 waitTranscoding: false,
0e1dc3e7 304 description: 'my super description',
2422c46b 305 support: 'my super support text',
0e1dc3e7 306 tags: [ 'tag' ],
11474c3c 307 privacy: VideoPrivacy.PUBLIC,
47564bbe 308 commentsEnabled: true,
0e1dc3e7 309 fixture: 'video_short.webm'
ac81d1a0 310 }, videoAttributesArg)
0e1dc3e7
C
311
312 const req = request(url)
313 .post(path)
314 .set('Accept', 'application/json')
315 .set('Authorization', 'Bearer ' + accessToken)
316 .field('name', attributes.name)
0e1dc3e7 317 .field('nsfw', JSON.stringify(attributes.nsfw))
47564bbe 318 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
2186386c 319 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
11474c3c 320 .field('privacy', attributes.privacy.toString())
5f04dd2f 321 .field('channelId', attributes.channelId)
0e1dc3e7 322
a87d467a
C
323 if (attributes.description !== undefined) {
324 req.field('description', attributes.description)
325 }
8df87ce7
C
326 if (attributes.language !== undefined) {
327 req.field('language', attributes.language.toString())
328 }
a7fea183
C
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 }
8df87ce7 335
2422c46b
C
336 for (let i = 0; i < attributes.tags.length; i++) {
337 req.field('tags[' + i + ']', attributes.tags[i])
338 }
339
ac81d1a0
C
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))
0e1dc3e7
C
345 }
346
2baea0c7
C
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
ac81d1a0 355 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
0e1dc3e7
C
356 .expect(specialStatus)
357}
358
ac81d1a0 359function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
0e1dc3e7
C
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
47564bbe
C
367 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
368 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
0e1dc3e7
C
369 if (attributes.description) body['description'] = attributes.description
370 if (attributes.tags) body['tags'] = attributes.tags
11474c3c 371 if (attributes.privacy) body['privacy'] = attributes.privacy
0f320037 372 if (attributes.channelId) body['channelId'] = attributes.channelId
2baea0c7 373 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
0e1dc3e7 374
ac81d1a0
C
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 })
0e1dc3e7
C
399}
400
401function 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
14d3270f 412function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
fdbda9e3 413 return new Promise<any>((res, rej) => {
14d3270f 414 const torrentName = videoUUID + '-' + resolution + '.torrent'
331128ed 415 const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
fdbda9e3
C
416 readFile(torrentPath, (err, data) => {
417 if (err) return rej(err)
418
419 return res(parseTorrent(data))
420 })
421 })
422}
423
a20399c9
C
424async function completeVideoCheck (
425 url: string,
426 video: any,
427 attributes: {
428 name: string
429 category: number
430 licence: number
9d3ef9fe 431 language: string
a20399c9 432 nsfw: boolean
47564bbe 433 commentsEnabled: boolean
a20399c9 434 description: string
53a61317 435 publishedAt?: string
2422c46b 436 support: string
b64c950a
C
437 account: {
438 name: string
439 host: string
440 }
f6eebcb3
C
441 isLocal: boolean
442 tags: string[]
443 privacy: number
444 likes?: number
445 dislikes?: number
446 duration: number
a20399c9 447 channel: {
f6eebcb3
C
448 displayName: string
449 name: string
b1f5b93e 450 description
a20399c9
C
451 isLocal: boolean
452 }
f6eebcb3 453 fixture: string
a20399c9
C
454 files: {
455 resolution: number
456 size: number
ac81d1a0
C
457 }[],
458 thumbnailfile?: string
459 previewfile?: string
a20399c9
C
460 }
461) {
b1f5b93e
C
462 if (!attributes.likes) attributes.likes = 0
463 if (!attributes.dislikes) attributes.dislikes = 0
464
a20399c9 465 expect(video.name).to.equal(attributes.name)
09700934 466 expect(video.category.id).to.equal(attributes.category)
9d3ef9fe 467 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
09700934 468 expect(video.licence.id).to.equal(attributes.licence)
9d3ef9fe 469 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
09700934 470 expect(video.language.id).to.equal(attributes.language)
9d3ef9fe 471 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
2243730c
C
472 expect(video.privacy.id).to.deep.equal(attributes.privacy)
473 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
a20399c9
C
474 expect(video.nsfw).to.equal(attributes.nsfw)
475 expect(video.description).to.equal(attributes.description)
03e12d7c
C
476 expect(video.account.id).to.be.a('number')
477 expect(video.account.uuid).to.be.a('string')
b64c950a
C
478 expect(video.account.host).to.equal(attributes.account.host)
479 expect(video.account.name).to.equal(attributes.account.name)
f6eebcb3
C
480 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
481 expect(video.channel.name).to.equal(attributes.channel.name)
b1f5b93e
C
482 expect(video.likes).to.equal(attributes.likes)
483 expect(video.dislikes).to.equal(attributes.dislikes)
a20399c9 484 expect(video.isLocal).to.equal(attributes.isLocal)
b1f5b93e 485 expect(video.duration).to.equal(attributes.duration)
a20399c9 486 expect(dateIsValid(video.createdAt)).to.be.true
c49db162 487 expect(dateIsValid(video.publishedAt)).to.be.true
a20399c9
C
488 expect(dateIsValid(video.updatedAt)).to.be.true
489
53a61317 490 if (attributes.publishedAt) {
53a61317
C
491 expect(video.publishedAt).to.equal(attributes.publishedAt)
492 }
493
66b16caf 494 const res = await getVideo(url, video.uuid)
0f320037 495 const videoDetails: VideoDetails = res.body
a20399c9
C
496
497 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
498 expect(videoDetails.tags).to.deep.equal(attributes.tags)
19a3b914
C
499 expect(videoDetails.account.name).to.equal(attributes.account.name)
500 expect(videoDetails.account.host).to.equal(attributes.account.host)
f6eebcb3
C
501 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
502 expect(video.channel.name).to.equal(attributes.channel.name)
0f320037 503 expect(videoDetails.channel.host).to.equal(attributes.account.host)
a20399c9 504 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
0f320037
C
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)
a20399c9
C
508
509 for (const attributeFile of attributes.files) {
5d00a3d7 510 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
a20399c9
C
511 expect(file).not.to.be.undefined
512
b1f5b93e
C
513 let extension = extname(attributes.fixture)
514 // Transcoding enabled on server 2, extension will always be .mp4
b64c950a 515 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
b1f5b93e 516
a20399c9
C
517 const magnetUri = file.magnetUri
518 expect(file.magnetUri).to.have.lengthOf.above(2)
5d00a3d7
C
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}`)
09700934
C
521 expect(file.resolution.id).to.equal(attributeFile.resolution)
522 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
b1f5b93e
C
523
524 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
525 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
4176e227 526 expect(file.size,
7160878c 527 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')')
4176e227 528 .to.be.above(minSize).and.below(maxSize)
a20399c9 529
ac81d1a0 530 {
7b0956ec 531 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
ac81d1a0
C
532 }
533
534 if (attributes.previewfile) {
7b0956ec 535 await testImage(url, attributes.previewfile, videoDetails.previewPath)
ac81d1a0 536 }
a20399c9
C
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
0e1dc3e7
C
545// ---------------------------------------------------------------------------
546
547export {
9567011b 548 getVideoDescription,
0e1dc3e7
C
549 getVideoCategories,
550 getVideoLicences,
11474c3c 551 getVideoPrivacies,
0e1dc3e7 552 getVideoLanguages,
11474c3c 553 getMyVideos,
6b738c7a
C
554 getAccountVideos,
555 getVideoChannelVideos,
0e1dc3e7 556 getVideo,
11474c3c 557 getVideoWithToken,
0e1dc3e7
C
558 getVideosList,
559 getVideosListPagination,
560 getVideosListSort,
561 removeVideo,
0883b324 562 getVideosListWithToken,
0e1dc3e7 563 uploadVideo,
d525fc39 564 getVideosWithFilters,
0e1dc3e7 565 updateVideo,
fdbda9e3 566 rateVideo,
1f3e9fec 567 viewVideo,
a20399c9 568 parseTorrentVideo,
066e94c5 569 getLocalVideos,
f05a1c30
C
570 completeVideoCheck,
571 checkVideoFilesWereRemoved
0e1dc3e7 572}