]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - shared/extra-utils/videos/videos.ts
Introduce history command
[github/Chocobozzz/PeerTube.git] / shared / extra-utils / videos / videos.ts
... / ...
CommitLineData
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { createReadStream, pathExists, readdir, readFile, stat } from 'fs-extra'
5import got, { Response as GotResponse } from 'got/dist/source'
6import * as parseTorrent from 'parse-torrent'
7import { join } from 'path'
8import * as request from 'supertest'
9import validator from 'validator'
10import { getLowercaseExtension } from '@server/helpers/core-utils'
11import { buildUUID } from '@server/helpers/uuid'
12import { HttpStatusCode } from '@shared/core-utils'
13import { BooleanBothQuery, VideosCommonQuery } from '@shared/models'
14import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
15import { VideoDetails, VideoPrivacy } from '../../models/videos'
16import {
17 buildAbsoluteFixturePath,
18 buildServerDirectory,
19 dateIsValid,
20 immutableAssign,
21 testImage,
22 wait,
23 webtorrentAdd
24} from '../miscs/miscs'
25import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests'
26import { waitJobs } from '../server/jobs'
27import { ServerInfo } from '../server/servers'
28import { getMyUserInformation } from '../users/users'
29
30loadLanguages()
31
32type VideoAttributes = {
33 name?: string
34 category?: number
35 licence?: number
36 language?: string
37 nsfw?: boolean
38 commentsEnabled?: boolean
39 downloadEnabled?: boolean
40 waitTranscoding?: boolean
41 description?: string
42 originallyPublishedAt?: string
43 tags?: string[]
44 channelId?: number
45 privacy?: VideoPrivacy
46 fixture?: string
47 support?: string
48 thumbnailfile?: string
49 previewfile?: string
50 scheduleUpdate?: {
51 updateAt: string
52 privacy?: VideoPrivacy
53 }
54}
55
56function getVideoCategories (url: string) {
57 const path = '/api/v1/videos/categories'
58
59 return makeGetRequest({
60 url,
61 path,
62 statusCodeExpected: HttpStatusCode.OK_200
63 })
64}
65
66function getVideoLicences (url: string) {
67 const path = '/api/v1/videos/licences'
68
69 return makeGetRequest({
70 url,
71 path,
72 statusCodeExpected: HttpStatusCode.OK_200
73 })
74}
75
76function getVideoLanguages (url: string) {
77 const path = '/api/v1/videos/languages'
78
79 return makeGetRequest({
80 url,
81 path,
82 statusCodeExpected: HttpStatusCode.OK_200
83 })
84}
85
86function getVideoPrivacies (url: string) {
87 const path = '/api/v1/videos/privacies'
88
89 return makeGetRequest({
90 url,
91 path,
92 statusCodeExpected: HttpStatusCode.OK_200
93 })
94}
95
96function getVideo (url: string, id: number | string, expectedStatus = HttpStatusCode.OK_200) {
97 const path = '/api/v1/videos/' + id
98
99 return request(url)
100 .get(path)
101 .set('Accept', 'application/json')
102 .expect(expectedStatus)
103}
104
105async function getVideoIdFromUUID (url: string, uuid: string) {
106 const res = await getVideo(url, uuid)
107
108 return res.body.id
109}
110
111function getVideoFileMetadataUrl (url: string) {
112 return request(url)
113 .get('/')
114 .set('Accept', 'application/json')
115 .expect(HttpStatusCode.OK_200)
116 .expect('Content-Type', /json/)
117}
118
119function viewVideo (url: string, id: number | string, expectedStatus = HttpStatusCode.NO_CONTENT_204, xForwardedFor?: string) {
120 const path = '/api/v1/videos/' + id + '/views'
121
122 const req = request(url)
123 .post(path)
124 .set('Accept', 'application/json')
125
126 if (xForwardedFor) {
127 req.set('X-Forwarded-For', xForwardedFor)
128 }
129
130 return req.expect(expectedStatus)
131}
132
133function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = HttpStatusCode.OK_200) {
134 const path = '/api/v1/videos/' + id
135
136 return request(url)
137 .get(path)
138 .set('Authorization', 'Bearer ' + token)
139 .set('Accept', 'application/json')
140 .expect(expectedStatus)
141}
142
143function getVideoDescription (url: string, descriptionPath: string) {
144 return request(url)
145 .get(descriptionPath)
146 .set('Accept', 'application/json')
147 .expect(HttpStatusCode.OK_200)
148 .expect('Content-Type', /json/)
149}
150
151function getVideosList (url: string) {
152 const path = '/api/v1/videos'
153
154 return request(url)
155 .get(path)
156 .query({ sort: 'name' })
157 .set('Accept', 'application/json')
158 .expect(HttpStatusCode.OK_200)
159 .expect('Content-Type', /json/)
160}
161
162function getVideosListWithToken (url: string, token: string, query: { nsfw?: BooleanBothQuery } = {}) {
163 const path = '/api/v1/videos'
164
165 return request(url)
166 .get(path)
167 .set('Authorization', 'Bearer ' + token)
168 .query(immutableAssign(query, { sort: 'name' }))
169 .set('Accept', 'application/json')
170 .expect(HttpStatusCode.OK_200)
171 .expect('Content-Type', /json/)
172}
173
174function getLocalVideos (url: string) {
175 const path = '/api/v1/videos'
176
177 return request(url)
178 .get(path)
179 .query({ sort: 'name', filter: 'local' })
180 .set('Accept', 'application/json')
181 .expect(HttpStatusCode.OK_200)
182 .expect('Content-Type', /json/)
183}
184
185function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string, search?: string) {
186 const path = '/api/v1/users/me/videos'
187
188 const req = request(url)
189 .get(path)
190 .query({ start: start })
191 .query({ count: count })
192 .query({ search: search })
193
194 if (sort) req.query({ sort })
195
196 return req.set('Accept', 'application/json')
197 .set('Authorization', 'Bearer ' + accessToken)
198 .expect(HttpStatusCode.OK_200)
199 .expect('Content-Type', /json/)
200}
201
202function getMyVideosWithFilter (url: string, accessToken: string, query: { isLive?: boolean }) {
203 const path = '/api/v1/users/me/videos'
204
205 return makeGetRequest({
206 url,
207 path,
208 token: accessToken,
209 query,
210 statusCodeExpected: HttpStatusCode.OK_200
211 })
212}
213
214function getAccountVideos (
215 url: string,
216 accessToken: string,
217 accountName: string,
218 start: number,
219 count: number,
220 sort?: string,
221 query: {
222 nsfw?: BooleanBothQuery
223 search?: string
224 } = {}
225) {
226 const path = '/api/v1/accounts/' + accountName + '/videos'
227
228 return makeGetRequest({
229 url,
230 path,
231 query: immutableAssign(query, {
232 start,
233 count,
234 sort
235 }),
236 token: accessToken,
237 statusCodeExpected: HttpStatusCode.OK_200
238 })
239}
240
241function getVideoChannelVideos (
242 url: string,
243 accessToken: string,
244 videoChannelName: string,
245 start: number,
246 count: number,
247 sort?: string,
248 query: { nsfw?: BooleanBothQuery } = {}
249) {
250 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
251
252 return makeGetRequest({
253 url,
254 path,
255 query: immutableAssign(query, {
256 start,
257 count,
258 sort
259 }),
260 token: accessToken,
261 statusCodeExpected: HttpStatusCode.OK_200
262 })
263}
264
265function getVideosListPagination (url: string, start: number, count: number, sort?: string, skipCount?: boolean) {
266 const path = '/api/v1/videos'
267
268 const req = request(url)
269 .get(path)
270 .query({ start: start })
271 .query({ count: count })
272
273 if (sort) req.query({ sort })
274 if (skipCount) req.query({ skipCount })
275
276 return req.set('Accept', 'application/json')
277 .expect(HttpStatusCode.OK_200)
278 .expect('Content-Type', /json/)
279}
280
281function getVideosListSort (url: string, sort: string) {
282 const path = '/api/v1/videos'
283
284 return request(url)
285 .get(path)
286 .query({ sort: sort })
287 .set('Accept', 'application/json')
288 .expect(HttpStatusCode.OK_200)
289 .expect('Content-Type', /json/)
290}
291
292function getVideosWithFilters (url: string, query: VideosCommonQuery) {
293 const path = '/api/v1/videos'
294
295 return request(url)
296 .get(path)
297 .query(query)
298 .set('Accept', 'application/json')
299 .expect(HttpStatusCode.OK_200)
300 .expect('Content-Type', /json/)
301}
302
303function removeVideo (url: string, token: string, id: number | string, expectedStatus = HttpStatusCode.NO_CONTENT_204) {
304 const path = '/api/v1/videos'
305
306 return request(url)
307 .delete(path + '/' + id)
308 .set('Accept', 'application/json')
309 .set('Authorization', 'Bearer ' + token)
310 .expect(expectedStatus)
311}
312
313async function removeAllVideos (server: ServerInfo) {
314 const resVideos = await getVideosList(server.url)
315
316 for (const v of resVideos.body.data) {
317 await removeVideo(server.url, server.accessToken, v.id)
318 }
319}
320
321async function checkVideoFilesWereRemoved (
322 videoUUID: string,
323 serverNumber: number,
324 directories = [
325 'redundancy',
326 'videos',
327 'thumbnails',
328 'torrents',
329 'previews',
330 'captions',
331 join('playlists', 'hls'),
332 join('redundancy', 'hls')
333 ]
334) {
335 for (const directory of directories) {
336 const directoryPath = buildServerDirectory({ internalServerNumber: serverNumber }, directory)
337
338 const directoryExists = await pathExists(directoryPath)
339 if (directoryExists === false) continue
340
341 const files = await readdir(directoryPath)
342 for (const file of files) {
343 expect(file, `File ${file} should not exist in ${directoryPath}`).to.not.contain(videoUUID)
344 }
345 }
346}
347
348async function uploadVideo (
349 url: string,
350 accessToken: string,
351 videoAttributesArg: VideoAttributes,
352 specialStatus = HttpStatusCode.OK_200,
353 mode: 'legacy' | 'resumable' = 'legacy'
354) {
355 let defaultChannelId = '1'
356
357 try {
358 const res = await getMyUserInformation(url, accessToken)
359 defaultChannelId = res.body.videoChannels[0].id
360 } catch (e) { /* empty */ }
361
362 // Override default attributes
363 const attributes = Object.assign({
364 name: 'my super video',
365 category: 5,
366 licence: 4,
367 language: 'zh',
368 channelId: defaultChannelId,
369 nsfw: true,
370 waitTranscoding: false,
371 description: 'my super description',
372 support: 'my super support text',
373 tags: [ 'tag' ],
374 privacy: VideoPrivacy.PUBLIC,
375 commentsEnabled: true,
376 downloadEnabled: true,
377 fixture: 'video_short.webm'
378 }, videoAttributesArg)
379
380 const res = mode === 'legacy'
381 ? await buildLegacyUpload(url, accessToken, attributes, specialStatus)
382 : await buildResumeUpload(url, accessToken, attributes, specialStatus)
383
384 // Wait torrent generation
385 if (specialStatus === HttpStatusCode.OK_200) {
386 let video: VideoDetails
387 do {
388 const resVideo = await getVideoWithToken(url, accessToken, res.body.video.uuid)
389 video = resVideo.body
390
391 await wait(50)
392 } while (!video.files[0].torrentUrl)
393 }
394
395 return res
396}
397
398function checkUploadVideoParam (
399 url: string,
400 token: string,
401 attributes: Partial<VideoAttributes>,
402 specialStatus = HttpStatusCode.OK_200,
403 mode: 'legacy' | 'resumable' = 'legacy'
404) {
405 return mode === 'legacy'
406 ? buildLegacyUpload(url, token, attributes, specialStatus)
407 : buildResumeUpload(url, token, attributes, specialStatus)
408}
409
410async function buildLegacyUpload (url: string, token: string, attributes: VideoAttributes, specialStatus = HttpStatusCode.OK_200) {
411 const path = '/api/v1/videos/upload'
412 const req = request(url)
413 .post(path)
414 .set('Accept', 'application/json')
415 .set('Authorization', 'Bearer ' + token)
416
417 buildUploadReq(req, attributes)
418
419 if (attributes.fixture !== undefined) {
420 req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
421 }
422
423 return req.expect(specialStatus)
424}
425
426async function buildResumeUpload (url: string, token: string, attributes: VideoAttributes, specialStatus = HttpStatusCode.OK_200) {
427 let size = 0
428 let videoFilePath: string
429 let mimetype = 'video/mp4'
430
431 if (attributes.fixture) {
432 videoFilePath = buildAbsoluteFixturePath(attributes.fixture)
433 size = (await stat(videoFilePath)).size
434
435 if (videoFilePath.endsWith('.mkv')) {
436 mimetype = 'video/x-matroska'
437 } else if (videoFilePath.endsWith('.webm')) {
438 mimetype = 'video/webm'
439 }
440 }
441
442 const initializeSessionRes = await prepareResumableUpload({ url, token, attributes, size, mimetype })
443 const initStatus = initializeSessionRes.status
444
445 if (videoFilePath && initStatus === HttpStatusCode.CREATED_201) {
446 const locationHeader = initializeSessionRes.header['location']
447 expect(locationHeader).to.not.be.undefined
448
449 const pathUploadId = locationHeader.split('?')[1]
450
451 return sendResumableChunks({ url, token, pathUploadId, videoFilePath, size, specialStatus })
452 }
453
454 const expectedInitStatus = specialStatus === HttpStatusCode.OK_200
455 ? HttpStatusCode.CREATED_201
456 : specialStatus
457
458 expect(initStatus).to.equal(expectedInitStatus)
459
460 return initializeSessionRes
461}
462
463async function prepareResumableUpload (options: {
464 url: string
465 token: string
466 attributes: VideoAttributes
467 size: number
468 mimetype: string
469}) {
470 const { url, token, attributes, size, mimetype } = options
471
472 const path = '/api/v1/videos/upload-resumable'
473
474 const req = request(url)
475 .post(path)
476 .set('Authorization', 'Bearer ' + token)
477 .set('X-Upload-Content-Type', mimetype)
478 .set('X-Upload-Content-Length', size.toString())
479
480 buildUploadReq(req, attributes)
481
482 if (attributes.fixture) {
483 req.field('filename', attributes.fixture)
484 }
485
486 return req
487}
488
489function sendResumableChunks (options: {
490 url: string
491 token: string
492 pathUploadId: string
493 videoFilePath: string
494 size: number
495 specialStatus?: HttpStatusCode
496 contentLength?: number
497 contentRangeBuilder?: (start: number, chunk: any) => string
498}) {
499 const { url, token, pathUploadId, videoFilePath, size, specialStatus, contentLength, contentRangeBuilder } = options
500
501 const expectedStatus = specialStatus || HttpStatusCode.OK_200
502
503 const path = '/api/v1/videos/upload-resumable'
504 let start = 0
505
506 const readable = createReadStream(videoFilePath, { highWaterMark: 8 * 1024 })
507 return new Promise<GotResponse>((resolve, reject) => {
508 readable.on('data', async function onData (chunk) {
509 readable.pause()
510
511 const headers = {
512 'Authorization': 'Bearer ' + token,
513 'Content-Type': 'application/octet-stream',
514 'Content-Range': contentRangeBuilder
515 ? contentRangeBuilder(start, chunk)
516 : `bytes ${start}-${start + chunk.length - 1}/${size}`,
517 'Content-Length': contentLength ? contentLength + '' : chunk.length + ''
518 }
519
520 const res = await got({
521 url,
522 method: 'put',
523 headers,
524 path: path + '?' + pathUploadId,
525 body: chunk,
526 responseType: 'json',
527 throwHttpErrors: false
528 })
529
530 start += chunk.length
531
532 if (res.statusCode === expectedStatus) {
533 return resolve(res)
534 }
535
536 if (res.statusCode !== HttpStatusCode.PERMANENT_REDIRECT_308) {
537 readable.off('data', onData)
538 return reject(new Error('Incorrect transient behaviour sending intermediary chunks'))
539 }
540
541 readable.resume()
542 })
543 })
544}
545
546function updateVideo (
547 url: string,
548 accessToken: string,
549 id: number | string,
550 attributes: VideoAttributes,
551 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
552) {
553 const path = '/api/v1/videos/' + id
554 const body = {}
555
556 if (attributes.name) body['name'] = attributes.name
557 if (attributes.category) body['category'] = attributes.category
558 if (attributes.licence) body['licence'] = attributes.licence
559 if (attributes.language) body['language'] = attributes.language
560 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
561 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
562 if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled)
563 if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt
564 if (attributes.description) body['description'] = attributes.description
565 if (attributes.tags) body['tags'] = attributes.tags
566 if (attributes.privacy) body['privacy'] = attributes.privacy
567 if (attributes.channelId) body['channelId'] = attributes.channelId
568 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
569
570 // Upload request
571 if (attributes.thumbnailfile || attributes.previewfile) {
572 const attaches: any = {}
573 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
574 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
575
576 return makeUploadRequest({
577 url,
578 method: 'PUT',
579 path,
580 token: accessToken,
581 fields: body,
582 attaches,
583 statusCodeExpected
584 })
585 }
586
587 return makePutBodyRequest({
588 url,
589 path,
590 fields: body,
591 token: accessToken,
592 statusCodeExpected
593 })
594}
595
596function rateVideo (url: string, accessToken: string, id: number | string, rating: string, specialStatus = HttpStatusCode.NO_CONTENT_204) {
597 const path = '/api/v1/videos/' + id + '/rate'
598
599 return request(url)
600 .put(path)
601 .set('Accept', 'application/json')
602 .set('Authorization', 'Bearer ' + accessToken)
603 .send({ rating })
604 .expect(specialStatus)
605}
606
607function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
608 return new Promise<any>((res, rej) => {
609 const torrentName = videoUUID + '-' + resolution + '.torrent'
610 const torrentPath = buildServerDirectory(server, join('torrents', torrentName))
611
612 readFile(torrentPath, (err, data) => {
613 if (err) return rej(err)
614
615 return res(parseTorrent(data))
616 })
617 })
618}
619
620async function completeVideoCheck (
621 url: string,
622 video: any,
623 attributes: {
624 name: string
625 category: number
626 licence: number
627 language: string
628 nsfw: boolean
629 commentsEnabled: boolean
630 downloadEnabled: boolean
631 description: string
632 publishedAt?: string
633 support: string
634 originallyPublishedAt?: string
635 account: {
636 name: string
637 host: string
638 }
639 isLocal: boolean
640 tags: string[]
641 privacy: number
642 likes?: number
643 dislikes?: number
644 duration: number
645 channel: {
646 displayName: string
647 name: string
648 description
649 isLocal: boolean
650 }
651 fixture: string
652 files: {
653 resolution: number
654 size: number
655 }[]
656 thumbnailfile?: string
657 previewfile?: string
658 }
659) {
660 if (!attributes.likes) attributes.likes = 0
661 if (!attributes.dislikes) attributes.dislikes = 0
662
663 const host = new URL(url).host
664 const originHost = attributes.account.host
665
666 expect(video.name).to.equal(attributes.name)
667 expect(video.category.id).to.equal(attributes.category)
668 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
669 expect(video.licence.id).to.equal(attributes.licence)
670 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
671 expect(video.language.id).to.equal(attributes.language)
672 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
673 expect(video.privacy.id).to.deep.equal(attributes.privacy)
674 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
675 expect(video.nsfw).to.equal(attributes.nsfw)
676 expect(video.description).to.equal(attributes.description)
677 expect(video.account.id).to.be.a('number')
678 expect(video.account.host).to.equal(attributes.account.host)
679 expect(video.account.name).to.equal(attributes.account.name)
680 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
681 expect(video.channel.name).to.equal(attributes.channel.name)
682 expect(video.likes).to.equal(attributes.likes)
683 expect(video.dislikes).to.equal(attributes.dislikes)
684 expect(video.isLocal).to.equal(attributes.isLocal)
685 expect(video.duration).to.equal(attributes.duration)
686 expect(dateIsValid(video.createdAt)).to.be.true
687 expect(dateIsValid(video.publishedAt)).to.be.true
688 expect(dateIsValid(video.updatedAt)).to.be.true
689
690 if (attributes.publishedAt) {
691 expect(video.publishedAt).to.equal(attributes.publishedAt)
692 }
693
694 if (attributes.originallyPublishedAt) {
695 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
696 } else {
697 expect(video.originallyPublishedAt).to.be.null
698 }
699
700 const res = await getVideo(url, video.uuid)
701 const videoDetails: VideoDetails = res.body
702
703 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
704 expect(videoDetails.tags).to.deep.equal(attributes.tags)
705 expect(videoDetails.account.name).to.equal(attributes.account.name)
706 expect(videoDetails.account.host).to.equal(attributes.account.host)
707 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
708 expect(video.channel.name).to.equal(attributes.channel.name)
709 expect(videoDetails.channel.host).to.equal(attributes.account.host)
710 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
711 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
712 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
713 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
714 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
715
716 for (const attributeFile of attributes.files) {
717 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
718 expect(file).not.to.be.undefined
719
720 let extension = getLowercaseExtension(attributes.fixture)
721 // Transcoding enabled: extension will always be .mp4
722 if (attributes.files.length > 1) extension = '.mp4'
723
724 expect(file.magnetUri).to.have.lengthOf.above(2)
725
726 expect(file.torrentDownloadUrl).to.equal(`http://${host}/download/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
727 expect(file.torrentUrl).to.equal(`http://${host}/lazy-static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
728
729 expect(file.fileUrl).to.equal(`http://${originHost}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
730 expect(file.fileDownloadUrl).to.equal(`http://${originHost}/download/videos/${videoDetails.uuid}-${file.resolution.id}${extension}`)
731
732 await Promise.all([
733 makeRawRequest(file.torrentUrl, 200),
734 makeRawRequest(file.torrentDownloadUrl, 200),
735 makeRawRequest(file.metadataUrl, 200),
736 // Backward compatibility
737 makeRawRequest(`http://${originHost}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`, 200)
738 ])
739
740 expect(file.resolution.id).to.equal(attributeFile.resolution)
741 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
742
743 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
744 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
745 expect(
746 file.size,
747 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
748 ).to.be.above(minSize).and.below(maxSize)
749
750 const torrent = await webtorrentAdd(file.magnetUri, true)
751 expect(torrent.files).to.be.an('array')
752 expect(torrent.files.length).to.equal(1)
753 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
754 }
755
756 expect(videoDetails.thumbnailPath).to.exist
757 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
758
759 if (attributes.previewfile) {
760 expect(videoDetails.previewPath).to.exist
761 await testImage(url, attributes.previewfile, videoDetails.previewPath)
762 }
763}
764
765async function videoUUIDToId (url: string, id: number | string) {
766 if (validator.isUUID('' + id) === false) return id
767
768 const res = await getVideo(url, id)
769 return res.body.id
770}
771
772async function uploadVideoAndGetId (options: {
773 server: ServerInfo
774 videoName: string
775 nsfw?: boolean
776 privacy?: VideoPrivacy
777 token?: string
778 fixture?: string
779}) {
780 const videoAttrs: any = { name: options.videoName }
781 if (options.nsfw) videoAttrs.nsfw = options.nsfw
782 if (options.privacy) videoAttrs.privacy = options.privacy
783 if (options.fixture) videoAttrs.fixture = options.fixture
784
785 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
786
787 return res.body.video as { id: number, uuid: string, shortUUID: string }
788}
789
790async function getLocalIdByUUID (url: string, uuid: string) {
791 const res = await getVideo(url, uuid)
792
793 return res.body.id
794}
795
796// serverNumber starts from 1
797async function uploadRandomVideoOnServers (servers: ServerInfo[], serverNumber: number, additionalParams: any = {}) {
798 const server = servers.find(s => s.serverNumber === serverNumber)
799 const res = await uploadRandomVideo(server, false, additionalParams)
800
801 await waitJobs(servers)
802
803 return res
804}
805
806async function uploadRandomVideo (server: ServerInfo, wait = true, additionalParams: any = {}) {
807 const prefixName = additionalParams.prefixName || ''
808 const name = prefixName + buildUUID()
809
810 const data = Object.assign({ name }, additionalParams)
811 const res = await uploadVideo(server.url, server.accessToken, data)
812
813 if (wait) await waitJobs([ server ])
814
815 return { uuid: res.body.video.uuid, name }
816}
817
818// ---------------------------------------------------------------------------
819
820export {
821 getVideoDescription,
822 getVideoCategories,
823 uploadRandomVideo,
824 getVideoLicences,
825 videoUUIDToId,
826 getVideoPrivacies,
827 getVideoLanguages,
828 getMyVideos,
829 getAccountVideos,
830 getVideoChannelVideos,
831 getVideo,
832 getVideoFileMetadataUrl,
833 getVideoWithToken,
834 getVideosList,
835 removeAllVideos,
836 checkUploadVideoParam,
837 getVideosListPagination,
838 getVideosListSort,
839 removeVideo,
840 getVideosListWithToken,
841 uploadVideo,
842 sendResumableChunks,
843 getVideosWithFilters,
844 uploadRandomVideoOnServers,
845 updateVideo,
846 rateVideo,
847 viewVideo,
848 parseTorrentVideo,
849 getLocalVideos,
850 completeVideoCheck,
851 checkVideoFilesWereRemoved,
852 getMyVideosWithFilter,
853 uploadVideoAndGetId,
854 getLocalIdByUUID,
855 getVideoIdFromUUID,
856 prepareResumableUpload
857}
858
859// ---------------------------------------------------------------------------
860
861function buildUploadReq (req: request.Test, attributes: VideoAttributes) {
862
863 for (const key of [ 'name', 'support', 'channelId', 'description', 'originallyPublishedAt' ]) {
864 if (attributes[key] !== undefined) {
865 req.field(key, attributes[key])
866 }
867 }
868
869 for (const key of [ 'nsfw', 'commentsEnabled', 'downloadEnabled', 'waitTranscoding' ]) {
870 if (attributes[key] !== undefined) {
871 req.field(key, JSON.stringify(attributes[key]))
872 }
873 }
874
875 for (const key of [ 'language', 'privacy', 'category', 'licence' ]) {
876 if (attributes[key] !== undefined) {
877 req.field(key, attributes[key].toString())
878 }
879 }
880
881 const tags = attributes.tags || []
882 for (let i = 0; i < tags.length; i++) {
883 req.field('tags[' + i + ']', attributes.tags[i])
884 }
885
886 for (const key of [ 'thumbnailfile', 'previewfile' ]) {
887 if (attributes[key] !== undefined) {
888 req.attach(key, buildAbsoluteFixturePath(attributes[key]))
889 }
890 }
891
892 if (attributes.scheduleUpdate) {
893 if (attributes.scheduleUpdate.updateAt) {
894 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
895 }
896
897 if (attributes.scheduleUpdate.privacy) {
898 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
899 }
900 }
901}