]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/shared/videos.ts
Add runner server tests
[github/Chocobozzz/PeerTube.git] / server / tests / shared / videos.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3 import { expect } from 'chai'
4 import { pathExists, readdir } from 'fs-extra'
5 import { basename, join } from 'path'
6 import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '@server/initializers/constants'
7 import { getLowercaseExtension, pick, uuidRegex } from '@shared/core-utils'
8 import { HttpStatusCode, VideoCaption, VideoDetails, VideoPrivacy, VideoResolution } from '@shared/models'
9 import { makeRawRequest, PeerTubeServer, VideoEdit, waitJobs } from '@shared/server-commands'
10 import { dateIsValid, expectStartWith, testImage } from './checks'
11 import { checkWebTorrentWorks } from './webtorrent'
12
13 loadLanguages()
14
15 async function completeWebVideoFilesCheck (options: {
16 server: PeerTubeServer
17 originServer: PeerTubeServer
18 videoUUID: string
19 fixture: string
20 files: {
21 resolution: number
22 size?: number
23 }[]
24 objectStorageBaseUrl?: string
25 }) {
26 const { originServer, server, videoUUID, files, fixture, objectStorageBaseUrl } = options
27 const video = await server.videos.getWithToken({ id: videoUUID })
28 const serverConfig = await originServer.config.getConfig()
29 const requiresAuth = video.privacy.id === VideoPrivacy.PRIVATE || video.privacy.id === VideoPrivacy.INTERNAL
30
31 const transcodingEnabled = serverConfig.transcoding.webtorrent.enabled
32
33 for (const attributeFile of files) {
34 const file = video.files.find(f => f.resolution.id === attributeFile.resolution)
35 expect(file, `resolution ${attributeFile.resolution} does not exist`).not.to.be.undefined
36
37 let extension = getLowercaseExtension(fixture)
38 // Transcoding enabled: extension will always be .mp4
39 if (transcodingEnabled) extension = '.mp4'
40
41 expect(file.id).to.exist
42 expect(file.magnetUri).to.have.lengthOf.above(2)
43
44 {
45 const privatePath = requiresAuth
46 ? 'private/'
47 : ''
48 const nameReg = `${uuidRegex}-${file.resolution.id}`
49
50 expect(file.torrentDownloadUrl).to.match(new RegExp(`${server.url}/download/torrents/${nameReg}.torrent`))
51 expect(file.torrentUrl).to.match(new RegExp(`${server.url}/lazy-static/torrents/${nameReg}.torrent`))
52
53 if (objectStorageBaseUrl && requiresAuth) {
54 expect(file.fileUrl).to.match(new RegExp(`${originServer.url}/object-storage-proxy/webseed/${privatePath}${nameReg}${extension}`))
55 } else if (objectStorageBaseUrl) {
56 expectStartWith(file.fileUrl, objectStorageBaseUrl)
57 } else {
58 expect(file.fileUrl).to.match(new RegExp(`${originServer.url}/static/webseed/${privatePath}${nameReg}${extension}`))
59 }
60
61 expect(file.fileDownloadUrl).to.match(new RegExp(`${originServer.url}/download/videos/${nameReg}${extension}`))
62 }
63
64 {
65 const token = requiresAuth
66 ? server.accessToken
67 : undefined
68
69 await Promise.all([
70 makeRawRequest({ url: file.torrentUrl, token, expectedStatus: HttpStatusCode.OK_200 }),
71 makeRawRequest({ url: file.torrentDownloadUrl, token, expectedStatus: HttpStatusCode.OK_200 }),
72 makeRawRequest({ url: file.metadataUrl, token, expectedStatus: HttpStatusCode.OK_200 }),
73 makeRawRequest({ url: file.fileUrl, token, expectedStatus: HttpStatusCode.OK_200 }),
74 makeRawRequest({
75 url: file.fileDownloadUrl,
76 token,
77 expectedStatus: objectStorageBaseUrl ? HttpStatusCode.FOUND_302 : HttpStatusCode.OK_200
78 })
79 ])
80 }
81
82 expect(file.resolution.id).to.equal(attributeFile.resolution)
83
84 if (file.resolution.id === VideoResolution.H_NOVIDEO) {
85 expect(file.resolution.label).to.equal('Audio')
86 } else {
87 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
88 }
89
90 if (attributeFile.size) {
91 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
92 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
93 expect(
94 file.size,
95 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
96 ).to.be.above(minSize).and.below(maxSize)
97 }
98
99 await checkWebTorrentWorks(file.magnetUri)
100 }
101 }
102
103 async function completeVideoCheck (options: {
104 server: PeerTubeServer
105 originServer: PeerTubeServer
106 videoUUID: string
107 attributes: {
108 name: string
109 category: number
110 licence: number
111 language: string
112 nsfw: boolean
113 commentsEnabled: boolean
114 downloadEnabled: boolean
115 description: string
116 publishedAt?: string
117 support: string
118 originallyPublishedAt?: string
119 account: {
120 name: string
121 host: string
122 }
123 isLocal: boolean
124 tags: string[]
125 privacy: number
126 likes?: number
127 dislikes?: number
128 duration: number
129 channel: {
130 displayName: string
131 name: string
132 description: string
133 isLocal: boolean
134 }
135 fixture: string
136 files: {
137 resolution: number
138 size: number
139 }[]
140 thumbnailfile?: string
141 previewfile?: string
142 }
143 }) {
144 const { attributes, originServer, server, videoUUID } = options
145
146 const video = await server.videos.get({ id: videoUUID })
147
148 if (!attributes.likes) attributes.likes = 0
149 if (!attributes.dislikes) attributes.dislikes = 0
150
151 expect(video.name).to.equal(attributes.name)
152 expect(video.category.id).to.equal(attributes.category)
153 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Unknown')
154 expect(video.licence.id).to.equal(attributes.licence)
155 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
156 expect(video.language.id).to.equal(attributes.language)
157 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
158 expect(video.privacy.id).to.deep.equal(attributes.privacy)
159 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
160 expect(video.nsfw).to.equal(attributes.nsfw)
161 expect(video.description).to.equal(attributes.description)
162 expect(video.account.id).to.be.a('number')
163 expect(video.account.host).to.equal(attributes.account.host)
164 expect(video.account.name).to.equal(attributes.account.name)
165 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
166 expect(video.channel.name).to.equal(attributes.channel.name)
167 expect(video.likes).to.equal(attributes.likes)
168 expect(video.dislikes).to.equal(attributes.dislikes)
169 expect(video.isLocal).to.equal(attributes.isLocal)
170 expect(video.duration).to.equal(attributes.duration)
171 expect(video.url).to.contain(originServer.host)
172 expect(dateIsValid(video.createdAt)).to.be.true
173 expect(dateIsValid(video.publishedAt)).to.be.true
174 expect(dateIsValid(video.updatedAt)).to.be.true
175
176 if (attributes.publishedAt) {
177 expect(video.publishedAt).to.equal(attributes.publishedAt)
178 }
179
180 if (attributes.originallyPublishedAt) {
181 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
182 } else {
183 expect(video.originallyPublishedAt).to.be.null
184 }
185
186 expect(video.files).to.have.lengthOf(attributes.files.length)
187 expect(video.tags).to.deep.equal(attributes.tags)
188 expect(video.account.name).to.equal(attributes.account.name)
189 expect(video.account.host).to.equal(attributes.account.host)
190 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
191 expect(video.channel.name).to.equal(attributes.channel.name)
192 expect(video.channel.host).to.equal(attributes.account.host)
193 expect(video.channel.isLocal).to.equal(attributes.channel.isLocal)
194 expect(dateIsValid(video.channel.createdAt.toString())).to.be.true
195 expect(dateIsValid(video.channel.updatedAt.toString())).to.be.true
196 expect(video.commentsEnabled).to.equal(attributes.commentsEnabled)
197 expect(video.downloadEnabled).to.equal(attributes.downloadEnabled)
198
199 expect(video.thumbnailPath).to.exist
200 await testImage(server.url, attributes.thumbnailfile || attributes.fixture, video.thumbnailPath)
201
202 if (attributes.previewfile) {
203 expect(video.previewPath).to.exist
204 await testImage(server.url, attributes.previewfile, video.previewPath)
205 }
206
207 await completeWebVideoFilesCheck({ server, originServer, videoUUID: video.uuid, ...pick(attributes, [ 'fixture', 'files' ]) })
208 }
209
210 async function checkVideoFilesWereRemoved (options: {
211 server: PeerTubeServer
212 video: VideoDetails
213 captions?: VideoCaption[]
214 onlyVideoFiles?: boolean // default false
215 }) {
216 const { video, server, captions = [], onlyVideoFiles = false } = options
217
218 const webtorrentFiles = video.files || []
219 const hlsFiles = video.streamingPlaylists[0]?.files || []
220
221 const thumbnailName = basename(video.thumbnailPath)
222 const previewName = basename(video.previewPath)
223
224 const torrentNames = webtorrentFiles.concat(hlsFiles).map(f => basename(f.torrentUrl))
225
226 const captionNames = captions.map(c => basename(c.captionPath))
227
228 const webtorrentFilenames = webtorrentFiles.map(f => basename(f.fileUrl))
229 const hlsFilenames = hlsFiles.map(f => basename(f.fileUrl))
230
231 let directories: { [ directory: string ]: string[] } = {
232 videos: webtorrentFilenames,
233 redundancy: webtorrentFilenames,
234 [join('playlists', 'hls')]: hlsFilenames,
235 [join('redundancy', 'hls')]: hlsFilenames
236 }
237
238 if (onlyVideoFiles !== true) {
239 directories = {
240 ...directories,
241
242 thumbnails: [ thumbnailName ],
243 previews: [ previewName ],
244 torrents: torrentNames,
245 captions: captionNames
246 }
247 }
248
249 for (const directory of Object.keys(directories)) {
250 const directoryPath = server.servers.buildDirectory(directory)
251
252 const directoryExists = await pathExists(directoryPath)
253 if (directoryExists === false) continue
254
255 const existingFiles = await readdir(directoryPath)
256 for (const existingFile of existingFiles) {
257 for (const shouldNotExist of directories[directory]) {
258 expect(existingFile, `File ${existingFile} should not exist in ${directoryPath}`).to.not.contain(shouldNotExist)
259 }
260 }
261 }
262 }
263
264 async function saveVideoInServers (servers: PeerTubeServer[], uuid: string) {
265 for (const server of servers) {
266 server.store.videoDetails = await server.videos.get({ id: uuid })
267 }
268 }
269
270 function checkUploadVideoParam (
271 server: PeerTubeServer,
272 token: string,
273 attributes: Partial<VideoEdit>,
274 expectedStatus = HttpStatusCode.OK_200,
275 mode: 'legacy' | 'resumable' = 'legacy'
276 ) {
277 return mode === 'legacy'
278 ? server.videos.buildLegacyUpload({ token, attributes, expectedStatus })
279 : server.videos.buildResumeUpload({ token, attributes, expectedStatus })
280 }
281
282 // serverNumber starts from 1
283 async function uploadRandomVideoOnServers (
284 servers: PeerTubeServer[],
285 serverNumber: number,
286 additionalParams?: VideoEdit & { prefixName?: string }
287 ) {
288 const server = servers.find(s => s.serverNumber === serverNumber)
289 const res = await server.videos.randomUpload({ wait: false, additionalParams })
290
291 await waitJobs(servers)
292
293 return res
294 }
295
296 // ---------------------------------------------------------------------------
297
298 export {
299 completeVideoCheck,
300 completeWebVideoFilesCheck,
301 checkUploadVideoParam,
302 uploadRandomVideoOnServers,
303 checkVideoFilesWereRemoved,
304 saveVideoInServers
305 }