diff options
author | Chocobozzz <me@florianbigard.com> | 2023-04-21 15:00:01 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2023-05-09 08:57:34 +0200 |
commit | d102de1b38f2877463529c3b27bd35ffef4fd8bf (patch) | |
tree | 31fa0bdf26ad7a2ee46d600d804a6f03260266c8 /server/tests/shared | |
parent | 2fe978744e5b74eb824e4d79c1bb9b840169f125 (diff) | |
download | PeerTube-d102de1b38f2877463529c3b27bd35ffef4fd8bf.tar.gz PeerTube-d102de1b38f2877463529c3b27bd35ffef4fd8bf.tar.zst PeerTube-d102de1b38f2877463529c3b27bd35ffef4fd8bf.zip |
Add runner server tests
Diffstat (limited to 'server/tests/shared')
-rw-r--r-- | server/tests/shared/checks.ts | 4 | ||||
-rw-r--r-- | server/tests/shared/generate.ts | 2 | ||||
-rw-r--r-- | server/tests/shared/index.ts | 3 | ||||
-rw-r--r-- | server/tests/shared/live.ts | 10 | ||||
-rw-r--r-- | server/tests/shared/peertube-runner-process.ts | 87 | ||||
-rw-r--r-- | server/tests/shared/sql-command.ts | 150 | ||||
-rw-r--r-- | server/tests/shared/streaming-playlists.ts | 134 | ||||
-rw-r--r-- | server/tests/shared/videos.ts | 187 | ||||
-rw-r--r-- | server/tests/shared/webtorrent.ts | 58 |
9 files changed, 520 insertions, 115 deletions
diff --git a/server/tests/shared/checks.ts b/server/tests/shared/checks.ts index c0098b293..d7eb25bb5 100644 --- a/server/tests/shared/checks.ts +++ b/server/tests/shared/checks.ts | |||
@@ -11,7 +11,7 @@ import { HttpStatusCode } from '@shared/models' | |||
11 | import { makeGetRequest, PeerTubeServer } from '@shared/server-commands' | 11 | import { makeGetRequest, PeerTubeServer } from '@shared/server-commands' |
12 | 12 | ||
13 | // Default interval -> 5 minutes | 13 | // Default interval -> 5 minutes |
14 | function dateIsValid (dateString: string, interval = 300000) { | 14 | function dateIsValid (dateString: string | Date, interval = 300000) { |
15 | const dateToCheck = new Date(dateString) | 15 | const dateToCheck = new Date(dateString) |
16 | const now = new Date() | 16 | const now = new Date() |
17 | 17 | ||
@@ -90,6 +90,8 @@ async function testFileExistsOrNot (server: PeerTubeServer, directory: string, f | |||
90 | expect(await pathExists(join(base, filePath))).to.equal(exist) | 90 | expect(await pathExists(join(base, filePath))).to.equal(exist) |
91 | } | 91 | } |
92 | 92 | ||
93 | // --------------------------------------------------------------------------- | ||
94 | |||
93 | function checkBadStartPagination (url: string, path: string, token?: string, query = {}) { | 95 | function checkBadStartPagination (url: string, path: string, token?: string, query = {}) { |
94 | return makeGetRequest({ | 96 | return makeGetRequest({ |
95 | url, | 97 | url, |
diff --git a/server/tests/shared/generate.ts b/server/tests/shared/generate.ts index 9a57084e4..b0c8dba66 100644 --- a/server/tests/shared/generate.ts +++ b/server/tests/shared/generate.ts | |||
@@ -3,7 +3,7 @@ import ffmpeg from 'fluent-ffmpeg' | |||
3 | import { ensureDir, pathExists } from 'fs-extra' | 3 | import { ensureDir, pathExists } from 'fs-extra' |
4 | import { dirname } from 'path' | 4 | import { dirname } from 'path' |
5 | import { buildAbsoluteFixturePath, getMaxBitrate } from '@shared/core-utils' | 5 | import { buildAbsoluteFixturePath, getMaxBitrate } from '@shared/core-utils' |
6 | import { getVideoStreamBitrate, getVideoStreamFPS, getVideoStreamDimensionsInfo } from '@shared/extra-utils' | 6 | import { getVideoStreamBitrate, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@shared/ffmpeg' |
7 | 7 | ||
8 | async function ensureHasTooBigBitrate (fixturePath: string) { | 8 | async function ensureHasTooBigBitrate (fixturePath: string) { |
9 | const bitrate = await getVideoStreamBitrate(fixturePath) | 9 | const bitrate = await getVideoStreamBitrate(fixturePath) |
diff --git a/server/tests/shared/index.ts b/server/tests/shared/index.ts index 963ef8fe6..eda24adb5 100644 --- a/server/tests/shared/index.ts +++ b/server/tests/shared/index.ts | |||
@@ -6,11 +6,14 @@ export * from './directories' | |||
6 | export * from './generate' | 6 | export * from './generate' |
7 | export * from './live' | 7 | export * from './live' |
8 | export * from './notifications' | 8 | export * from './notifications' |
9 | export * from './peertube-runner-process' | ||
9 | export * from './video-playlists' | 10 | export * from './video-playlists' |
10 | export * from './plugins' | 11 | export * from './plugins' |
11 | export * from './requests' | 12 | export * from './requests' |
13 | export * from './sql-command' | ||
12 | export * from './streaming-playlists' | 14 | export * from './streaming-playlists' |
13 | export * from './tests' | 15 | export * from './tests' |
14 | export * from './tracker' | 16 | export * from './tracker' |
15 | export * from './videos' | 17 | export * from './videos' |
16 | export * from './views' | 18 | export * from './views' |
19 | export * from './webtorrent' | ||
diff --git a/server/tests/shared/live.ts b/server/tests/shared/live.ts index ff0b2f226..31f92ef19 100644 --- a/server/tests/shared/live.ts +++ b/server/tests/shared/live.ts | |||
@@ -6,6 +6,7 @@ import { join } from 'path' | |||
6 | import { sha1 } from '@shared/extra-utils' | 6 | import { sha1 } from '@shared/extra-utils' |
7 | import { LiveVideo, VideoStreamingPlaylistType } from '@shared/models' | 7 | import { LiveVideo, VideoStreamingPlaylistType } from '@shared/models' |
8 | import { ObjectStorageCommand, PeerTubeServer } from '@shared/server-commands' | 8 | import { ObjectStorageCommand, PeerTubeServer } from '@shared/server-commands' |
9 | import { SQLCommand } from './sql-command' | ||
9 | import { checkLiveSegmentHash, checkResolutionsInMasterPlaylist } from './streaming-playlists' | 10 | import { checkLiveSegmentHash, checkResolutionsInMasterPlaylist } from './streaming-playlists' |
10 | 11 | ||
11 | async function checkLiveCleanup (options: { | 12 | async function checkLiveCleanup (options: { |
@@ -36,8 +37,10 @@ async function checkLiveCleanup (options: { | |||
36 | 37 | ||
37 | // --------------------------------------------------------------------------- | 38 | // --------------------------------------------------------------------------- |
38 | 39 | ||
39 | async function testVideoResolutions (options: { | 40 | async function testLiveVideoResolutions (options: { |
41 | sqlCommand: SQLCommand | ||
40 | originServer: PeerTubeServer | 42 | originServer: PeerTubeServer |
43 | |||
41 | servers: PeerTubeServer[] | 44 | servers: PeerTubeServer[] |
42 | liveVideoId: string | 45 | liveVideoId: string |
43 | resolutions: number[] | 46 | resolutions: number[] |
@@ -48,6 +51,7 @@ async function testVideoResolutions (options: { | |||
48 | }) { | 51 | }) { |
49 | const { | 52 | const { |
50 | originServer, | 53 | originServer, |
54 | sqlCommand, | ||
51 | servers, | 55 | servers, |
52 | liveVideoId, | 56 | liveVideoId, |
53 | resolutions, | 57 | resolutions, |
@@ -116,7 +120,7 @@ async function testVideoResolutions (options: { | |||
116 | 120 | ||
117 | if (originServer.internalServerNumber === server.internalServerNumber) { | 121 | if (originServer.internalServerNumber === server.internalServerNumber) { |
118 | const infohash = sha1(`${2 + hlsPlaylist.playlistUrl}+V${i}`) | 122 | const infohash = sha1(`${2 + hlsPlaylist.playlistUrl}+V${i}`) |
119 | const dbInfohashes = await originServer.sql.getPlaylistInfohash(hlsPlaylist.id) | 123 | const dbInfohashes = await sqlCommand.getPlaylistInfohash(hlsPlaylist.id) |
120 | 124 | ||
121 | expect(dbInfohashes).to.include(infohash) | 125 | expect(dbInfohashes).to.include(infohash) |
122 | } | 126 | } |
@@ -128,7 +132,7 @@ async function testVideoResolutions (options: { | |||
128 | 132 | ||
129 | export { | 133 | export { |
130 | checkLiveCleanup, | 134 | checkLiveCleanup, |
131 | testVideoResolutions | 135 | testLiveVideoResolutions |
132 | } | 136 | } |
133 | 137 | ||
134 | // --------------------------------------------------------------------------- | 138 | // --------------------------------------------------------------------------- |
diff --git a/server/tests/shared/peertube-runner-process.ts b/server/tests/shared/peertube-runner-process.ts new file mode 100644 index 000000000..84e2dc6df --- /dev/null +++ b/server/tests/shared/peertube-runner-process.ts | |||
@@ -0,0 +1,87 @@ | |||
1 | import { ChildProcess, fork } from 'child_process' | ||
2 | import execa from 'execa' | ||
3 | import { join } from 'path' | ||
4 | import { root } from '@shared/core-utils' | ||
5 | import { PeerTubeServer } from '@shared/server-commands' | ||
6 | |||
7 | export class PeerTubeRunnerProcess { | ||
8 | private app?: ChildProcess | ||
9 | |||
10 | runServer (options: { | ||
11 | hideLogs?: boolean // default true | ||
12 | } = {}) { | ||
13 | const { hideLogs = true } = options | ||
14 | |||
15 | return new Promise<void>((res, rej) => { | ||
16 | const args = [ 'server', '--verbose', '--id', 'test' ] | ||
17 | |||
18 | const forkOptions = { | ||
19 | detached: false, | ||
20 | silent: true | ||
21 | } | ||
22 | this.app = fork(this.getRunnerPath(), args, forkOptions) | ||
23 | |||
24 | this.app.stdout.on('data', data => { | ||
25 | const str = data.toString() as string | ||
26 | |||
27 | if (!hideLogs) { | ||
28 | console.log(str) | ||
29 | } | ||
30 | }) | ||
31 | |||
32 | res() | ||
33 | }) | ||
34 | } | ||
35 | |||
36 | registerPeerTubeInstance (options: { | ||
37 | server: PeerTubeServer | ||
38 | registrationToken: string | ||
39 | runnerName: string | ||
40 | runnerDescription?: string | ||
41 | }) { | ||
42 | const { server, registrationToken, runnerName, runnerDescription } = options | ||
43 | |||
44 | const args = [ | ||
45 | 'register', | ||
46 | '--url', server.url, | ||
47 | '--registration-token', registrationToken, | ||
48 | '--runner-name', runnerName, | ||
49 | '--id', 'test' | ||
50 | ] | ||
51 | |||
52 | if (runnerDescription) { | ||
53 | args.push('--runner-description') | ||
54 | args.push(runnerDescription) | ||
55 | } | ||
56 | |||
57 | return execa.node(this.getRunnerPath(), args) | ||
58 | } | ||
59 | |||
60 | unregisterPeerTubeInstance (options: { | ||
61 | server: PeerTubeServer | ||
62 | }) { | ||
63 | const { server } = options | ||
64 | |||
65 | const args = [ 'unregister', '--url', server.url, '--id', 'test' ] | ||
66 | return execa.node(this.getRunnerPath(), args) | ||
67 | } | ||
68 | |||
69 | async listRegisteredPeerTubeInstances () { | ||
70 | const args = [ 'list-registered', '--id', 'test' ] | ||
71 | const { stdout } = await execa.node(this.getRunnerPath(), args) | ||
72 | |||
73 | return stdout | ||
74 | } | ||
75 | |||
76 | kill () { | ||
77 | if (!this.app) return | ||
78 | |||
79 | process.kill(this.app.pid) | ||
80 | |||
81 | this.app = null | ||
82 | } | ||
83 | |||
84 | private getRunnerPath () { | ||
85 | return join(root(), 'packages', 'peertube-runner', 'dist', 'peertube-runner.js') | ||
86 | } | ||
87 | } | ||
diff --git a/server/tests/shared/sql-command.ts b/server/tests/shared/sql-command.ts new file mode 100644 index 000000000..5c53a8ac6 --- /dev/null +++ b/server/tests/shared/sql-command.ts | |||
@@ -0,0 +1,150 @@ | |||
1 | import { QueryTypes, Sequelize } from 'sequelize' | ||
2 | import { forceNumber } from '@shared/core-utils' | ||
3 | import { PeerTubeServer } from '@shared/server-commands' | ||
4 | |||
5 | export class SQLCommand { | ||
6 | private sequelize: Sequelize | ||
7 | |||
8 | constructor (private readonly server: PeerTubeServer) { | ||
9 | |||
10 | } | ||
11 | |||
12 | deleteAll (table: string) { | ||
13 | const seq = this.getSequelize() | ||
14 | |||
15 | const options = { type: QueryTypes.DELETE } | ||
16 | |||
17 | return seq.query(`DELETE FROM "${table}"`, options) | ||
18 | } | ||
19 | |||
20 | async getVideoShareCount () { | ||
21 | const [ { total } ] = await this.selectQuery<{ total: string }>(`SELECT COUNT(*) as total FROM "videoShare"`) | ||
22 | if (total === null) return 0 | ||
23 | |||
24 | return parseInt(total, 10) | ||
25 | } | ||
26 | |||
27 | async getInternalFileUrl (fileId: number) { | ||
28 | return this.selectQuery<{ fileUrl: string }>(`SELECT "fileUrl" FROM "videoFile" WHERE id = :fileId`, { fileId }) | ||
29 | .then(rows => rows[0].fileUrl) | ||
30 | } | ||
31 | |||
32 | setActorField (to: string, field: string, value: string) { | ||
33 | return this.updateQuery(`UPDATE actor SET ${this.escapeColumnName(field)} = :value WHERE url = :to`, { value, to }) | ||
34 | } | ||
35 | |||
36 | setVideoField (uuid: string, field: string, value: string) { | ||
37 | return this.updateQuery(`UPDATE video SET ${this.escapeColumnName(field)} = :value WHERE uuid = :uuid`, { value, uuid }) | ||
38 | } | ||
39 | |||
40 | setPlaylistField (uuid: string, field: string, value: string) { | ||
41 | return this.updateQuery(`UPDATE "videoPlaylist" SET ${this.escapeColumnName(field)} = :value WHERE uuid = :uuid`, { value, uuid }) | ||
42 | } | ||
43 | |||
44 | async countVideoViewsOf (uuid: string) { | ||
45 | const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' + | ||
46 | `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = :uuid` | ||
47 | |||
48 | const [ { total } ] = await this.selectQuery<{ total: number }>(query, { uuid }) | ||
49 | if (!total) return 0 | ||
50 | |||
51 | return forceNumber(total) | ||
52 | } | ||
53 | |||
54 | getActorImage (filename: string) { | ||
55 | return this.selectQuery<{ width: number, height: number }>(`SELECT * FROM "actorImage" WHERE filename = :filename`, { filename }) | ||
56 | .then(rows => rows[0]) | ||
57 | } | ||
58 | |||
59 | // --------------------------------------------------------------------------- | ||
60 | |||
61 | setPluginVersion (pluginName: string, newVersion: string) { | ||
62 | return this.setPluginField(pluginName, 'version', newVersion) | ||
63 | } | ||
64 | |||
65 | setPluginLatestVersion (pluginName: string, newVersion: string) { | ||
66 | return this.setPluginField(pluginName, 'latestVersion', newVersion) | ||
67 | } | ||
68 | |||
69 | setPluginField (pluginName: string, field: string, value: string) { | ||
70 | return this.updateQuery( | ||
71 | `UPDATE "plugin" SET ${this.escapeColumnName(field)} = :value WHERE "name" = :pluginName`, | ||
72 | { pluginName, value } | ||
73 | ) | ||
74 | } | ||
75 | |||
76 | // --------------------------------------------------------------------------- | ||
77 | |||
78 | selectQuery <T extends object> (query: string, replacements: { [id: string]: string | number } = {}) { | ||
79 | const seq = this.getSequelize() | ||
80 | const options = { | ||
81 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
82 | replacements | ||
83 | } | ||
84 | |||
85 | return seq.query<T>(query, options) | ||
86 | } | ||
87 | |||
88 | updateQuery (query: string, replacements: { [id: string]: string | number } = {}) { | ||
89 | const seq = this.getSequelize() | ||
90 | const options = { type: QueryTypes.UPDATE as QueryTypes.UPDATE, replacements } | ||
91 | |||
92 | return seq.query(query, options) | ||
93 | } | ||
94 | |||
95 | // --------------------------------------------------------------------------- | ||
96 | |||
97 | async getPlaylistInfohash (playlistId: number) { | ||
98 | const query = 'SELECT "p2pMediaLoaderInfohashes" FROM "videoStreamingPlaylist" WHERE id = :playlistId' | ||
99 | |||
100 | const result = await this.selectQuery<{ p2pMediaLoaderInfohashes: string }>(query, { playlistId }) | ||
101 | if (!result || result.length === 0) return [] | ||
102 | |||
103 | return result[0].p2pMediaLoaderInfohashes | ||
104 | } | ||
105 | |||
106 | // --------------------------------------------------------------------------- | ||
107 | |||
108 | setActorFollowScores (newScore: number) { | ||
109 | return this.updateQuery(`UPDATE "actorFollow" SET "score" = :newScore`, { newScore }) | ||
110 | } | ||
111 | |||
112 | setTokenField (accessToken: string, field: string, value: string) { | ||
113 | return this.updateQuery( | ||
114 | `UPDATE "oAuthToken" SET ${this.escapeColumnName(field)} = :value WHERE "accessToken" = :accessToken`, | ||
115 | { value, accessToken } | ||
116 | ) | ||
117 | } | ||
118 | |||
119 | async cleanup () { | ||
120 | if (!this.sequelize) return | ||
121 | |||
122 | await this.sequelize.close() | ||
123 | this.sequelize = undefined | ||
124 | } | ||
125 | |||
126 | private getSequelize () { | ||
127 | if (this.sequelize) return this.sequelize | ||
128 | |||
129 | const dbname = 'peertube_test' + this.server.internalServerNumber | ||
130 | const username = 'peertube' | ||
131 | const password = 'peertube' | ||
132 | const host = '127.0.0.1' | ||
133 | const port = 5432 | ||
134 | |||
135 | this.sequelize = new Sequelize(dbname, username, password, { | ||
136 | dialect: 'postgres', | ||
137 | host, | ||
138 | port, | ||
139 | logging: false | ||
140 | }) | ||
141 | |||
142 | return this.sequelize | ||
143 | } | ||
144 | |||
145 | private escapeColumnName (columnName: string) { | ||
146 | return this.getSequelize().escape(columnName) | ||
147 | .replace(/^'/, '"') | ||
148 | .replace(/'$/, '"') | ||
149 | } | ||
150 | } | ||
diff --git a/server/tests/shared/streaming-playlists.ts b/server/tests/shared/streaming-playlists.ts index 1c38cb512..acfb2b408 100644 --- a/server/tests/shared/streaming-playlists.ts +++ b/server/tests/shared/streaming-playlists.ts | |||
@@ -4,10 +4,11 @@ import { expect } from 'chai' | |||
4 | import { basename, dirname, join } from 'path' | 4 | import { basename, dirname, join } from 'path' |
5 | import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils' | 5 | import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils' |
6 | import { sha256 } from '@shared/extra-utils' | 6 | import { sha256 } from '@shared/extra-utils' |
7 | import { HttpStatusCode, VideoStreamingPlaylist, VideoStreamingPlaylistType } from '@shared/models' | 7 | import { HttpStatusCode, VideoPrivacy, VideoResolution, VideoStreamingPlaylist, VideoStreamingPlaylistType } from '@shared/models' |
8 | import { makeRawRequest, PeerTubeServer, webtorrentAdd } from '@shared/server-commands' | 8 | import { makeRawRequest, PeerTubeServer } from '@shared/server-commands' |
9 | import { expectStartWith } from './checks' | 9 | import { expectStartWith } from './checks' |
10 | import { hlsInfohashExist } from './tracker' | 10 | import { hlsInfohashExist } from './tracker' |
11 | import { checkWebTorrentWorks } from './webtorrent' | ||
11 | 12 | ||
12 | async function checkSegmentHash (options: { | 13 | async function checkSegmentHash (options: { |
13 | server: PeerTubeServer | 14 | server: PeerTubeServer |
@@ -15,14 +16,15 @@ async function checkSegmentHash (options: { | |||
15 | baseUrlSegment: string | 16 | baseUrlSegment: string |
16 | resolution: number | 17 | resolution: number |
17 | hlsPlaylist: VideoStreamingPlaylist | 18 | hlsPlaylist: VideoStreamingPlaylist |
19 | token?: string | ||
18 | }) { | 20 | }) { |
19 | const { server, baseUrlPlaylist, baseUrlSegment, resolution, hlsPlaylist } = options | 21 | const { server, baseUrlPlaylist, baseUrlSegment, resolution, hlsPlaylist, token } = options |
20 | const command = server.streamingPlaylists | 22 | const command = server.streamingPlaylists |
21 | 23 | ||
22 | const file = hlsPlaylist.files.find(f => f.resolution.id === resolution) | 24 | const file = hlsPlaylist.files.find(f => f.resolution.id === resolution) |
23 | const videoName = basename(file.fileUrl) | 25 | const videoName = basename(file.fileUrl) |
24 | 26 | ||
25 | const playlist = await command.get({ url: `${baseUrlPlaylist}/${removeFragmentedMP4Ext(videoName)}.m3u8` }) | 27 | const playlist = await command.get({ url: `${baseUrlPlaylist}/${removeFragmentedMP4Ext(videoName)}.m3u8`, token }) |
26 | 28 | ||
27 | const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) | 29 | const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist) |
28 | 30 | ||
@@ -33,11 +35,12 @@ async function checkSegmentHash (options: { | |||
33 | const segmentBody = await command.getFragmentedSegment({ | 35 | const segmentBody = await command.getFragmentedSegment({ |
34 | url: `${baseUrlSegment}/${videoName}`, | 36 | url: `${baseUrlSegment}/${videoName}`, |
35 | expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206, | 37 | expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206, |
36 | range: `bytes=${range}` | 38 | range: `bytes=${range}`, |
39 | token | ||
37 | }) | 40 | }) |
38 | 41 | ||
39 | const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url }) | 42 | const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url, token }) |
40 | expect(sha256(segmentBody)).to.equal(shaBody[videoName][range]) | 43 | expect(sha256(segmentBody)).to.equal(shaBody[videoName][range], `Invalid sha256 result for ${videoName} range ${range}`) |
41 | } | 44 | } |
42 | 45 | ||
43 | // --------------------------------------------------------------------------- | 46 | // --------------------------------------------------------------------------- |
@@ -64,19 +67,24 @@ async function checkResolutionsInMasterPlaylist (options: { | |||
64 | server: PeerTubeServer | 67 | server: PeerTubeServer |
65 | playlistUrl: string | 68 | playlistUrl: string |
66 | resolutions: number[] | 69 | resolutions: number[] |
70 | token?: string | ||
67 | transcoded?: boolean // default true | 71 | transcoded?: boolean // default true |
68 | withRetry?: boolean // default false | 72 | withRetry?: boolean // default false |
69 | }) { | 73 | }) { |
70 | const { server, playlistUrl, resolutions, withRetry = false, transcoded = true } = options | 74 | const { server, playlistUrl, resolutions, token, withRetry = false, transcoded = true } = options |
71 | 75 | ||
72 | const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl, withRetry }) | 76 | const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl, token, withRetry }) |
73 | 77 | ||
74 | for (const resolution of resolutions) { | 78 | for (const resolution of resolutions) { |
75 | const reg = transcoded | 79 | const base = '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution |
76 | ? new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"') | 80 | |
77 | : new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + '') | 81 | if (resolution === VideoResolution.H_NOVIDEO) { |
78 | 82 | expect(masterPlaylist).to.match(new RegExp(`${base},CODECS="mp4a.40.2"`)) | |
79 | expect(masterPlaylist).to.match(reg) | 83 | } else if (transcoded) { |
84 | expect(masterPlaylist).to.match(new RegExp(`${base},(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"`)) | ||
85 | } else { | ||
86 | expect(masterPlaylist).to.match(new RegExp(`${base}`)) | ||
87 | } | ||
80 | } | 88 | } |
81 | 89 | ||
82 | const playlistsLength = masterPlaylist.split('\n').filter(line => line.startsWith('#EXT-X-STREAM-INF:BANDWIDTH=')) | 90 | const playlistsLength = masterPlaylist.split('\n').filter(line => line.startsWith('#EXT-X-STREAM-INF:BANDWIDTH=')) |
@@ -89,14 +97,23 @@ async function completeCheckHlsPlaylist (options: { | |||
89 | hlsOnly: boolean | 97 | hlsOnly: boolean |
90 | 98 | ||
91 | resolutions?: number[] | 99 | resolutions?: number[] |
92 | objectStorageBaseUrl: string | 100 | objectStorageBaseUrl?: string |
93 | }) { | 101 | }) { |
94 | const { videoUUID, hlsOnly, objectStorageBaseUrl } = options | 102 | const { videoUUID, hlsOnly, objectStorageBaseUrl } = options |
95 | 103 | ||
96 | const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ] | 104 | const resolutions = options.resolutions ?? [ 240, 360, 480, 720 ] |
97 | 105 | ||
98 | for (const server of options.servers) { | 106 | for (const server of options.servers) { |
99 | const videoDetails = await server.videos.get({ id: videoUUID }) | 107 | const videoDetails = await server.videos.getWithToken({ id: videoUUID }) |
108 | const requiresAuth = videoDetails.privacy.id === VideoPrivacy.PRIVATE || videoDetails.privacy.id === VideoPrivacy.INTERNAL | ||
109 | |||
110 | const privatePath = requiresAuth | ||
111 | ? 'private/' | ||
112 | : '' | ||
113 | const token = requiresAuth | ||
114 | ? server.accessToken | ||
115 | : undefined | ||
116 | |||
100 | const baseUrl = `http://${videoDetails.account.host}` | 117 | const baseUrl = `http://${videoDetails.account.host}` |
101 | 118 | ||
102 | expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) | 119 | expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) |
@@ -115,35 +132,55 @@ async function completeCheckHlsPlaylist (options: { | |||
115 | const file = hlsFiles.find(f => f.resolution.id === resolution) | 132 | const file = hlsFiles.find(f => f.resolution.id === resolution) |
116 | expect(file).to.not.be.undefined | 133 | expect(file).to.not.be.undefined |
117 | 134 | ||
118 | expect(file.magnetUri).to.have.lengthOf.above(2) | 135 | if (file.resolution.id === VideoResolution.H_NOVIDEO) { |
119 | expect(file.torrentUrl).to.match( | 136 | expect(file.resolution.label).to.equal('Audio') |
120 | new RegExp(`${server.url}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}-hls.torrent`) | ||
121 | ) | ||
122 | |||
123 | if (objectStorageBaseUrl) { | ||
124 | expectStartWith(file.fileUrl, objectStorageBaseUrl) | ||
125 | } else { | 137 | } else { |
126 | expect(file.fileUrl).to.match( | 138 | expect(file.resolution.label).to.equal(resolution + 'p') |
127 | new RegExp(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${uuidRegex}-${file.resolution.id}-fragmented.mp4`) | ||
128 | ) | ||
129 | } | 139 | } |
130 | 140 | ||
131 | expect(file.resolution.label).to.equal(resolution + 'p') | 141 | expect(file.magnetUri).to.have.lengthOf.above(2) |
132 | 142 | await checkWebTorrentWorks(file.magnetUri) | |
133 | await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 }) | 143 | |
134 | await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 }) | 144 | { |
145 | const nameReg = `${uuidRegex}-${file.resolution.id}` | ||
146 | |||
147 | expect(file.torrentUrl).to.match(new RegExp(`${server.url}/lazy-static/torrents/${nameReg}-hls.torrent`)) | ||
148 | |||
149 | if (objectStorageBaseUrl && requiresAuth) { | ||
150 | // eslint-disable-next-line max-len | ||
151 | expect(file.fileUrl).to.match(new RegExp(`${server.url}/object-storage-proxy/streaming-playlists/hls/${privatePath}${videoDetails.uuid}/${nameReg}-fragmented.mp4`)) | ||
152 | } else if (objectStorageBaseUrl) { | ||
153 | expectStartWith(file.fileUrl, objectStorageBaseUrl) | ||
154 | } else { | ||
155 | expect(file.fileUrl).to.match( | ||
156 | new RegExp(`${baseUrl}/static/streaming-playlists/hls/${privatePath}${videoDetails.uuid}/${nameReg}-fragmented.mp4`) | ||
157 | ) | ||
158 | } | ||
159 | } | ||
135 | 160 | ||
136 | const torrent = await webtorrentAdd(file.magnetUri, true) | 161 | { |
137 | expect(torrent.files).to.be.an('array') | 162 | await Promise.all([ |
138 | expect(torrent.files.length).to.equal(1) | 163 | makeRawRequest({ url: file.torrentUrl, token, expectedStatus: HttpStatusCode.OK_200 }), |
139 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | 164 | makeRawRequest({ url: file.torrentDownloadUrl, token, expectedStatus: HttpStatusCode.OK_200 }), |
165 | makeRawRequest({ url: file.metadataUrl, token, expectedStatus: HttpStatusCode.OK_200 }), | ||
166 | makeRawRequest({ url: file.fileUrl, token, expectedStatus: HttpStatusCode.OK_200 }), | ||
167 | |||
168 | makeRawRequest({ | ||
169 | url: file.fileDownloadUrl, | ||
170 | token, | ||
171 | expectedStatus: objectStorageBaseUrl | ||
172 | ? HttpStatusCode.FOUND_302 | ||
173 | : HttpStatusCode.OK_200 | ||
174 | }) | ||
175 | ]) | ||
176 | } | ||
140 | } | 177 | } |
141 | 178 | ||
142 | // Check master playlist | 179 | // Check master playlist |
143 | { | 180 | { |
144 | await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions }) | 181 | await checkResolutionsInMasterPlaylist({ server, token, playlistUrl: hlsPlaylist.playlistUrl, resolutions }) |
145 | 182 | ||
146 | const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl }) | 183 | const masterPlaylist = await server.streamingPlaylists.get({ url: hlsPlaylist.playlistUrl, token }) |
147 | 184 | ||
148 | let i = 0 | 185 | let i = 0 |
149 | for (const resolution of resolutions) { | 186 | for (const resolution of resolutions) { |
@@ -163,11 +200,16 @@ async function completeCheckHlsPlaylist (options: { | |||
163 | const file = hlsFiles.find(f => f.resolution.id === resolution) | 200 | const file = hlsFiles.find(f => f.resolution.id === resolution) |
164 | const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8' | 201 | const playlistName = removeFragmentedMP4Ext(basename(file.fileUrl)) + '.m3u8' |
165 | 202 | ||
166 | const url = objectStorageBaseUrl | 203 | let url: string |
167 | ? `${objectStorageBaseUrl}hls/${videoUUID}/${playlistName}` | 204 | if (objectStorageBaseUrl && requiresAuth) { |
168 | : `${baseUrl}/static/streaming-playlists/hls/${videoUUID}/${playlistName}` | 205 | url = `${baseUrl}/object-storage-proxy/streaming-playlists/hls/${privatePath}${videoUUID}/${playlistName}` |
206 | } else if (objectStorageBaseUrl) { | ||
207 | url = `${objectStorageBaseUrl}hls/${videoUUID}/${playlistName}` | ||
208 | } else { | ||
209 | url = `${baseUrl}/static/streaming-playlists/hls/${privatePath}${videoUUID}/${playlistName}` | ||
210 | } | ||
169 | 211 | ||
170 | const subPlaylist = await server.streamingPlaylists.get({ url }) | 212 | const subPlaylist = await server.streamingPlaylists.get({ url, token }) |
171 | 213 | ||
172 | expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`)) | 214 | expect(subPlaylist).to.match(new RegExp(`${uuidRegex}-${resolution}-fragmented.mp4`)) |
173 | expect(subPlaylist).to.contain(basename(file.fileUrl)) | 215 | expect(subPlaylist).to.contain(basename(file.fileUrl)) |
@@ -175,13 +217,19 @@ async function completeCheckHlsPlaylist (options: { | |||
175 | } | 217 | } |
176 | 218 | ||
177 | { | 219 | { |
178 | const baseUrlAndPath = objectStorageBaseUrl | 220 | let baseUrlAndPath: string |
179 | ? objectStorageBaseUrl + 'hls/' + videoUUID | 221 | if (objectStorageBaseUrl && requiresAuth) { |
180 | : baseUrl + '/static/streaming-playlists/hls/' + videoUUID | 222 | baseUrlAndPath = `${baseUrl}/object-storage-proxy/streaming-playlists/hls/${privatePath}${videoUUID}` |
223 | } else if (objectStorageBaseUrl) { | ||
224 | baseUrlAndPath = `${objectStorageBaseUrl}hls/${videoUUID}` | ||
225 | } else { | ||
226 | baseUrlAndPath = `${baseUrl}/static/streaming-playlists/hls/${privatePath}${videoUUID}` | ||
227 | } | ||
181 | 228 | ||
182 | for (const resolution of resolutions) { | 229 | for (const resolution of resolutions) { |
183 | await checkSegmentHash({ | 230 | await checkSegmentHash({ |
184 | server, | 231 | server, |
232 | token, | ||
185 | baseUrlPlaylist: baseUrlAndPath, | 233 | baseUrlPlaylist: baseUrlAndPath, |
186 | baseUrlSegment: baseUrlAndPath, | 234 | baseUrlSegment: baseUrlAndPath, |
187 | resolution, | 235 | resolution, |
diff --git a/server/tests/shared/videos.ts b/server/tests/shared/videos.ts index f8ec65752..856fabd11 100644 --- a/server/tests/shared/videos.ts +++ b/server/tests/shared/videos.ts | |||
@@ -4,16 +4,106 @@ import { expect } from 'chai' | |||
4 | import { pathExists, readdir } from 'fs-extra' | 4 | import { pathExists, readdir } from 'fs-extra' |
5 | import { basename, join } from 'path' | 5 | import { basename, join } from 'path' |
6 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '@server/initializers/constants' | 6 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '@server/initializers/constants' |
7 | import { getLowercaseExtension, uuidRegex } from '@shared/core-utils' | 7 | import { getLowercaseExtension, pick, uuidRegex } from '@shared/core-utils' |
8 | import { HttpStatusCode, VideoCaption, VideoDetails } from '@shared/models' | 8 | import { HttpStatusCode, VideoCaption, VideoDetails, VideoPrivacy, VideoResolution } from '@shared/models' |
9 | import { makeRawRequest, PeerTubeServer, VideoEdit, waitJobs, webtorrentAdd } from '@shared/server-commands' | 9 | import { makeRawRequest, PeerTubeServer, VideoEdit, waitJobs } from '@shared/server-commands' |
10 | import { dateIsValid, testImage } from './checks' | 10 | import { dateIsValid, expectStartWith, testImage } from './checks' |
11 | import { checkWebTorrentWorks } from './webtorrent' | ||
11 | 12 | ||
12 | loadLanguages() | 13 | loadLanguages() |
13 | 14 | ||
14 | async function completeVideoCheck ( | 15 | async function completeWebVideoFilesCheck (options: { |
15 | server: PeerTubeServer, | 16 | server: PeerTubeServer |
16 | video: any, | 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 | ||
17 | attributes: { | 107 | attributes: { |
18 | name: string | 108 | name: string |
19 | category: number | 109 | category: number |
@@ -50,13 +140,14 @@ async function completeVideoCheck ( | |||
50 | thumbnailfile?: string | 140 | thumbnailfile?: string |
51 | previewfile?: string | 141 | previewfile?: string |
52 | } | 142 | } |
53 | ) { | 143 | }) { |
144 | const { attributes, originServer, server, videoUUID } = options | ||
145 | |||
146 | const video = await server.videos.get({ id: videoUUID }) | ||
147 | |||
54 | if (!attributes.likes) attributes.likes = 0 | 148 | if (!attributes.likes) attributes.likes = 0 |
55 | if (!attributes.dislikes) attributes.dislikes = 0 | 149 | if (!attributes.dislikes) attributes.dislikes = 0 |
56 | 150 | ||
57 | const host = new URL(server.url).host | ||
58 | const originHost = attributes.account.host | ||
59 | |||
60 | expect(video.name).to.equal(attributes.name) | 151 | expect(video.name).to.equal(attributes.name) |
61 | expect(video.category.id).to.equal(attributes.category) | 152 | expect(video.category.id).to.equal(attributes.category) |
62 | expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Unknown') | 153 | expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Unknown') |
@@ -77,7 +168,7 @@ async function completeVideoCheck ( | |||
77 | expect(video.dislikes).to.equal(attributes.dislikes) | 168 | expect(video.dislikes).to.equal(attributes.dislikes) |
78 | expect(video.isLocal).to.equal(attributes.isLocal) | 169 | expect(video.isLocal).to.equal(attributes.isLocal) |
79 | expect(video.duration).to.equal(attributes.duration) | 170 | expect(video.duration).to.equal(attributes.duration) |
80 | expect(video.url).to.contain(originHost) | 171 | expect(video.url).to.contain(originServer.host) |
81 | expect(dateIsValid(video.createdAt)).to.be.true | 172 | expect(dateIsValid(video.createdAt)).to.be.true |
82 | expect(dateIsValid(video.publishedAt)).to.be.true | 173 | expect(dateIsValid(video.publishedAt)).to.be.true |
83 | expect(dateIsValid(video.updatedAt)).to.be.true | 174 | expect(dateIsValid(video.updatedAt)).to.be.true |
@@ -92,67 +183,28 @@ async function completeVideoCheck ( | |||
92 | expect(video.originallyPublishedAt).to.be.null | 183 | expect(video.originallyPublishedAt).to.be.null |
93 | } | 184 | } |
94 | 185 | ||
95 | const videoDetails = await server.videos.get({ id: video.uuid }) | 186 | expect(video.files).to.have.lengthOf(attributes.files.length) |
96 | 187 | expect(video.tags).to.deep.equal(attributes.tags) | |
97 | expect(videoDetails.files).to.have.lengthOf(attributes.files.length) | 188 | expect(video.account.name).to.equal(attributes.account.name) |
98 | expect(videoDetails.tags).to.deep.equal(attributes.tags) | 189 | expect(video.account.host).to.equal(attributes.account.host) |
99 | expect(videoDetails.account.name).to.equal(attributes.account.name) | ||
100 | expect(videoDetails.account.host).to.equal(attributes.account.host) | ||
101 | expect(video.channel.displayName).to.equal(attributes.channel.displayName) | 190 | expect(video.channel.displayName).to.equal(attributes.channel.displayName) |
102 | expect(video.channel.name).to.equal(attributes.channel.name) | 191 | expect(video.channel.name).to.equal(attributes.channel.name) |
103 | expect(videoDetails.channel.host).to.equal(attributes.account.host) | 192 | expect(video.channel.host).to.equal(attributes.account.host) |
104 | expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal) | 193 | expect(video.channel.isLocal).to.equal(attributes.channel.isLocal) |
105 | expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true | 194 | expect(dateIsValid(video.channel.createdAt.toString())).to.be.true |
106 | expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true | 195 | expect(dateIsValid(video.channel.updatedAt.toString())).to.be.true |
107 | expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled) | 196 | expect(video.commentsEnabled).to.equal(attributes.commentsEnabled) |
108 | expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled) | 197 | expect(video.downloadEnabled).to.equal(attributes.downloadEnabled) |
109 | |||
110 | for (const attributeFile of attributes.files) { | ||
111 | const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution) | ||
112 | expect(file).not.to.be.undefined | ||
113 | |||
114 | let extension = getLowercaseExtension(attributes.fixture) | ||
115 | // Transcoding enabled: extension will always be .mp4 | ||
116 | if (attributes.files.length > 1) extension = '.mp4' | ||
117 | |||
118 | expect(file.id).to.exist | ||
119 | expect(file.magnetUri).to.have.lengthOf.above(2) | ||
120 | |||
121 | expect(file.torrentDownloadUrl).to.match(new RegExp(`http://${host}/download/torrents/${uuidRegex}-${file.resolution.id}.torrent`)) | ||
122 | expect(file.torrentUrl).to.match(new RegExp(`http://${host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}.torrent`)) | ||
123 | |||
124 | expect(file.fileUrl).to.match(new RegExp(`http://${originHost}/static/webseed/${uuidRegex}-${file.resolution.id}${extension}`)) | ||
125 | expect(file.fileDownloadUrl).to.match(new RegExp(`http://${originHost}/download/videos/${uuidRegex}-${file.resolution.id}${extension}`)) | ||
126 | 198 | ||
127 | await Promise.all([ | 199 | expect(video.thumbnailPath).to.exist |
128 | makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 }), | 200 | await testImage(server.url, attributes.thumbnailfile || attributes.fixture, video.thumbnailPath) |
129 | makeRawRequest({ url: file.torrentDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }), | ||
130 | makeRawRequest({ url: file.metadataUrl, expectedStatus: HttpStatusCode.OK_200 }) | ||
131 | ]) | ||
132 | |||
133 | expect(file.resolution.id).to.equal(attributeFile.resolution) | ||
134 | expect(file.resolution.label).to.equal(attributeFile.resolution + 'p') | ||
135 | |||
136 | const minSize = attributeFile.size - ((10 * attributeFile.size) / 100) | ||
137 | const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) | ||
138 | expect( | ||
139 | file.size, | ||
140 | 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')' | ||
141 | ).to.be.above(minSize).and.below(maxSize) | ||
142 | |||
143 | const torrent = await webtorrentAdd(file.magnetUri, true) | ||
144 | expect(torrent.files).to.be.an('array') | ||
145 | expect(torrent.files.length).to.equal(1) | ||
146 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | ||
147 | } | ||
148 | |||
149 | expect(videoDetails.thumbnailPath).to.exist | ||
150 | await testImage(server.url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath) | ||
151 | 201 | ||
152 | if (attributes.previewfile) { | 202 | if (attributes.previewfile) { |
153 | expect(videoDetails.previewPath).to.exist | 203 | expect(video.previewPath).to.exist |
154 | await testImage(server.url, attributes.previewfile, videoDetails.previewPath) | 204 | await testImage(server.url, attributes.previewfile, video.previewPath) |
155 | } | 205 | } |
206 | |||
207 | await completeWebVideoFilesCheck({ server, originServer, videoUUID: video.uuid, ...pick(attributes, [ 'fixture', 'files' ]) }) | ||
156 | } | 208 | } |
157 | 209 | ||
158 | async function checkVideoFilesWereRemoved (options: { | 210 | async function checkVideoFilesWereRemoved (options: { |
@@ -245,6 +297,7 @@ async function uploadRandomVideoOnServers ( | |||
245 | 297 | ||
246 | export { | 298 | export { |
247 | completeVideoCheck, | 299 | completeVideoCheck, |
300 | completeWebVideoFilesCheck, | ||
248 | checkUploadVideoParam, | 301 | checkUploadVideoParam, |
249 | uploadRandomVideoOnServers, | 302 | uploadRandomVideoOnServers, |
250 | checkVideoFilesWereRemoved, | 303 | checkVideoFilesWereRemoved, |
diff --git a/server/tests/shared/webtorrent.ts b/server/tests/shared/webtorrent.ts new file mode 100644 index 000000000..d5bd86500 --- /dev/null +++ b/server/tests/shared/webtorrent.ts | |||
@@ -0,0 +1,58 @@ | |||
1 | import { expect } from 'chai' | ||
2 | import { readFile } from 'fs-extra' | ||
3 | import parseTorrent from 'parse-torrent' | ||
4 | import { basename, join } from 'path' | ||
5 | import * as WebTorrent from 'webtorrent' | ||
6 | import { VideoFile } from '@shared/models' | ||
7 | import { PeerTubeServer } from '@shared/server-commands' | ||
8 | |||
9 | let webtorrent: WebTorrent.Instance | ||
10 | |||
11 | export async function checkWebTorrentWorks (magnetUri: string, pathMatch?: RegExp) { | ||
12 | const torrent = await webtorrentAdd(magnetUri, true) | ||
13 | |||
14 | expect(torrent.files).to.be.an('array') | ||
15 | expect(torrent.files.length).to.equal(1) | ||
16 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | ||
17 | |||
18 | if (pathMatch) { | ||
19 | expect(torrent.files[0].path).match(pathMatch) | ||
20 | } | ||
21 | } | ||
22 | |||
23 | export async function parseTorrentVideo (server: PeerTubeServer, file: VideoFile) { | ||
24 | const torrentName = basename(file.torrentUrl) | ||
25 | const torrentPath = server.servers.buildDirectory(join('torrents', torrentName)) | ||
26 | |||
27 | const data = await readFile(torrentPath) | ||
28 | |||
29 | return parseTorrent(data) | ||
30 | } | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | // Private | ||
34 | // --------------------------------------------------------------------------- | ||
35 | |||
36 | function webtorrentAdd (torrentId: string, refreshWebTorrent = false) { | ||
37 | const WebTorrent = require('webtorrent') | ||
38 | |||
39 | if (webtorrent && refreshWebTorrent) webtorrent.destroy() | ||
40 | if (!webtorrent || refreshWebTorrent) webtorrent = new WebTorrent() | ||
41 | |||
42 | webtorrent.on('error', err => console.error('Error in webtorrent', err)) | ||
43 | |||
44 | return new Promise<WebTorrent.Torrent>(res => { | ||
45 | const torrent = webtorrent.add(torrentId, res) | ||
46 | |||
47 | torrent.on('error', err => console.error('Error in webtorrent torrent', err)) | ||
48 | torrent.on('warning', warn => { | ||
49 | const msg = typeof warn === 'string' | ||
50 | ? warn | ||
51 | : warn.message | ||
52 | |||
53 | if (msg.includes('Unsupported')) return | ||
54 | |||
55 | console.error('Warning in webtorrent torrent', warn) | ||
56 | }) | ||
57 | }) | ||
58 | } | ||