]>
Commit | Line | Data |
---|---|---|
c55e3d72 C |
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ |
2 | ||
06aad801 | 3 | import { expect } from 'chai' |
c55e3d72 C |
4 | import { pathExists, readdir } from 'fs-extra' |
5 | import { basename, join } from 'path' | |
c77fdc60 | 6 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '@server/initializers/constants' |
d102de1b C |
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' | |
06aad801 | 12 | |
c77fdc60 C |
13 | loadLanguages() |
14 | ||
d102de1b C |
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 | |
06aad801 | 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 | } | |
d102de1b C |
143 | }) { |
144 | const { attributes, originServer, server, videoUUID } = options | |
145 | ||
146 | const video = await server.videos.get({ id: videoUUID }) | |
147 | ||
06aad801 | 148 | if (!attributes.likes) attributes.likes = 0 |
149 | if (!attributes.dislikes) attributes.dislikes = 0 | |
150 | ||
06aad801 | 151 | expect(video.name).to.equal(attributes.name) |
152 | expect(video.category.id).to.equal(attributes.category) | |
32fde390 | 153 | expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Unknown') |
06aad801 | 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) | |
d102de1b | 171 | expect(video.url).to.contain(originServer.host) |
06aad801 | 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 | ||
d102de1b C |
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) | |
06aad801 | 190 | expect(video.channel.displayName).to.equal(attributes.channel.displayName) |
191 | expect(video.channel.name).to.equal(attributes.channel.name) | |
d102de1b C |
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) | |
06aad801 | 198 | |
d102de1b C |
199 | expect(video.thumbnailPath).to.exist |
200 | await testImage(server.url, attributes.thumbnailfile || attributes.fixture, video.thumbnailPath) | |
06aad801 | 201 | |
202 | if (attributes.previewfile) { | |
d102de1b C |
203 | expect(video.previewPath).to.exist |
204 | await testImage(server.url, attributes.previewfile, video.previewPath) | |
06aad801 | 205 | } |
d102de1b C |
206 | |
207 | await completeWebVideoFilesCheck({ server, originServer, videoUUID: video.uuid, ...pick(attributes, [ 'fixture', 'files' ]) }) | |
06aad801 | 208 | } |
c55e3d72 C |
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, | |
d102de1b | 300 | completeWebVideoFilesCheck, |
c55e3d72 C |
301 | checkUploadVideoParam, |
302 | uploadRandomVideoOnServers, | |
303 | checkVideoFilesWereRemoved, | |
367a9dc6 | 304 | saveVideoInServers |
c55e3d72 | 305 | } |