aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/core-utils/common/array.ts (renamed from shared/core-utils/utils/array.ts)0
-rw-r--r--shared/core-utils/common/crypto.ts (renamed from shared/core-utils/crypto.ts)6
-rw-r--r--shared/core-utils/common/env.ts30
-rw-r--r--shared/core-utils/common/index.ts8
-rw-r--r--shared/core-utils/common/object.ts30
-rw-r--r--shared/core-utils/common/path.ts (renamed from shared/core-utils/path.ts)12
-rw-r--r--shared/core-utils/common/time.ts7
-rw-r--r--shared/core-utils/common/version.ts (renamed from shared/core-utils/common/miscs.ts)23
-rw-r--r--shared/core-utils/index.ts3
-rw-r--r--shared/core-utils/utils/index.ts2
-rw-r--r--shared/core-utils/utils/object.ts15
-rw-r--r--shared/core-utils/videos/index.ts1
-rw-r--r--shared/core-utils/videos/uuid.ts (renamed from shared/core-utils/uuid.ts)0
-rw-r--r--shared/extra-utils/file.ts11
-rw-r--r--shared/extra-utils/index.ts1
-rw-r--r--shared/models/users/index.ts1
-rw-r--r--shared/server-commands/index.ts1
-rw-r--r--shared/server-commands/logs/logs-command.ts3
-rw-r--r--shared/server-commands/miscs/checks.ts58
-rw-r--r--shared/server-commands/miscs/generate.ts75
-rw-r--r--shared/server-commands/miscs/index.ts3
-rw-r--r--shared/server-commands/miscs/sql-command.ts2
-rw-r--r--shared/server-commands/miscs/tests.ts101
-rw-r--r--shared/server-commands/mock-servers/index.ts5
-rw-r--r--shared/server-commands/mock-servers/mock-429.ts33
-rw-r--r--shared/server-commands/mock-servers/mock-email.ts63
-rw-r--r--shared/server-commands/mock-servers/mock-instances-index.ts46
-rw-r--r--shared/server-commands/mock-servers/mock-joinpeertube-versions.ts34
-rw-r--r--shared/server-commands/mock-servers/mock-object-storage.ts41
-rw-r--r--shared/server-commands/mock-servers/mock-plugin-blocklist.ts36
-rw-r--r--shared/server-commands/mock-servers/mock-proxy.ts25
-rw-r--r--shared/server-commands/mock-servers/utils.ts33
-rw-r--r--shared/server-commands/requests/check-api-params.ts48
-rw-r--r--shared/server-commands/requests/index.ts2
-rw-r--r--shared/server-commands/requests/requests.ts2
-rw-r--r--shared/server-commands/server/config-command.ts5
-rw-r--r--shared/server-commands/server/directories.ts34
-rw-r--r--shared/server-commands/server/index.ts3
-rw-r--r--shared/server-commands/server/jobs-command.ts3
-rw-r--r--shared/server-commands/server/jobs.ts2
-rw-r--r--shared/server-commands/server/plugins.ts18
-rw-r--r--shared/server-commands/server/server.ts8
-rw-r--r--shared/server-commands/server/servers-command.ts4
-rw-r--r--shared/server-commands/server/servers.ts2
-rw-r--r--shared/server-commands/server/tracker.ts27
-rw-r--r--shared/server-commands/shared/abstract-command.ts2
-rw-r--r--shared/server-commands/users/accounts-command.ts4
-rw-r--r--shared/server-commands/users/actors.ts73
-rw-r--r--shared/server-commands/users/index.ts2
-rw-r--r--shared/server-commands/users/notifications-command.ts3
-rw-r--r--shared/server-commands/users/notifications.ts795
-rw-r--r--shared/server-commands/users/users-command.ts2
-rw-r--r--shared/server-commands/videos/blacklist-command.ts3
-rw-r--r--shared/server-commands/videos/captions-command.ts2
-rw-r--r--shared/server-commands/videos/captions.ts21
-rw-r--r--shared/server-commands/videos/channels-command.ts12
-rw-r--r--shared/server-commands/videos/index.ts4
-rw-r--r--shared/server-commands/videos/live-command.ts2
-rw-r--r--shared/server-commands/videos/live.ts41
-rw-r--r--shared/server-commands/videos/playlists.ts25
-rw-r--r--shared/server-commands/videos/streaming-playlists.ts77
-rw-r--r--shared/server-commands/videos/videos-command.ts4
-rw-r--r--shared/server-commands/videos/videos.ts104
63 files changed, 140 insertions, 1903 deletions
diff --git a/shared/core-utils/utils/array.ts b/shared/core-utils/common/array.ts
index 9e326a5aa..9e326a5aa 100644
--- a/shared/core-utils/utils/array.ts
+++ b/shared/core-utils/common/array.ts
diff --git a/shared/core-utils/crypto.ts b/shared/core-utils/common/crypto.ts
index d6d1150d0..1a583f1a0 100644
--- a/shared/core-utils/crypto.ts
+++ b/shared/core-utils/common/crypto.ts
@@ -8,7 +8,13 @@ function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
8 return createHash('sha1').update(str).digest(encoding) 8 return createHash('sha1').update(str).digest(encoding)
9} 9}
10 10
11// high excluded
12function randomInt (low: number, high: number) {
13 return Math.floor(Math.random() * (high - low) + low)
14}
15
11export { 16export {
17 randomInt,
12 sha256, 18 sha256,
13 sha1 19 sha1
14} 20}
diff --git a/shared/core-utils/common/env.ts b/shared/core-utils/common/env.ts
new file mode 100644
index 000000000..38c96b152
--- /dev/null
+++ b/shared/core-utils/common/env.ts
@@ -0,0 +1,30 @@
1function parallelTests () {
2 return process.env.MOCHA_PARALLEL === 'true'
3}
4
5function isGithubCI () {
6 return !!process.env.GITHUB_WORKSPACE
7}
8
9function areHttpImportTestsDisabled () {
10 const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true'
11
12 if (disabled) console.log('DISABLE_HTTP_IMPORT_TESTS env set to "true" so import tests are disabled')
13
14 return disabled
15}
16
17function areObjectStorageTestsDisabled () {
18 const disabled = process.env.ENABLE_OBJECT_STORAGE_TESTS !== 'true'
19
20 if (disabled) console.log('ENABLE_OBJECT_STORAGE_TESTS env is not set to "true" so object storage tests are disabled')
21
22 return disabled
23}
24
25export {
26 parallelTests,
27 isGithubCI,
28 areHttpImportTestsDisabled,
29 areObjectStorageTestsDisabled
30}
diff --git a/shared/core-utils/common/index.ts b/shared/core-utils/common/index.ts
index 5d3512148..f9ba10d5e 100644
--- a/shared/core-utils/common/index.ts
+++ b/shared/core-utils/common/index.ts
@@ -1,5 +1,11 @@
1export * from './array'
2export * from './crypto'
1export * from './date' 3export * from './date'
2export * from './miscs' 4export * from './env'
5export * from './object'
6export * from './path'
3export * from './regexp' 7export * from './regexp'
8export * from './time'
4export * from './promises' 9export * from './promises'
5export * from './url' 10export * from './url'
11export * from './version'
diff --git a/shared/core-utils/common/object.ts b/shared/core-utils/common/object.ts
new file mode 100644
index 000000000..88d6b7514
--- /dev/null
+++ b/shared/core-utils/common/object.ts
@@ -0,0 +1,30 @@
1function pick <O extends object, K extends keyof O> (object: O, keys: K[]): Pick<O, K> {
2 const result: any = {}
3
4 for (const key of keys) {
5 if (Object.prototype.hasOwnProperty.call(object, key)) {
6 result[key] = object[key]
7 }
8 }
9
10 return result
11}
12
13function sortObjectComparator (key: string, order: 'asc' | 'desc') {
14 return (a: any, b: any) => {
15 if (a[key] < b[key]) {
16 return order === 'asc' ? -1 : 1
17 }
18
19 if (a[key] > b[key]) {
20 return order === 'asc' ? 1 : -1
21 }
22
23 return 0
24 }
25}
26
27export {
28 pick,
29 sortObjectComparator
30}
diff --git a/shared/core-utils/path.ts b/shared/core-utils/common/path.ts
index b1a45d69b..ec507538b 100644
--- a/shared/core-utils/path.ts
+++ b/shared/core-utils/common/path.ts
@@ -7,6 +7,7 @@ function root () {
7 7
8 rootPath = __dirname 8 rootPath = __dirname
9 9
10 if (basename(rootPath) === 'common') rootPath = resolve(rootPath, '..')
10 if (basename(rootPath) === 'core-utils') rootPath = resolve(rootPath, '..') 11 if (basename(rootPath) === 'core-utils') rootPath = resolve(rootPath, '..')
11 if (basename(rootPath) === 'shared') rootPath = resolve(rootPath, '..') 12 if (basename(rootPath) === 'shared') rootPath = resolve(rootPath, '..')
12 if (basename(rootPath) === 'server') rootPath = resolve(rootPath, '..') 13 if (basename(rootPath) === 'server') rootPath = resolve(rootPath, '..')
@@ -27,8 +28,19 @@ function getLowercaseExtension (filename: string) {
27 return ext.toLowerCase() 28 return ext.toLowerCase()
28} 29}
29 30
31function buildAbsoluteFixturePath (path: string, customCIPath = false) {
32 if (isAbsolute(path)) return path
33
34 if (customCIPath && process.env.GITHUB_WORKSPACE) {
35 return join(process.env.GITHUB_WORKSPACE, 'fixtures', path)
36 }
37
38 return join(root(), 'server', 'tests', 'fixtures', path)
39}
40
30export { 41export {
31 root, 42 root,
32 buildPath, 43 buildPath,
44 buildAbsoluteFixturePath,
33 getLowercaseExtension 45 getLowercaseExtension
34} 46}
diff --git a/shared/core-utils/common/time.ts b/shared/core-utils/common/time.ts
new file mode 100644
index 000000000..2992609ca
--- /dev/null
+++ b/shared/core-utils/common/time.ts
@@ -0,0 +1,7 @@
1function wait (milliseconds: number) {
2 return new Promise(resolve => setTimeout(resolve, milliseconds))
3}
4
5export {
6 wait
7}
diff --git a/shared/core-utils/common/miscs.ts b/shared/core-utils/common/version.ts
index bc65dc338..8a64f8c4d 100644
--- a/shared/core-utils/common/miscs.ts
+++ b/shared/core-utils/common/version.ts
@@ -1,8 +1,3 @@
1// high excluded
2function randomInt (low: number, high: number) {
3 return Math.floor(Math.random() * (high - low) + low)
4}
5
6// Thanks https://stackoverflow.com/a/16187766 1// Thanks https://stackoverflow.com/a/16187766
7function compareSemVer (a: string, b: string) { 2function compareSemVer (a: string, b: string) {
8 const regExStrip0 = /(\.0+)+$/ 3 const regExStrip0 = /(\.0+)+$/
@@ -20,22 +15,6 @@ function compareSemVer (a: string, b: string) {
20 return segmentsA.length - segmentsB.length 15 return segmentsA.length - segmentsB.length
21} 16}
22 17
23function sortObjectComparator (key: string, order: 'asc' | 'desc') {
24 return (a: any, b: any) => {
25 if (a[key] < b[key]) {
26 return order === 'asc' ? -1 : 1
27 }
28
29 if (a[key] > b[key]) {
30 return order === 'asc' ? 1 : -1
31 }
32
33 return 0
34 }
35}
36
37export { 18export {
38 randomInt, 19 compareSemVer
39 compareSemVer,
40 sortObjectComparator
41} 20}
diff --git a/shared/core-utils/index.ts b/shared/core-utils/index.ts
index ee5cd4412..8daaa2d04 100644
--- a/shared/core-utils/index.ts
+++ b/shared/core-utils/index.ts
@@ -1,10 +1,7 @@
1export * from './abuse' 1export * from './abuse'
2export * from './common' 2export * from './common'
3export * from './i18n' 3export * from './i18n'
4export * from './path'
5export * from './plugins' 4export * from './plugins'
6export * from './renderer' 5export * from './renderer'
7export * from './users' 6export * from './users'
8export * from './utils'
9export * from './videos' 7export * from './videos'
10export * from './uuid'
diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts
deleted file mode 100644
index 8d16365a8..000000000
--- a/shared/core-utils/utils/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './array'
2export * from './object'
diff --git a/shared/core-utils/utils/object.ts b/shared/core-utils/utils/object.ts
deleted file mode 100644
index 9a8a98f9b..000000000
--- a/shared/core-utils/utils/object.ts
+++ /dev/null
@@ -1,15 +0,0 @@
1function pick <O extends object, K extends keyof O> (object: O, keys: K[]): Pick<O, K> {
2 const result: any = {}
3
4 for (const key of keys) {
5 if (Object.prototype.hasOwnProperty.call(object, key)) {
6 result[key] = object[key]
7 }
8 }
9
10 return result
11}
12
13export {
14 pick
15}
diff --git a/shared/core-utils/videos/index.ts b/shared/core-utils/videos/index.ts
index 620e3a716..8f6736d39 100644
--- a/shared/core-utils/videos/index.ts
+++ b/shared/core-utils/videos/index.ts
@@ -1,2 +1,3 @@
1export * from './bitrate' 1export * from './bitrate'
2export * from './privacy' 2export * from './privacy'
3export * from './uuid'
diff --git a/shared/core-utils/uuid.ts b/shared/core-utils/videos/uuid.ts
index f3c80e046..f3c80e046 100644
--- a/shared/core-utils/uuid.ts
+++ b/shared/core-utils/videos/uuid.ts
diff --git a/shared/extra-utils/file.ts b/shared/extra-utils/file.ts
new file mode 100644
index 000000000..8060ab520
--- /dev/null
+++ b/shared/extra-utils/file.ts
@@ -0,0 +1,11 @@
1import { stat } from 'fs-extra'
2
3async function getFileSize (path: string) {
4 const stats = await stat(path)
5
6 return stats.size
7}
8
9export {
10 getFileSize
11}
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts
index 5710c5ab5..38303cf1f 100644
--- a/shared/extra-utils/index.ts
+++ b/shared/extra-utils/index.ts
@@ -1 +1,2 @@
1export * from './ffprobe' 1export * from './ffprobe'
2export * from './file'
diff --git a/shared/models/users/index.ts b/shared/models/users/index.ts
index b61a8cd40..a24ffee96 100644
--- a/shared/models/users/index.ts
+++ b/shared/models/users/index.ts
@@ -8,6 +8,7 @@ export * from './user-refresh-token.model'
8export * from './user-register.model' 8export * from './user-register.model'
9export * from './user-right.enum' 9export * from './user-right.enum'
10export * from './user-role' 10export * from './user-role'
11export * from './user-scoped-token'
11export * from './user-update-me.model' 12export * from './user-update-me.model'
12export * from './user-update.model' 13export * from './user-update.model'
13export * from './user-video-quota.model' 14export * from './user-video-quota.model'
diff --git a/shared/server-commands/index.ts b/shared/server-commands/index.ts
index 4b3636d06..c24ebb2df 100644
--- a/shared/server-commands/index.ts
+++ b/shared/server-commands/index.ts
@@ -4,7 +4,6 @@ export * from './custom-pages'
4export * from './feeds' 4export * from './feeds'
5export * from './logs' 5export * from './logs'
6export * from './miscs' 6export * from './miscs'
7export * from './mock-servers'
8export * from './moderation' 7export * from './moderation'
9export * from './overviews' 8export * from './overviews'
10export * from './requests' 9export * from './requests'
diff --git a/shared/server-commands/logs/logs-command.ts b/shared/server-commands/logs/logs-command.ts
index 7b5c66c0c..8f63383ea 100644
--- a/shared/server-commands/logs/logs-command.ts
+++ b/shared/server-commands/logs/logs-command.ts
@@ -1,5 +1,4 @@
1import { HttpStatusCode } from '@shared/models' 1import { HttpStatusCode, LogLevel } from '@shared/models'
2import { LogLevel } from '../../models/server/log-level.type'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 3
5export class LogsCommand extends AbstractCommand { 4export class LogsCommand extends AbstractCommand {
diff --git a/shared/server-commands/miscs/checks.ts b/shared/server-commands/miscs/checks.ts
deleted file mode 100644
index 589928997..000000000
--- a/shared/server-commands/miscs/checks.ts
+++ /dev/null
@@ -1,58 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { pathExists, readFile } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@shared/core-utils'
7import { HttpStatusCode } from '@shared/models'
8import { makeGetRequest } from '../requests'
9import { PeerTubeServer } from '../server'
10
11// Default interval -> 5 minutes
12function dateIsValid (dateString: string, interval = 300000) {
13 const dateToCheck = new Date(dateString)
14 const now = new Date()
15
16 return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval
17}
18
19function expectStartWith (str: string, start: string) {
20 expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.true
21}
22
23async function expectLogDoesNotContain (server: PeerTubeServer, str: string) {
24 const content = await server.servers.getLogContent()
25
26 expect(content.toString()).to.not.contain(str)
27}
28
29async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
30 const res = await makeGetRequest({
31 url,
32 path: imagePath,
33 expectedStatus: HttpStatusCode.OK_200
34 })
35
36 const body = res.body
37
38 const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension))
39 const minLength = body.length - ((30 * body.length) / 100)
40 const maxLength = body.length + ((30 * body.length) / 100)
41
42 expect(data.length).to.be.above(minLength, 'the generated image is way smaller than the recorded fixture')
43 expect(data.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture')
44}
45
46async function testFileExistsOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) {
47 const base = server.servers.buildDirectory(directory)
48
49 expect(await pathExists(join(base, filePath))).to.equal(exist)
50}
51
52export {
53 dateIsValid,
54 testImage,
55 expectLogDoesNotContain,
56 testFileExistsOrNot,
57 expectStartWith
58}
diff --git a/shared/server-commands/miscs/generate.ts b/shared/server-commands/miscs/generate.ts
deleted file mode 100644
index 93673a063..000000000
--- a/shared/server-commands/miscs/generate.ts
+++ /dev/null
@@ -1,75 +0,0 @@
1import { expect } from 'chai'
2import ffmpeg from 'fluent-ffmpeg'
3import { ensureDir, pathExists } from 'fs-extra'
4import { dirname } from 'path'
5import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '@shared/extra-utils/ffprobe'
6import { getMaxBitrate } from '@shared/core-utils'
7import { buildAbsoluteFixturePath } from './tests'
8
9async function ensureHasTooBigBitrate (fixturePath: string) {
10 const bitrate = await getVideoFileBitrate(fixturePath)
11 const dataResolution = await getVideoFileResolution(fixturePath)
12 const fps = await getVideoFileFPS(fixturePath)
13
14 const maxBitrate = getMaxBitrate({ ...dataResolution, fps })
15 expect(bitrate).to.be.above(maxBitrate)
16}
17
18async function generateHighBitrateVideo () {
19 const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true)
20
21 await ensureDir(dirname(tempFixturePath))
22
23 const exists = await pathExists(tempFixturePath)
24 if (!exists) {
25 console.log('Generating high bitrate video.')
26
27 // Generate a random, high bitrate video on the fly, so we don't have to include
28 // a large file in the repo. The video needs to have a certain minimum length so
29 // that FFmpeg properly applies bitrate limits.
30 // https://stackoverflow.com/a/15795112
31 return new Promise<string>((res, rej) => {
32 ffmpeg()
33 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ])
34 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
35 .outputOptions([ '-maxrate 10M', '-bufsize 10M' ])
36 .output(tempFixturePath)
37 .on('error', rej)
38 .on('end', () => res(tempFixturePath))
39 .run()
40 })
41 }
42
43 await ensureHasTooBigBitrate(tempFixturePath)
44
45 return tempFixturePath
46}
47
48async function generateVideoWithFramerate (fps = 60) {
49 const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true)
50
51 await ensureDir(dirname(tempFixturePath))
52
53 const exists = await pathExists(tempFixturePath)
54 if (!exists) {
55 console.log('Generating video with framerate %d.', fps)
56
57 return new Promise<string>((res, rej) => {
58 ffmpeg()
59 .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ])
60 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
61 .outputOptions([ `-r ${fps}` ])
62 .output(tempFixturePath)
63 .on('error', rej)
64 .on('end', () => res(tempFixturePath))
65 .run()
66 })
67 }
68
69 return tempFixturePath
70}
71
72export {
73 generateHighBitrateVideo,
74 generateVideoWithFramerate
75}
diff --git a/shared/server-commands/miscs/index.ts b/shared/server-commands/miscs/index.ts
index 4474661de..a1d14e998 100644
--- a/shared/server-commands/miscs/index.ts
+++ b/shared/server-commands/miscs/index.ts
@@ -1,5 +1,2 @@
1export * from './checks'
2export * from './generate'
3export * from './sql-command' 1export * from './sql-command'
4export * from './tests'
5export * from './webtorrent' 2export * from './webtorrent'
diff --git a/shared/server-commands/miscs/sql-command.ts b/shared/server-commands/miscs/sql-command.ts
index bedb3349b..09a99f834 100644
--- a/shared/server-commands/miscs/sql-command.ts
+++ b/shared/server-commands/miscs/sql-command.ts
@@ -1,5 +1,5 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { AbstractCommand } from '../shared/abstract-command' 2import { AbstractCommand } from '../shared'
3 3
4export class SQLCommand extends AbstractCommand { 4export class SQLCommand extends AbstractCommand {
5 private sequelize: Sequelize 5 private sequelize: Sequelize
diff --git a/shared/server-commands/miscs/tests.ts b/shared/server-commands/miscs/tests.ts
deleted file mode 100644
index 658fe5fd3..000000000
--- a/shared/server-commands/miscs/tests.ts
+++ /dev/null
@@ -1,101 +0,0 @@
1import { stat } from 'fs-extra'
2import { basename, isAbsolute, join, resolve } from 'path'
3
4const FIXTURE_URLS = {
5 peertube_long: 'https://peertube2.cpy.re/videos/watch/122d093a-1ede-43bd-bd34-59d2931ffc5e',
6 peertube_short: 'https://peertube2.cpy.re/w/3fbif9S3WmtTP8gGsC5HBd',
7
8 youtube: 'https://www.youtube.com/watch?v=msX3jv1XdvM',
9
10 /**
11 * The video is used to check format-selection correctness wrt. HDR,
12 * which brings its own set of oddities outside of a MediaSource.
13 *
14 * The video needs to have the following format_ids:
15 * (which you can check by using `youtube-dl <url> -F`):
16 * - (webm vp9)
17 * - (mp4 avc1)
18 * - (webm vp9.2 HDR)
19 */
20 youtubeHDR: 'https://www.youtube.com/watch?v=RQgnBB9z_N4',
21
22 // eslint-disable-next-line max-len
23 magnet: 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Flazy-static%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4',
24
25 badVideo: 'https://download.cpy.re/peertube/bad_video.mp4',
26 goodVideo: 'https://download.cpy.re/peertube/good_video.mp4',
27 goodVideo720: 'https://download.cpy.re/peertube/good_video_720.mp4',
28
29 file4K: 'https://download.cpy.re/peertube/4k_file.txt'
30}
31
32function parallelTests () {
33 return process.env.MOCHA_PARALLEL === 'true'
34}
35
36function isGithubCI () {
37 return !!process.env.GITHUB_WORKSPACE
38}
39
40function areHttpImportTestsDisabled () {
41 const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true'
42
43 if (disabled) console.log('DISABLE_HTTP_IMPORT_TESTS env set to "true" so import tests are disabled')
44
45 return disabled
46}
47
48function areObjectStorageTestsDisabled () {
49 const disabled = process.env.ENABLE_OBJECT_STORAGE_TESTS !== 'true'
50
51 if (disabled) console.log('ENABLE_OBJECT_STORAGE_TESTS env is not set to "true" so object storage tests are disabled')
52
53 return disabled
54}
55
56function buildAbsoluteFixturePath (path: string, customCIPath = false) {
57 if (isAbsolute(path)) return path
58
59 if (customCIPath && process.env.GITHUB_WORKSPACE) {
60 return join(process.env.GITHUB_WORKSPACE, 'fixtures', path)
61 }
62
63 return join(root(), 'server', 'tests', 'fixtures', path)
64}
65
66function root () {
67 // We are in /miscs
68 let root = join(__dirname, '..', '..', '..')
69
70 if (basename(root) === 'dist') root = resolve(root, '..')
71
72 return root
73}
74
75function wait (milliseconds: number) {
76 return new Promise(resolve => setTimeout(resolve, milliseconds))
77}
78
79async function getFileSize (path: string) {
80 const stats = await stat(path)
81
82 return stats.size
83}
84
85function buildRequestStub (): any {
86 return { }
87}
88
89export {
90 FIXTURE_URLS,
91
92 parallelTests,
93 isGithubCI,
94 areHttpImportTestsDisabled,
95 buildAbsoluteFixturePath,
96 getFileSize,
97 buildRequestStub,
98 areObjectStorageTestsDisabled,
99 wait,
100 root
101}
diff --git a/shared/server-commands/mock-servers/index.ts b/shared/server-commands/mock-servers/index.ts
deleted file mode 100644
index 93c00c788..000000000
--- a/shared/server-commands/mock-servers/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
1export * from './mock-email'
2export * from './mock-instances-index'
3export * from './mock-joinpeertube-versions'
4export * from './mock-plugin-blocklist'
5export * from './mock-object-storage'
diff --git a/shared/server-commands/mock-servers/mock-429.ts b/shared/server-commands/mock-servers/mock-429.ts
deleted file mode 100644
index 9e0d1281a..000000000
--- a/shared/server-commands/mock-servers/mock-429.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './utils'
4
5export class Mock429 {
6 private server: Server
7 private responseSent = false
8
9 async initialize () {
10 const app = express()
11
12 app.get('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
13
14 if (!this.responseSent) {
15 this.responseSent = true
16
17 // Retry after 5 seconds
18 res.header('retry-after', '2')
19 return res.sendStatus(429)
20 }
21
22 return res.sendStatus(200)
23 })
24
25 this.server = await randomListen(app)
26
27 return getPort(this.server)
28 }
29
30 terminate () {
31 return terminateServer(this.server)
32 }
33}
diff --git a/shared/server-commands/mock-servers/mock-email.ts b/shared/server-commands/mock-servers/mock-email.ts
deleted file mode 100644
index f646c1621..000000000
--- a/shared/server-commands/mock-servers/mock-email.ts
+++ /dev/null
@@ -1,63 +0,0 @@
1import { ChildProcess } from 'child_process'
2import MailDev from '@peertube/maildev'
3import { randomInt } from '@shared/core-utils'
4import { parallelTests } from '../miscs'
5
6class MockSmtpServer {
7
8 private static instance: MockSmtpServer
9 private started = false
10 private emailChildProcess: ChildProcess
11 private emails: object[]
12
13 private constructor () { }
14
15 collectEmails (emailsCollection: object[]) {
16 return new Promise<number>((res, rej) => {
17 const port = parallelTests() ? randomInt(1000, 2000) : 1025
18 this.emails = emailsCollection
19
20 if (this.started) {
21 return res(undefined)
22 }
23
24 const maildev = new MailDev({
25 ip: '127.0.0.1',
26 smtp: port,
27 disableWeb: true,
28 silent: true
29 })
30
31 maildev.on('new', email => {
32 this.emails.push(email)
33 })
34
35 maildev.listen(err => {
36 if (err) return rej(err)
37
38 this.started = true
39
40 return res(port)
41 })
42 })
43 }
44
45 kill () {
46 if (!this.emailChildProcess) return
47
48 process.kill(this.emailChildProcess.pid)
49
50 this.emailChildProcess = null
51 MockSmtpServer.instance = null
52 }
53
54 static get Instance () {
55 return this.instance || (this.instance = new this())
56 }
57}
58
59// ---------------------------------------------------------------------------
60
61export {
62 MockSmtpServer
63}
diff --git a/shared/server-commands/mock-servers/mock-instances-index.ts b/shared/server-commands/mock-servers/mock-instances-index.ts
deleted file mode 100644
index 92b12d6f3..000000000
--- a/shared/server-commands/mock-servers/mock-instances-index.ts
+++ /dev/null
@@ -1,46 +0,0 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './utils'
4
5export class MockInstancesIndex {
6 private server: Server
7
8 private readonly indexInstances: { host: string, createdAt: string }[] = []
9
10 async initialize () {
11 const app = express()
12
13 app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
15
16 return next()
17 })
18
19 app.get('/api/v1/instances/hosts', (req: express.Request, res: express.Response) => {
20 const since = req.query.since
21
22 const filtered = this.indexInstances.filter(i => {
23 if (!since) return true
24
25 return i.createdAt > since
26 })
27
28 return res.json({
29 total: filtered.length,
30 data: filtered
31 })
32 })
33
34 this.server = await randomListen(app)
35
36 return getPort(this.server)
37 }
38
39 addInstance (host: string) {
40 this.indexInstances.push({ host, createdAt: new Date().toISOString() })
41 }
42
43 terminate () {
44 return terminateServer(this.server)
45 }
46}
diff --git a/shared/server-commands/mock-servers/mock-joinpeertube-versions.ts b/shared/server-commands/mock-servers/mock-joinpeertube-versions.ts
deleted file mode 100644
index e7906ea56..000000000
--- a/shared/server-commands/mock-servers/mock-joinpeertube-versions.ts
+++ /dev/null
@@ -1,34 +0,0 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen } from './utils'
4
5export class MockJoinPeerTubeVersions {
6 private server: Server
7 private latestVersion: string
8
9 async initialize () {
10 const app = express()
11
12 app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
14
15 return next()
16 })
17
18 app.get('/versions.json', (req: express.Request, res: express.Response) => {
19 return res.json({
20 peertube: {
21 latestVersion: this.latestVersion
22 }
23 })
24 })
25
26 this.server = await randomListen(app)
27
28 return getPort(this.server)
29 }
30
31 setLatestVersion (latestVersion: string) {
32 this.latestVersion = latestVersion
33 }
34}
diff --git a/shared/server-commands/mock-servers/mock-object-storage.ts b/shared/server-commands/mock-servers/mock-object-storage.ts
deleted file mode 100644
index d135c2631..000000000
--- a/shared/server-commands/mock-servers/mock-object-storage.ts
+++ /dev/null
@@ -1,41 +0,0 @@
1import express from 'express'
2import got, { RequestError } from 'got'
3import { Server } from 'http'
4import { pipeline } from 'stream'
5import { ObjectStorageCommand } from '../server'
6import { getPort, randomListen, terminateServer } from './utils'
7
8export class MockObjectStorage {
9 private server: Server
10
11 async initialize () {
12 const app = express()
13
14 app.get('/:bucketName/:path(*)', (req: express.Request, res: express.Response, next: express.NextFunction) => {
15 const url = `http://${req.params.bucketName}.${ObjectStorageCommand.getEndpointHost()}/${req.params.path}`
16
17 if (process.env.DEBUG) {
18 console.log('Receiving request on mocked server %s.', req.url)
19 console.log('Proxifying request to %s', url)
20 }
21
22 return pipeline(
23 got.stream(url, { throwHttpErrors: false }),
24 res,
25 (err: RequestError) => {
26 if (!err) return
27
28 console.error('Pipeline failed.', err)
29 }
30 )
31 })
32
33 this.server = await randomListen(app)
34
35 return getPort(this.server)
36 }
37
38 terminate () {
39 return terminateServer(this.server)
40 }
41}
diff --git a/shared/server-commands/mock-servers/mock-plugin-blocklist.ts b/shared/server-commands/mock-servers/mock-plugin-blocklist.ts
deleted file mode 100644
index f8a271cba..000000000
--- a/shared/server-commands/mock-servers/mock-plugin-blocklist.ts
+++ /dev/null
@@ -1,36 +0,0 @@
1import express, { Request, Response } from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './utils'
4
5type BlocklistResponse = {
6 data: {
7 value: string
8 action?: 'add' | 'remove'
9 updatedAt?: string
10 }[]
11}
12
13export class MockBlocklist {
14 private body: BlocklistResponse
15 private server: Server
16
17 async initialize () {
18 const app = express()
19
20 app.get('/blocklist', (req: Request, res: Response) => {
21 return res.json(this.body)
22 })
23
24 this.server = await randomListen(app)
25
26 return getPort(this.server)
27 }
28
29 replace (body: BlocklistResponse) {
30 this.body = body
31 }
32
33 terminate () {
34 return terminateServer(this.server)
35 }
36}
diff --git a/shared/server-commands/mock-servers/mock-proxy.ts b/shared/server-commands/mock-servers/mock-proxy.ts
deleted file mode 100644
index 75ac79055..000000000
--- a/shared/server-commands/mock-servers/mock-proxy.ts
+++ /dev/null
@@ -1,25 +0,0 @@
1
2import { createServer, Server } from 'http'
3import proxy from 'proxy'
4import { getPort, terminateServer } from './utils'
5
6class MockProxy {
7 private server: Server
8
9 initialize () {
10 return new Promise<number>(res => {
11 this.server = proxy(createServer())
12 this.server.listen(0, () => res(getPort(this.server)))
13 })
14 }
15
16 terminate () {
17 return terminateServer(this.server)
18 }
19}
20
21// ---------------------------------------------------------------------------
22
23export {
24 MockProxy
25}
diff --git a/shared/server-commands/mock-servers/utils.ts b/shared/server-commands/mock-servers/utils.ts
deleted file mode 100644
index 235642439..000000000
--- a/shared/server-commands/mock-servers/utils.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import { Express } from 'express'
2import { Server } from 'http'
3import { AddressInfo } from 'net'
4
5function randomListen (app: Express) {
6 return new Promise<Server>(res => {
7 const server = app.listen(0, () => res(server))
8 })
9}
10
11function getPort (server: Server) {
12 const address = server.address() as AddressInfo
13
14 return address.port
15}
16
17function terminateServer (server: Server) {
18 if (!server) return Promise.resolve()
19
20 return new Promise<void>((res, rej) => {
21 server.close(err => {
22 if (err) return rej(err)
23
24 return res()
25 })
26 })
27}
28
29export {
30 randomListen,
31 getPort,
32 terminateServer
33}
diff --git a/shared/server-commands/requests/check-api-params.ts b/shared/server-commands/requests/check-api-params.ts
deleted file mode 100644
index 26ba1e913..000000000
--- a/shared/server-commands/requests/check-api-params.ts
+++ /dev/null
@@ -1,48 +0,0 @@
1import { HttpStatusCode } from '@shared/models'
2import { makeGetRequest } from './requests'
3
4function checkBadStartPagination (url: string, path: string, token?: string, query = {}) {
5 return makeGetRequest({
6 url,
7 path,
8 token,
9 query: { ...query, start: 'hello' },
10 expectedStatus: HttpStatusCode.BAD_REQUEST_400
11 })
12}
13
14async function checkBadCountPagination (url: string, path: string, token?: string, query = {}) {
15 await makeGetRequest({
16 url,
17 path,
18 token,
19 query: { ...query, count: 'hello' },
20 expectedStatus: HttpStatusCode.BAD_REQUEST_400
21 })
22
23 await makeGetRequest({
24 url,
25 path,
26 token,
27 query: { ...query, count: 2000 },
28 expectedStatus: HttpStatusCode.BAD_REQUEST_400
29 })
30}
31
32function checkBadSortPagination (url: string, path: string, token?: string, query = {}) {
33 return makeGetRequest({
34 url,
35 path,
36 token,
37 query: { ...query, sort: 'hello' },
38 expectedStatus: HttpStatusCode.BAD_REQUEST_400
39 })
40}
41
42// ---------------------------------------------------------------------------
43
44export {
45 checkBadStartPagination,
46 checkBadCountPagination,
47 checkBadSortPagination
48}
diff --git a/shared/server-commands/requests/index.ts b/shared/server-commands/requests/index.ts
index 501163f92..802982301 100644
--- a/shared/server-commands/requests/index.ts
+++ b/shared/server-commands/requests/index.ts
@@ -1,3 +1 @@
1// Don't include activitypub that import stuff from server
2export * from './check-api-params'
3export * from './requests' export * from './requests'
diff --git a/shared/server-commands/requests/requests.ts b/shared/server-commands/requests/requests.ts
index b6b9024ed..95e4fe6b1 100644
--- a/shared/server-commands/requests/requests.ts
+++ b/shared/server-commands/requests/requests.ts
@@ -3,8 +3,8 @@
3import { decode } from 'querystring' 3import { decode } from 'querystring'
4import request from 'supertest' 4import request from 'supertest'
5import { URL } from 'url' 5import { URL } from 'url'
6import { buildAbsoluteFixturePath } from '@shared/core-utils'
6import { HttpStatusCode } from '@shared/models' 7import { HttpStatusCode } from '@shared/models'
7import { buildAbsoluteFixturePath } from '../miscs/tests'
8 8
9export type CommonRequestParams = { 9export type CommonRequestParams = {
10 url: string 10 url: string
diff --git a/shared/server-commands/server/config-command.ts b/shared/server-commands/server/config-command.ts
index 89ae8eb4f..797231b1d 100644
--- a/shared/server-commands/server/config-command.ts
+++ b/shared/server-commands/server/config-command.ts
@@ -1,8 +1,7 @@
1import { merge } from 'lodash' 1import { merge } from 'lodash'
2import { About, CustomConfig, HttpStatusCode, ServerConfig } from '@shared/models'
2import { DeepPartial } from '@shared/typescript-utils' 3import { DeepPartial } from '@shared/typescript-utils'
3import { About, HttpStatusCode, ServerConfig } from '@shared/models' 4import { AbstractCommand, OverrideCommandOptions } from '../shared/abstract-command'
4import { CustomConfig } from '../../models/server/custom-config.model'
5import { AbstractCommand, OverrideCommandOptions } from '../shared'
6 5
7export class ConfigCommand extends AbstractCommand { 6export class ConfigCommand extends AbstractCommand {
8 7
diff --git a/shared/server-commands/server/directories.ts b/shared/server-commands/server/directories.ts
deleted file mode 100644
index e6f72d6fc..000000000
--- a/shared/server-commands/server/directories.ts
+++ /dev/null
@@ -1,34 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@shared/core-utils'
7import { PeerTubeServer } from './server'
8
9async function checkTmpIsEmpty (server: PeerTubeServer) {
10 await checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css', 'hls', 'resumable-uploads' ])
11
12 if (await pathExists(join('test' + server.internalServerNumber, 'tmp', 'hls'))) {
13 await checkDirectoryIsEmpty(server, 'tmp/hls')
14 }
15}
16
17async function checkDirectoryIsEmpty (server: PeerTubeServer, directory: string, exceptions: string[] = []) {
18 const testDirectory = 'test' + server.internalServerNumber
19
20 const directoryPath = join(root(), testDirectory, directory)
21
22 const directoryExists = await pathExists(directoryPath)
23 expect(directoryExists).to.be.true
24
25 const files = await readdir(directoryPath)
26 const filtered = files.filter(f => exceptions.includes(f) === false)
27
28 expect(filtered).to.have.lengthOf(0)
29}
30
31export {
32 checkTmpIsEmpty,
33 checkDirectoryIsEmpty
34}
diff --git a/shared/server-commands/server/index.ts b/shared/server-commands/server/index.ts
index 76a2099da..0a4b21fc4 100644
--- a/shared/server-commands/server/index.ts
+++ b/shared/server-commands/server/index.ts
@@ -1,17 +1,14 @@
1export * from './config-command' 1export * from './config-command'
2export * from './contact-form-command' 2export * from './contact-form-command'
3export * from './debug-command' 3export * from './debug-command'
4export * from './directories'
5export * from './follows-command' 4export * from './follows-command'
6export * from './follows' 5export * from './follows'
7export * from './jobs' 6export * from './jobs'
8export * from './jobs-command' 7export * from './jobs-command'
9export * from './object-storage-command' 8export * from './object-storage-command'
10export * from './plugins-command' 9export * from './plugins-command'
11export * from './plugins'
12export * from './redundancy-command' 10export * from './redundancy-command'
13export * from './server' 11export * from './server'
14export * from './servers-command' 12export * from './servers-command'
15export * from './servers' 13export * from './servers'
16export * from './stats-command' 14export * from './stats-command'
17export * from './tracker'
diff --git a/shared/server-commands/server/jobs-command.ts b/shared/server-commands/server/jobs-command.ts
index 6636e7e4d..ac62157d1 100644
--- a/shared/server-commands/server/jobs-command.ts
+++ b/shared/server-commands/server/jobs-command.ts
@@ -1,6 +1,5 @@
1import { pick } from '@shared/core-utils' 1import { pick } from '@shared/core-utils'
2import { HttpStatusCode } from '@shared/models' 2import { HttpStatusCode, Job, JobState, JobType, ResultList } from '@shared/models'
3import { Job, JobState, JobType, ResultList } from '../../models'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 4
6export class JobsCommand extends AbstractCommand { 5export class JobsCommand extends AbstractCommand {
diff --git a/shared/server-commands/server/jobs.ts b/shared/server-commands/server/jobs.ts
index 34fefd444..fc65a873b 100644
--- a/shared/server-commands/server/jobs.ts
+++ b/shared/server-commands/server/jobs.ts
@@ -1,7 +1,7 @@
1 1
2import { expect } from 'chai' 2import { expect } from 'chai'
3import { wait } from '@shared/core-utils'
3import { JobState, JobType } from '../../models' 4import { JobState, JobType } from '../../models'
4import { wait } from '../miscs'
5import { PeerTubeServer } from './server' 5import { PeerTubeServer } from './server'
6 6
7async function waitJobs (serversArg: PeerTubeServer[] | PeerTubeServer, skipDelayed = false) { 7async function waitJobs (serversArg: PeerTubeServer[] | PeerTubeServer, skipDelayed = false) {
diff --git a/shared/server-commands/server/plugins.ts b/shared/server-commands/server/plugins.ts
deleted file mode 100644
index c6316898d..000000000
--- a/shared/server-commands/server/plugins.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { PeerTubeServer } from './server'
5
6async function testHelloWorldRegisteredSettings (server: PeerTubeServer) {
7 const body = await server.plugins.getRegisteredSettings({ npmName: 'peertube-plugin-hello-world' })
8
9 const registeredSettings = body.registeredSettings
10 expect(registeredSettings).to.have.length.at.least(1)
11
12 const adminNameSettings = registeredSettings.find(s => s.name === 'admin-name')
13 expect(adminNameSettings).to.not.be.undefined
14}
15
16export {
17 testHelloWorldRegisteredSettings
18}
diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts
index 339b9cabb..617069b11 100644
--- a/shared/server-commands/server/server.ts
+++ b/shared/server-commands/server/server.ts
@@ -1,14 +1,14 @@
1import { ChildProcess, fork } from 'child_process' 1import { ChildProcess, fork } from 'child_process'
2import { copy } from 'fs-extra' 2import { copy } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import { root, randomInt } from '@shared/core-utils' 4import { parallelTests, randomInt, root } from '@shared/core-utils'
5import { Video, VideoChannel, VideoCreateResult, VideoDetails } from '../../models/videos' 5import { Video, VideoChannel, VideoCreateResult, VideoDetails } from '@shared/models'
6import { BulkCommand } from '../bulk' 6import { BulkCommand } from '../bulk'
7import { CLICommand } from '../cli' 7import { CLICommand } from '../cli'
8import { CustomPagesCommand } from '../custom-pages' 8import { CustomPagesCommand } from '../custom-pages'
9import { FeedCommand } from '../feeds' 9import { FeedCommand } from '../feeds'
10import { LogsCommand } from '../logs' 10import { LogsCommand } from '../logs'
11import { parallelTests, SQLCommand } from '../miscs' 11import { SQLCommand } from '../miscs'
12import { AbusesCommand } from '../moderation' 12import { AbusesCommand } from '../moderation'
13import { OverviewsCommand } from '../overviews' 13import { OverviewsCommand } from '../overviews'
14import { SearchCommand } from '../search' 14import { SearchCommand } from '../search'
@@ -33,11 +33,11 @@ import { ContactFormCommand } from './contact-form-command'
33import { DebugCommand } from './debug-command' 33import { DebugCommand } from './debug-command'
34import { FollowsCommand } from './follows-command' 34import { FollowsCommand } from './follows-command'
35import { JobsCommand } from './jobs-command' 35import { JobsCommand } from './jobs-command'
36import { ObjectStorageCommand } from './object-storage-command'
36import { PluginsCommand } from './plugins-command' 37import { PluginsCommand } from './plugins-command'
37import { RedundancyCommand } from './redundancy-command' 38import { RedundancyCommand } from './redundancy-command'
38import { ServersCommand } from './servers-command' 39import { ServersCommand } from './servers-command'
39import { StatsCommand } from './stats-command' 40import { StatsCommand } from './stats-command'
40import { ObjectStorageCommand } from './object-storage-command'
41 41
42export type RunServerOptions = { 42export type RunServerOptions = {
43 hideLogs?: boolean 43 hideLogs?: boolean
diff --git a/shared/server-commands/server/servers-command.ts b/shared/server-commands/server/servers-command.ts
index 47420c95f..c5d8d18dc 100644
--- a/shared/server-commands/server/servers-command.ts
+++ b/shared/server-commands/server/servers-command.ts
@@ -1,9 +1,9 @@
1import { exec } from 'child_process' 1import { exec } from 'child_process'
2import { copy, ensureDir, readFile, remove } from 'fs-extra' 2import { copy, ensureDir, readFile, remove } from 'fs-extra'
3import { basename, join } from 'path' 3import { basename, join } from 'path'
4import { root } from '@shared/core-utils' 4import { isGithubCI, root, wait } from '@shared/core-utils'
5import { getFileSize } from '@shared/extra-utils'
5import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
6import { getFileSize, isGithubCI, wait } from '../miscs'
7import { AbstractCommand, OverrideCommandOptions } from '../shared' 7import { AbstractCommand, OverrideCommandOptions } from '../shared'
8 8
9export class ServersCommand extends AbstractCommand { 9export class ServersCommand extends AbstractCommand {
diff --git a/shared/server-commands/server/servers.ts b/shared/server-commands/server/servers.ts
index 21ab9405b..0faee3a8d 100644
--- a/shared/server-commands/server/servers.ts
+++ b/shared/server-commands/server/servers.ts
@@ -1,5 +1,5 @@
1import { ensureDir } from 'fs-extra' 1import { ensureDir } from 'fs-extra'
2import { isGithubCI } from '../miscs' 2import { isGithubCI } from '@shared/core-utils'
3import { PeerTubeServer, RunServerOptions } from './server' 3import { PeerTubeServer, RunServerOptions } from './server'
4 4
5async function createSingleServer (serverNumber: number, configOverride?: Object, options: RunServerOptions = {}) { 5async function createSingleServer (serverNumber: number, configOverride?: Object, options: RunServerOptions = {}) {
diff --git a/shared/server-commands/server/tracker.ts b/shared/server-commands/server/tracker.ts
deleted file mode 100644
index ed43a5924..000000000
--- a/shared/server-commands/server/tracker.ts
+++ /dev/null
@@ -1,27 +0,0 @@
1import { expect } from 'chai'
2import { sha1 } from '@shared/core-utils/crypto'
3import { makeGetRequest } from '../requests'
4
5async function hlsInfohashExist (serverUrl: string, masterPlaylistUrl: string, fileNumber: number) {
6 const path = '/tracker/announce'
7
8 const infohash = sha1(`2${masterPlaylistUrl}+V${fileNumber}`)
9
10 // From bittorrent-tracker
11 const infohashBinary = escape(Buffer.from(infohash, 'hex').toString('binary')).replace(/[@*/+]/g, function (char) {
12 return '%' + char.charCodeAt(0).toString(16).toUpperCase()
13 })
14
15 const res = await makeGetRequest({
16 url: serverUrl,
17 path,
18 rawQuery: `peer_id=-WW0105-NkvYO/egUAr4&info_hash=${infohashBinary}&port=42100`,
19 expectedStatus: 200
20 })
21
22 expect(res.text).to.not.contain('failure')
23}
24
25export {
26 hlsInfohashExist
27}
diff --git a/shared/server-commands/shared/abstract-command.ts b/shared/server-commands/shared/abstract-command.ts
index a57c857fc..1b53a5330 100644
--- a/shared/server-commands/shared/abstract-command.ts
+++ b/shared/server-commands/shared/abstract-command.ts
@@ -1,5 +1,5 @@
1import { isAbsolute, join } from 'path' 1import { isAbsolute, join } from 'path'
2import { root } from '../miscs/tests' 2import { root } from '@shared/core-utils'
3import { 3import {
4 makeDeleteRequest, 4 makeDeleteRequest,
5 makeGetRequest, 5 makeGetRequest,
diff --git a/shared/server-commands/users/accounts-command.ts b/shared/server-commands/users/accounts-command.ts
index 98d9d5927..5844b330b 100644
--- a/shared/server-commands/users/accounts-command.ts
+++ b/shared/server-commands/users/accounts-command.ts
@@ -1,6 +1,4 @@
1import { HttpStatusCode, ResultList } from '@shared/models' 1import { Account, AccountVideoRate, ActorFollow, HttpStatusCode, ResultList, VideoRateType } from '@shared/models'
2import { Account, ActorFollow } from '../../models/actors'
3import { AccountVideoRate, VideoRateType } from '../../models/videos'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 3
6export class AccountsCommand extends AbstractCommand { 4export class AccountsCommand extends AbstractCommand {
diff --git a/shared/server-commands/users/actors.ts b/shared/server-commands/users/actors.ts
deleted file mode 100644
index 12c3e078a..000000000
--- a/shared/server-commands/users/actors.ts
+++ /dev/null
@@ -1,73 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@shared/core-utils'
7import { Account, VideoChannel } from '@shared/models'
8import { PeerTubeServer } from '../server'
9
10async function expectChannelsFollows (options: {
11 server: PeerTubeServer
12 handle: string
13 followers: number
14 following: number
15}) {
16 const { server } = options
17 const { data } = await server.channels.list()
18
19 return expectActorFollow({ ...options, data })
20}
21
22async function expectAccountFollows (options: {
23 server: PeerTubeServer
24 handle: string
25 followers: number
26 following: number
27}) {
28 const { server } = options
29 const { data } = await server.accounts.list()
30
31 return expectActorFollow({ ...options, data })
32}
33
34async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
35 const testDirectory = 'test' + serverNumber
36
37 for (const directory of [ 'avatars' ]) {
38 const directoryPath = join(root(), testDirectory, directory)
39
40 const directoryExists = await pathExists(directoryPath)
41 expect(directoryExists).to.be.true
42
43 const files = await readdir(directoryPath)
44 for (const file of files) {
45 expect(file).to.not.contain(filename)
46 }
47 }
48}
49
50export {
51 expectAccountFollows,
52 expectChannelsFollows,
53 checkActorFilesWereRemoved
54}
55
56// ---------------------------------------------------------------------------
57
58function expectActorFollow (options: {
59 server: PeerTubeServer
60 data: (Account | VideoChannel)[]
61 handle: string
62 followers: number
63 following: number
64}) {
65 const { server, data, handle, followers, following } = options
66
67 const actor = data.find(a => a.name + '@' + a.host === handle)
68 const message = `${handle} on ${server.url}`
69
70 expect(actor, message).to.exist
71 expect(actor.followersCount).to.equal(followers, message)
72 expect(actor.followingCount).to.equal(following, message)
73}
diff --git a/shared/server-commands/users/index.ts b/shared/server-commands/users/index.ts
index 460a06f70..c2bc5c44f 100644
--- a/shared/server-commands/users/index.ts
+++ b/shared/server-commands/users/index.ts
@@ -1,9 +1,7 @@
1export * from './accounts-command' 1export * from './accounts-command'
2export * from './actors'
3export * from './blocklist-command' 2export * from './blocklist-command'
4export * from './login' 3export * from './login'
5export * from './login-command' 4export * from './login-command'
6export * from './notifications'
7export * from './notifications-command' 5export * from './notifications-command'
8export * from './subscriptions-command' 6export * from './subscriptions-command'
9export * from './users-command' 7export * from './users-command'
diff --git a/shared/server-commands/users/notifications-command.ts b/shared/server-commands/users/notifications-command.ts
index 692420b8b..6bd815daa 100644
--- a/shared/server-commands/users/notifications-command.ts
+++ b/shared/server-commands/users/notifications-command.ts
@@ -1,5 +1,4 @@
1import { HttpStatusCode, ResultList } from '@shared/models' 1import { HttpStatusCode, ResultList, UserNotification, UserNotificationSetting } from '@shared/models'
2import { UserNotification, UserNotificationSetting } from '../../models/users'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 3
5export class NotificationsCommand extends AbstractCommand { 4export class NotificationsCommand extends AbstractCommand {
diff --git a/shared/server-commands/users/notifications.ts b/shared/server-commands/users/notifications.ts
deleted file mode 100644
index 07ccb0f8d..000000000
--- a/shared/server-commands/users/notifications.ts
+++ /dev/null
@@ -1,795 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { inspect } from 'util'
5import { AbuseState, PluginType } from '@shared/models'
6import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
7import { MockSmtpServer } from '../mock-servers/mock-email'
8import { PeerTubeServer } from '../server'
9import { doubleFollow } from '../server/follows'
10import { createMultipleServers } from '../server/servers'
11import { setAccessTokensToServers } from './login'
12
13type CheckerBaseParams = {
14 server: PeerTubeServer
15 emails: any[]
16 socketNotifications: UserNotification[]
17 token: string
18 check?: { web: boolean, mail: boolean }
19}
20
21type CheckerType = 'presence' | 'absence'
22
23function getAllNotificationsSettings (): UserNotificationSetting {
24 return {
25 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
26 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
27 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
28 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
29 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
30 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
31 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
32 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
33 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
34 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
35 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
36 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
37 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
38 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
39 newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
40 newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
41 }
42}
43
44async function checkNewVideoFromSubscription (options: CheckerBaseParams & {
45 videoName: string
46 shortUUID: string
47 checkType: CheckerType
48}) {
49 const { videoName, shortUUID } = options
50 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
51
52 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
53 if (checkType === 'presence') {
54 expect(notification).to.not.be.undefined
55 expect(notification.type).to.equal(notificationType)
56
57 checkVideo(notification.video, videoName, shortUUID)
58 checkActor(notification.video.channel)
59 } else {
60 expect(notification).to.satisfy((n: UserNotification) => {
61 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
62 })
63 }
64 }
65
66 function emailNotificationFinder (email: object) {
67 const text = email['text']
68 return text.indexOf(shortUUID) !== -1 && text.indexOf('Your subscription') !== -1
69 }
70
71 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
72}
73
74async function checkVideoIsPublished (options: CheckerBaseParams & {
75 videoName: string
76 shortUUID: string
77 checkType: CheckerType
78}) {
79 const { videoName, shortUUID } = options
80 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
81
82 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
83 if (checkType === 'presence') {
84 expect(notification).to.not.be.undefined
85 expect(notification.type).to.equal(notificationType)
86
87 checkVideo(notification.video, videoName, shortUUID)
88 checkActor(notification.video.channel)
89 } else {
90 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
91 }
92 }
93
94 function emailNotificationFinder (email: object) {
95 const text: string = email['text']
96 return text.includes(shortUUID) && text.includes('Your video')
97 }
98
99 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
100}
101
102async function checkMyVideoImportIsFinished (options: CheckerBaseParams & {
103 videoName: string
104 shortUUID: string
105 url: string
106 success: boolean
107 checkType: CheckerType
108}) {
109 const { videoName, shortUUID, url, success } = options
110
111 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
112
113 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
114 if (checkType === 'presence') {
115 expect(notification).to.not.be.undefined
116 expect(notification.type).to.equal(notificationType)
117
118 expect(notification.videoImport.targetUrl).to.equal(url)
119
120 if (success) checkVideo(notification.videoImport.video, videoName, shortUUID)
121 } else {
122 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
123 }
124 }
125
126 function emailNotificationFinder (email: object) {
127 const text: string = email['text']
128 const toFind = success ? ' finished' : ' error'
129
130 return text.includes(url) && text.includes(toFind)
131 }
132
133 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
134}
135
136async function checkUserRegistered (options: CheckerBaseParams & {
137 username: string
138 checkType: CheckerType
139}) {
140 const { username } = options
141 const notificationType = UserNotificationType.NEW_USER_REGISTRATION
142
143 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
144 if (checkType === 'presence') {
145 expect(notification).to.not.be.undefined
146 expect(notification.type).to.equal(notificationType)
147
148 checkActor(notification.account)
149 expect(notification.account.name).to.equal(username)
150 } else {
151 expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
152 }
153 }
154
155 function emailNotificationFinder (email: object) {
156 const text: string = email['text']
157
158 return text.includes(' registered.') && text.includes(username)
159 }
160
161 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
162}
163
164async function checkNewActorFollow (options: CheckerBaseParams & {
165 followType: 'channel' | 'account'
166 followerName: string
167 followerDisplayName: string
168 followingDisplayName: string
169 checkType: CheckerType
170}) {
171 const { followType, followerName, followerDisplayName, followingDisplayName } = options
172 const notificationType = UserNotificationType.NEW_FOLLOW
173
174 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
175 if (checkType === 'presence') {
176 expect(notification).to.not.be.undefined
177 expect(notification.type).to.equal(notificationType)
178
179 checkActor(notification.actorFollow.follower)
180 expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
181 expect(notification.actorFollow.follower.name).to.equal(followerName)
182 expect(notification.actorFollow.follower.host).to.not.be.undefined
183
184 const following = notification.actorFollow.following
185 expect(following.displayName).to.equal(followingDisplayName)
186 expect(following.type).to.equal(followType)
187 } else {
188 expect(notification).to.satisfy(n => {
189 return n.type !== notificationType ||
190 (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
191 })
192 }
193 }
194
195 function emailNotificationFinder (email: object) {
196 const text: string = email['text']
197
198 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
199 }
200
201 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
202}
203
204async function checkNewInstanceFollower (options: CheckerBaseParams & {
205 followerHost: string
206 checkType: CheckerType
207}) {
208 const { followerHost } = options
209 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
210
211 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
212 if (checkType === 'presence') {
213 expect(notification).to.not.be.undefined
214 expect(notification.type).to.equal(notificationType)
215
216 checkActor(notification.actorFollow.follower)
217 expect(notification.actorFollow.follower.name).to.equal('peertube')
218 expect(notification.actorFollow.follower.host).to.equal(followerHost)
219
220 expect(notification.actorFollow.following.name).to.equal('peertube')
221 } else {
222 expect(notification).to.satisfy(n => {
223 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
224 })
225 }
226 }
227
228 function emailNotificationFinder (email: object) {
229 const text: string = email['text']
230
231 return text.includes('instance has a new follower') && text.includes(followerHost)
232 }
233
234 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
235}
236
237async function checkAutoInstanceFollowing (options: CheckerBaseParams & {
238 followerHost: string
239 followingHost: string
240 checkType: CheckerType
241}) {
242 const { followerHost, followingHost } = options
243 const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
244
245 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
246 if (checkType === 'presence') {
247 expect(notification).to.not.be.undefined
248 expect(notification.type).to.equal(notificationType)
249
250 const following = notification.actorFollow.following
251 checkActor(following)
252 expect(following.name).to.equal('peertube')
253 expect(following.host).to.equal(followingHost)
254
255 expect(notification.actorFollow.follower.name).to.equal('peertube')
256 expect(notification.actorFollow.follower.host).to.equal(followerHost)
257 } else {
258 expect(notification).to.satisfy(n => {
259 return n.type !== notificationType || n.actorFollow.following.host !== followingHost
260 })
261 }
262 }
263
264 function emailNotificationFinder (email: object) {
265 const text: string = email['text']
266
267 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
268 }
269
270 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
271}
272
273async function checkCommentMention (options: CheckerBaseParams & {
274 shortUUID: string
275 commentId: number
276 threadId: number
277 byAccountDisplayName: string
278 checkType: CheckerType
279}) {
280 const { shortUUID, commentId, threadId, byAccountDisplayName } = options
281 const notificationType = UserNotificationType.COMMENT_MENTION
282
283 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
284 if (checkType === 'presence') {
285 expect(notification).to.not.be.undefined
286 expect(notification.type).to.equal(notificationType)
287
288 checkComment(notification.comment, commentId, threadId)
289 checkActor(notification.comment.account)
290 expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
291
292 checkVideo(notification.comment.video, undefined, shortUUID)
293 } else {
294 expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
295 }
296 }
297
298 function emailNotificationFinder (email: object) {
299 const text: string = email['text']
300
301 return text.includes(' mentioned ') && text.includes(shortUUID) && text.includes(byAccountDisplayName)
302 }
303
304 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
305}
306
307let lastEmailCount = 0
308
309async function checkNewCommentOnMyVideo (options: CheckerBaseParams & {
310 shortUUID: string
311 commentId: number
312 threadId: number
313 checkType: CheckerType
314}) {
315 const { server, shortUUID, commentId, threadId, checkType, emails } = options
316 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
317
318 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
319 if (checkType === 'presence') {
320 expect(notification).to.not.be.undefined
321 expect(notification.type).to.equal(notificationType)
322
323 checkComment(notification.comment, commentId, threadId)
324 checkActor(notification.comment.account)
325 checkVideo(notification.comment.video, undefined, shortUUID)
326 } else {
327 expect(notification).to.satisfy((n: UserNotification) => {
328 return n === undefined || n.comment === undefined || n.comment.id !== commentId
329 })
330 }
331 }
332
333 const commentUrl = `http://localhost:${server.port}/w/${shortUUID};threadId=${threadId}`
334
335 function emailNotificationFinder (email: object) {
336 return email['text'].indexOf(commentUrl) !== -1
337 }
338
339 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
340
341 if (checkType === 'presence') {
342 // We cannot detect email duplicates, so check we received another email
343 expect(emails).to.have.length.above(lastEmailCount)
344 lastEmailCount = emails.length
345 }
346}
347
348async function checkNewVideoAbuseForModerators (options: CheckerBaseParams & {
349 shortUUID: string
350 videoName: string
351 checkType: CheckerType
352}) {
353 const { shortUUID, videoName } = options
354 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
355
356 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
357 if (checkType === 'presence') {
358 expect(notification).to.not.be.undefined
359 expect(notification.type).to.equal(notificationType)
360
361 expect(notification.abuse.id).to.be.a('number')
362 checkVideo(notification.abuse.video, videoName, shortUUID)
363 } else {
364 expect(notification).to.satisfy((n: UserNotification) => {
365 return n === undefined || n.abuse === undefined || n.abuse.video.shortUUID !== shortUUID
366 })
367 }
368 }
369
370 function emailNotificationFinder (email: object) {
371 const text = email['text']
372 return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1
373 }
374
375 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
376}
377
378async function checkNewAbuseMessage (options: CheckerBaseParams & {
379 abuseId: number
380 message: string
381 toEmail: string
382 checkType: CheckerType
383}) {
384 const { abuseId, message, toEmail } = options
385 const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE
386
387 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
388 if (checkType === 'presence') {
389 expect(notification).to.not.be.undefined
390 expect(notification.type).to.equal(notificationType)
391
392 expect(notification.abuse.id).to.equal(abuseId)
393 } else {
394 expect(notification).to.satisfy((n: UserNotification) => {
395 return n === undefined || n.type !== notificationType || n.abuse === undefined || n.abuse.id !== abuseId
396 })
397 }
398 }
399
400 function emailNotificationFinder (email: object) {
401 const text = email['text']
402 const to = email['to'].filter(t => t.address === toEmail)
403
404 return text.indexOf(message) !== -1 && to.length !== 0
405 }
406
407 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
408}
409
410async function checkAbuseStateChange (options: CheckerBaseParams & {
411 abuseId: number
412 state: AbuseState
413 checkType: CheckerType
414}) {
415 const { abuseId, state } = options
416 const notificationType = UserNotificationType.ABUSE_STATE_CHANGE
417
418 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
419 if (checkType === 'presence') {
420 expect(notification).to.not.be.undefined
421 expect(notification.type).to.equal(notificationType)
422
423 expect(notification.abuse.id).to.equal(abuseId)
424 expect(notification.abuse.state).to.equal(state)
425 } else {
426 expect(notification).to.satisfy((n: UserNotification) => {
427 return n === undefined || n.abuse === undefined || n.abuse.id !== abuseId
428 })
429 }
430 }
431
432 function emailNotificationFinder (email: object) {
433 const text = email['text']
434
435 const contains = state === AbuseState.ACCEPTED
436 ? ' accepted'
437 : ' rejected'
438
439 return text.indexOf(contains) !== -1
440 }
441
442 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
443}
444
445async function checkNewCommentAbuseForModerators (options: CheckerBaseParams & {
446 shortUUID: string
447 videoName: string
448 checkType: CheckerType
449}) {
450 const { shortUUID, videoName } = options
451 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
452
453 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
454 if (checkType === 'presence') {
455 expect(notification).to.not.be.undefined
456 expect(notification.type).to.equal(notificationType)
457
458 expect(notification.abuse.id).to.be.a('number')
459 checkVideo(notification.abuse.comment.video, videoName, shortUUID)
460 } else {
461 expect(notification).to.satisfy((n: UserNotification) => {
462 return n === undefined || n.abuse === undefined || n.abuse.comment.video.shortUUID !== shortUUID
463 })
464 }
465 }
466
467 function emailNotificationFinder (email: object) {
468 const text = email['text']
469 return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1
470 }
471
472 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
473}
474
475async function checkNewAccountAbuseForModerators (options: CheckerBaseParams & {
476 displayName: string
477 checkType: CheckerType
478}) {
479 const { displayName } = options
480 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
481
482 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
483 if (checkType === 'presence') {
484 expect(notification).to.not.be.undefined
485 expect(notification.type).to.equal(notificationType)
486
487 expect(notification.abuse.id).to.be.a('number')
488 expect(notification.abuse.account.displayName).to.equal(displayName)
489 } else {
490 expect(notification).to.satisfy((n: UserNotification) => {
491 return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
492 })
493 }
494 }
495
496 function emailNotificationFinder (email: object) {
497 const text = email['text']
498 return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
499 }
500
501 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
502}
503
504async function checkVideoAutoBlacklistForModerators (options: CheckerBaseParams & {
505 shortUUID: string
506 videoName: string
507 checkType: CheckerType
508}) {
509 const { shortUUID, videoName } = options
510 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
511
512 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
513 if (checkType === 'presence') {
514 expect(notification).to.not.be.undefined
515 expect(notification.type).to.equal(notificationType)
516
517 expect(notification.videoBlacklist.video.id).to.be.a('number')
518 checkVideo(notification.videoBlacklist.video, videoName, shortUUID)
519 } else {
520 expect(notification).to.satisfy((n: UserNotification) => {
521 return n === undefined || n.video === undefined || n.video.shortUUID !== shortUUID
522 })
523 }
524 }
525
526 function emailNotificationFinder (email: object) {
527 const text = email['text']
528 return text.indexOf(shortUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
529 }
530
531 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
532}
533
534async function checkNewBlacklistOnMyVideo (options: CheckerBaseParams & {
535 shortUUID: string
536 videoName: string
537 blacklistType: 'blacklist' | 'unblacklist'
538}) {
539 const { videoName, shortUUID, blacklistType } = options
540 const notificationType = blacklistType === 'blacklist'
541 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
542 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
543
544 function notificationChecker (notification: UserNotification) {
545 expect(notification).to.not.be.undefined
546 expect(notification.type).to.equal(notificationType)
547
548 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
549
550 checkVideo(video, videoName, shortUUID)
551 }
552
553 function emailNotificationFinder (email: object) {
554 const text = email['text']
555 const blacklistText = blacklistType === 'blacklist'
556 ? 'blacklisted'
557 : 'unblacklisted'
558
559 return text.includes(shortUUID) && text.includes(blacklistText)
560 }
561
562 await checkNotification({ ...options, notificationChecker, emailNotificationFinder, checkType: 'presence' })
563}
564
565async function checkNewPeerTubeVersion (options: CheckerBaseParams & {
566 latestVersion: string
567 checkType: CheckerType
568}) {
569 const { latestVersion } = options
570 const notificationType = UserNotificationType.NEW_PEERTUBE_VERSION
571
572 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
573 if (checkType === 'presence') {
574 expect(notification).to.not.be.undefined
575 expect(notification.type).to.equal(notificationType)
576
577 expect(notification.peertube).to.exist
578 expect(notification.peertube.latestVersion).to.equal(latestVersion)
579 } else {
580 expect(notification).to.satisfy((n: UserNotification) => {
581 return n === undefined || n.peertube === undefined || n.peertube.latestVersion !== latestVersion
582 })
583 }
584 }
585
586 function emailNotificationFinder (email: object) {
587 const text = email['text']
588
589 return text.includes(latestVersion)
590 }
591
592 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
593}
594
595async function checkNewPluginVersion (options: CheckerBaseParams & {
596 pluginType: PluginType
597 pluginName: string
598 checkType: CheckerType
599}) {
600 const { pluginName, pluginType } = options
601 const notificationType = UserNotificationType.NEW_PLUGIN_VERSION
602
603 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
604 if (checkType === 'presence') {
605 expect(notification).to.not.be.undefined
606 expect(notification.type).to.equal(notificationType)
607
608 expect(notification.plugin.name).to.equal(pluginName)
609 expect(notification.plugin.type).to.equal(pluginType)
610 } else {
611 expect(notification).to.satisfy((n: UserNotification) => {
612 return n === undefined || n.plugin === undefined || n.plugin.name !== pluginName
613 })
614 }
615 }
616
617 function emailNotificationFinder (email: object) {
618 const text = email['text']
619
620 return text.includes(pluginName)
621 }
622
623 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
624}
625
626async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) {
627 const userNotifications: UserNotification[] = []
628 const adminNotifications: UserNotification[] = []
629 const adminNotificationsServer2: UserNotification[] = []
630 const emails: object[] = []
631
632 const port = await MockSmtpServer.Instance.collectEmails(emails)
633
634 const overrideConfig = {
635 smtp: {
636 hostname: 'localhost',
637 port
638 },
639 signup: {
640 limit: 20
641 }
642 }
643 const servers = await createMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg))
644
645 await setAccessTokensToServers(servers)
646
647 if (serversCount > 1) {
648 await doubleFollow(servers[0], servers[1])
649 }
650
651 const user = { username: 'user_1', password: 'super password' }
652 await servers[0].users.create({ ...user, videoQuota: 10 * 1000 * 1000 })
653 const userAccessToken = await servers[0].login.getAccessToken(user)
654
655 await servers[0].notifications.updateMySettings({ token: userAccessToken, settings: getAllNotificationsSettings() })
656 await servers[0].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
657
658 if (serversCount > 1) {
659 await servers[1].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
660 }
661
662 {
663 const socket = servers[0].socketIO.getUserNotificationSocket({ token: userAccessToken })
664 socket.on('new-notification', n => userNotifications.push(n))
665 }
666 {
667 const socket = servers[0].socketIO.getUserNotificationSocket()
668 socket.on('new-notification', n => adminNotifications.push(n))
669 }
670
671 if (serversCount > 1) {
672 const socket = servers[1].socketIO.getUserNotificationSocket()
673 socket.on('new-notification', n => adminNotificationsServer2.push(n))
674 }
675
676 const { videoChannels } = await servers[0].users.getMyInfo()
677 const channelId = videoChannels[0].id
678
679 return {
680 userNotifications,
681 adminNotifications,
682 adminNotificationsServer2,
683 userAccessToken,
684 emails,
685 servers,
686 channelId
687 }
688}
689
690// ---------------------------------------------------------------------------
691
692export {
693 getAllNotificationsSettings,
694
695 CheckerBaseParams,
696 CheckerType,
697 checkMyVideoImportIsFinished,
698 checkUserRegistered,
699 checkAutoInstanceFollowing,
700 checkVideoIsPublished,
701 checkNewVideoFromSubscription,
702 checkNewActorFollow,
703 checkNewCommentOnMyVideo,
704 checkNewBlacklistOnMyVideo,
705 checkCommentMention,
706 checkNewVideoAbuseForModerators,
707 checkVideoAutoBlacklistForModerators,
708 checkNewAbuseMessage,
709 checkAbuseStateChange,
710 checkNewInstanceFollower,
711 prepareNotificationsTest,
712 checkNewCommentAbuseForModerators,
713 checkNewAccountAbuseForModerators,
714 checkNewPeerTubeVersion,
715 checkNewPluginVersion
716}
717
718// ---------------------------------------------------------------------------
719
720async function checkNotification (options: CheckerBaseParams & {
721 notificationChecker: (notification: UserNotification, checkType: CheckerType) => void
722 emailNotificationFinder: (email: object) => boolean
723 checkType: CheckerType
724}) {
725 const { server, token, checkType, notificationChecker, emailNotificationFinder, socketNotifications, emails } = options
726
727 const check = options.check || { web: true, mail: true }
728
729 if (check.web) {
730 const notification = await server.notifications.getLatest({ token: token })
731
732 if (notification || checkType !== 'absence') {
733 notificationChecker(notification, checkType)
734 }
735
736 const socketNotification = socketNotifications.find(n => {
737 try {
738 notificationChecker(n, 'presence')
739 return true
740 } catch {
741 return false
742 }
743 })
744
745 if (checkType === 'presence') {
746 const obj = inspect(socketNotifications, { depth: 5 })
747 expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined
748 } else {
749 const obj = inspect(socketNotification, { depth: 5 })
750 expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined
751 }
752 }
753
754 if (check.mail) {
755 // Last email
756 const email = emails
757 .slice()
758 .reverse()
759 .find(e => emailNotificationFinder(e))
760
761 if (checkType === 'presence') {
762 const texts = emails.map(e => e.text)
763 expect(email, 'The email is absent when is should be present. ' + inspect(texts)).to.not.be.undefined
764 } else {
765 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
766 }
767 }
768}
769
770function checkVideo (video: any, videoName?: string, shortUUID?: string) {
771 if (videoName) {
772 expect(video.name).to.be.a('string')
773 expect(video.name).to.not.be.empty
774 expect(video.name).to.equal(videoName)
775 }
776
777 if (shortUUID) {
778 expect(video.shortUUID).to.be.a('string')
779 expect(video.shortUUID).to.not.be.empty
780 expect(video.shortUUID).to.equal(shortUUID)
781 }
782
783 expect(video.id).to.be.a('number')
784}
785
786function checkActor (actor: any) {
787 expect(actor.displayName).to.be.a('string')
788 expect(actor.displayName).to.not.be.empty
789 expect(actor.host).to.not.be.undefined
790}
791
792function checkComment (comment: any, commentId: number, threadId: number) {
793 expect(comment.id).to.equal(commentId)
794 expect(comment.threadId).to.equal(threadId)
795}
diff --git a/shared/server-commands/users/users-command.ts b/shared/server-commands/users/users-command.ts
index 90c5f2183..b5ae9008e 100644
--- a/shared/server-commands/users/users-command.ts
+++ b/shared/server-commands/users/users-command.ts
@@ -4,6 +4,7 @@ import {
4 HttpStatusCode, 4 HttpStatusCode,
5 MyUser, 5 MyUser,
6 ResultList, 6 ResultList,
7 ScopedToken,
7 User, 8 User,
8 UserAdminFlag, 9 UserAdminFlag,
9 UserCreateResult, 10 UserCreateResult,
@@ -13,7 +14,6 @@ import {
13 UserVideoQuota, 14 UserVideoQuota,
14 UserVideoRate 15 UserVideoRate
15} from '@shared/models' 16} from '@shared/models'
16import { ScopedToken } from '@shared/models/users/user-scoped-token'
17import { unwrapBody } from '../requests' 17import { unwrapBody } from '../requests'
18import { AbstractCommand, OverrideCommandOptions } from '../shared' 18import { AbstractCommand, OverrideCommandOptions } from '../shared'
19 19
diff --git a/shared/server-commands/videos/blacklist-command.ts b/shared/server-commands/videos/blacklist-command.ts
index 3a2ef89ba..47e23ebc8 100644
--- a/shared/server-commands/videos/blacklist-command.ts
+++ b/shared/server-commands/videos/blacklist-command.ts
@@ -1,6 +1,5 @@
1 1
2import { HttpStatusCode, ResultList } from '@shared/models' 2import { HttpStatusCode, ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models'
3import { VideoBlacklist, VideoBlacklistType } from '../../models/videos'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 4
6export class BlacklistCommand extends AbstractCommand { 5export class BlacklistCommand extends AbstractCommand {
diff --git a/shared/server-commands/videos/captions-command.ts b/shared/server-commands/videos/captions-command.ts
index a65ea99e3..62bf9c5e6 100644
--- a/shared/server-commands/videos/captions-command.ts
+++ b/shared/server-commands/videos/captions-command.ts
@@ -1,5 +1,5 @@
1import { buildAbsoluteFixturePath } from '@shared/core-utils'
1import { HttpStatusCode, ResultList, VideoCaption } from '@shared/models' 2import { HttpStatusCode, ResultList, VideoCaption } from '@shared/models'
2import { buildAbsoluteFixturePath } from '../miscs'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 4
5export class CaptionsCommand extends AbstractCommand { 5export class CaptionsCommand extends AbstractCommand {
diff --git a/shared/server-commands/videos/captions.ts b/shared/server-commands/videos/captions.ts
deleted file mode 100644
index 35e722408..000000000
--- a/shared/server-commands/videos/captions.ts
+++ /dev/null
@@ -1,21 +0,0 @@
1import { expect } from 'chai'
2import request from 'supertest'
3import { HttpStatusCode } from '@shared/models'
4
5async function testCaptionFile (url: string, captionPath: string, toTest: RegExp | string) {
6 const res = await request(url)
7 .get(captionPath)
8 .expect(HttpStatusCode.OK_200)
9
10 if (toTest instanceof RegExp) {
11 expect(res.text).to.match(toTest)
12 } else {
13 expect(res.text).to.contain(toTest)
14 }
15}
16
17// ---------------------------------------------------------------------------
18
19export {
20 testCaptionFile
21}
diff --git a/shared/server-commands/videos/channels-command.ts b/shared/server-commands/videos/channels-command.ts
index e406e570b..8ab124658 100644
--- a/shared/server-commands/videos/channels-command.ts
+++ b/shared/server-commands/videos/channels-command.ts
@@ -1,7 +1,13 @@
1import { pick } from '@shared/core-utils' 1import { pick } from '@shared/core-utils'
2import { ActorFollow, HttpStatusCode, ResultList, VideoChannel, VideoChannelCreateResult } from '@shared/models' 2import {
3import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' 3 ActorFollow,
4import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' 4 HttpStatusCode,
5 ResultList,
6 VideoChannel,
7 VideoChannelCreate,
8 VideoChannelCreateResult,
9 VideoChannelUpdate
10} from '@shared/models'
5import { unwrapBody } from '../requests' 11import { unwrapBody } from '../requests'
6import { AbstractCommand, OverrideCommandOptions } from '../shared' 12import { AbstractCommand, OverrideCommandOptions } from '../shared'
7 13
diff --git a/shared/server-commands/videos/index.ts b/shared/server-commands/videos/index.ts
index 26e663f46..68a188b21 100644
--- a/shared/server-commands/videos/index.ts
+++ b/shared/server-commands/videos/index.ts
@@ -1,6 +1,5 @@
1export * from './blacklist-command' 1export * from './blacklist-command'
2export * from './captions-command' 2export * from './captions-command'
3export * from './captions'
4export * from './change-ownership-command' 3export * from './change-ownership-command'
5export * from './channels' 4export * from './channels'
6export * from './channels-command' 5export * from './channels-command'
@@ -10,10 +9,7 @@ export * from './imports-command'
10export * from './live-command' 9export * from './live-command'
11export * from './live' 10export * from './live'
12export * from './playlists-command' 11export * from './playlists-command'
13export * from './playlists'
14export * from './services-command' 12export * from './services-command'
15export * from './streaming-playlists-command' 13export * from './streaming-playlists-command'
16export * from './streaming-playlists'
17export * from './comments-command' 14export * from './comments-command'
18export * from './videos-command' 15export * from './videos-command'
19export * from './videos'
diff --git a/shared/server-commands/videos/live-command.ts b/shared/server-commands/videos/live-command.ts
index 74f5d3089..f7816eca0 100644
--- a/shared/server-commands/videos/live-command.ts
+++ b/shared/server-commands/videos/live-command.ts
@@ -3,8 +3,8 @@
3import { readdir } from 'fs-extra' 3import { readdir } from 'fs-extra'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { join } from 'path' 5import { join } from 'path'
6import { wait } from '@shared/core-utils'
6import { HttpStatusCode, LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoCreateResult, VideoDetails, VideoState } from '@shared/models' 7import { HttpStatusCode, LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoCreateResult, VideoDetails, VideoState } from '@shared/models'
7import { wait } from '../miscs'
8import { unwrapBody } from '../requests' 8import { unwrapBody } from '../requests'
9import { AbstractCommand, OverrideCommandOptions } from '../shared' 9import { AbstractCommand, OverrideCommandOptions } from '../shared'
10import { sendRTMPStream, testFfmpegStreamError } from './live' 10import { sendRTMPStream, testFfmpegStreamError } from './live'
diff --git a/shared/server-commands/videos/live.ts b/shared/server-commands/videos/live.ts
index d3665bc90..7a7faa911 100644
--- a/shared/server-commands/videos/live.ts
+++ b/shared/server-commands/videos/live.ts
@@ -1,10 +1,5 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' 1import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg'
5import { pathExists, readdir } from 'fs-extra' 2import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
6import { join } from 'path'
7import { buildAbsoluteFixturePath, wait } from '../miscs'
8import { PeerTubeServer } from '../server/server' 3import { PeerTubeServer } from '../server/server'
9 4
10function sendRTMPStream (options: { 5function sendRTMPStream (options: {
@@ -95,43 +90,11 @@ async function waitUntilLiveSavedOnAllServers (servers: PeerTubeServer[], videoI
95 } 90 }
96} 91}
97 92
98async function checkLiveCleanupAfterSave (server: PeerTubeServer, videoUUID: string, resolutions: number[] = []) {
99 const basePath = server.servers.buildDirectory('streaming-playlists')
100 const hlsPath = join(basePath, 'hls', videoUUID)
101
102 if (resolutions.length === 0) {
103 const result = await pathExists(hlsPath)
104 expect(result).to.be.false
105
106 return
107 }
108
109 const files = await readdir(hlsPath)
110
111 // fragmented file and playlist per resolution + master playlist + segments sha256 json file
112 expect(files).to.have.lengthOf(resolutions.length * 2 + 2)
113
114 for (const resolution of resolutions) {
115 const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`))
116 expect(fragmentedFile).to.exist
117
118 const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`))
119 expect(playlistFile).to.exist
120 }
121
122 const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8'))
123 expect(masterPlaylistFile).to.exist
124
125 const shaFile = files.find(f => f.endsWith('-segments-sha256.json'))
126 expect(shaFile).to.exist
127}
128
129export { 93export {
130 sendRTMPStream, 94 sendRTMPStream,
131 waitFfmpegUntilError, 95 waitFfmpegUntilError,
132 testFfmpegStreamError, 96 testFfmpegStreamError,
133 stopFfmpeg, 97 stopFfmpeg,
134 waitUntilLivePublishedOnAllServers, 98 waitUntilLivePublishedOnAllServers,
135 waitUntilLiveSavedOnAllServers, 99 waitUntilLiveSavedOnAllServers
136 checkLiveCleanupAfterSave
137} 100}
diff --git a/shared/server-commands/videos/playlists.ts b/shared/server-commands/videos/playlists.ts
deleted file mode 100644
index 3dde52bb9..000000000
--- a/shared/server-commands/videos/playlists.ts
+++ /dev/null
@@ -1,25 +0,0 @@
1import { expect } from 'chai'
2import { readdir } from 'fs-extra'
3import { join } from 'path'
4import { root } from '../miscs'
5
6async function checkPlaylistFilesWereRemoved (
7 playlistUUID: string,
8 internalServerNumber: number,
9 directories = [ 'thumbnails' ]
10) {
11 const testDirectory = 'test' + internalServerNumber
12
13 for (const directory of directories) {
14 const directoryPath = join(root(), testDirectory, directory)
15
16 const files = await readdir(directoryPath)
17 for (const file of files) {
18 expect(file).to.not.contain(playlistUUID)
19 }
20 }
21}
22
23export {
24 checkPlaylistFilesWereRemoved
25}
diff --git a/shared/server-commands/videos/streaming-playlists.ts b/shared/server-commands/videos/streaming-playlists.ts
deleted file mode 100644
index 0451c0efe..000000000
--- a/shared/server-commands/videos/streaming-playlists.ts
+++ /dev/null
@@ -1,77 +0,0 @@
1import { expect } from 'chai'
2import { basename } from 'path'
3import { sha256 } from '@shared/core-utils/crypto'
4import { removeFragmentedMP4Ext } from '@shared/core-utils'
5import { HttpStatusCode, VideoStreamingPlaylist } from '@shared/models'
6import { PeerTubeServer } from '../server'
7
8async function checkSegmentHash (options: {
9 server: PeerTubeServer
10 baseUrlPlaylist: string
11 baseUrlSegment: string
12 resolution: number
13 hlsPlaylist: VideoStreamingPlaylist
14}) {
15 const { server, baseUrlPlaylist, baseUrlSegment, resolution, hlsPlaylist } = options
16 const command = server.streamingPlaylists
17
18 const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
19 const videoName = basename(file.fileUrl)
20
21 const playlist = await command.get({ url: `${baseUrlPlaylist}/${removeFragmentedMP4Ext(videoName)}.m3u8` })
22
23 const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
24
25 const length = parseInt(matches[1], 10)
26 const offset = parseInt(matches[2], 10)
27 const range = `${offset}-${offset + length - 1}`
28
29 const segmentBody = await command.getSegment({
30 url: `${baseUrlSegment}/${videoName}`,
31 expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206,
32 range: `bytes=${range}`
33 })
34
35 const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
36 expect(sha256(segmentBody)).to.equal(shaBody[videoName][range])
37}
38
39async function checkLiveSegmentHash (options: {
40 server: PeerTubeServer
41 baseUrlSegment: string
42 videoUUID: string
43 segmentName: string
44 hlsPlaylist: VideoStreamingPlaylist
45}) {
46 const { server, baseUrlSegment, videoUUID, segmentName, hlsPlaylist } = options
47 const command = server.streamingPlaylists
48
49 const segmentBody = await command.getSegment({ url: `${baseUrlSegment}/${videoUUID}/${segmentName}` })
50 const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
51
52 expect(sha256(segmentBody)).to.equal(shaBody[segmentName])
53}
54
55async function checkResolutionsInMasterPlaylist (options: {
56 server: PeerTubeServer
57 playlistUrl: string
58 resolutions: number[]
59}) {
60 const { server, playlistUrl, resolutions } = options
61
62 const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl })
63
64 for (const resolution of resolutions) {
65 const reg = new RegExp(
66 '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"'
67 )
68
69 expect(masterPlaylist).to.match(reg)
70 }
71}
72
73export {
74 checkSegmentHash,
75 checkLiveSegmentHash,
76 checkResolutionsInMasterPlaylist
77}
diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts
index 8ea828b40..ead57b9aa 100644
--- a/shared/server-commands/videos/videos-command.ts
+++ b/shared/server-commands/videos/videos-command.ts
@@ -5,8 +5,7 @@ import { createReadStream, stat } from 'fs-extra'
5import got, { Response as GotResponse } from 'got' 5import got, { Response as GotResponse } from 'got'
6import { omit } from 'lodash' 6import { omit } from 'lodash'
7import validator from 'validator' 7import validator from 'validator'
8import { buildUUID } from '@shared/core-utils/uuid' 8import { buildAbsoluteFixturePath, buildUUID, pick, wait } from '@shared/core-utils'
9import { pick } from '@shared/core-utils'
10import { 9import {
11 HttpStatusCode, 10 HttpStatusCode,
12 ResultList, 11 ResultList,
@@ -20,7 +19,6 @@ import {
20 VideosCommonQuery, 19 VideosCommonQuery,
21 VideoTranscodingCreate 20 VideoTranscodingCreate
22} from '@shared/models' 21} from '@shared/models'
23import { buildAbsoluteFixturePath, wait } from '../miscs'
24import { unwrapBody } from '../requests' 22import { unwrapBody } from '../requests'
25import { waitJobs } from '../server' 23import { waitJobs } from '../server'
26import { AbstractCommand, OverrideCommandOptions } from '../shared' 24import { AbstractCommand, OverrideCommandOptions } from '../shared'
diff --git a/shared/server-commands/videos/videos.ts b/shared/server-commands/videos/videos.ts
deleted file mode 100644
index 2c3464aa8..000000000
--- a/shared/server-commands/videos/videos.ts
+++ /dev/null
@@ -1,104 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { basename, join } from 'path'
6import { HttpStatusCode, VideoCaption, VideoDetails } from '@shared/models'
7import { waitJobs } from '../server'
8import { PeerTubeServer } from '../server/server'
9import { VideoEdit } from './videos-command'
10
11async function checkVideoFilesWereRemoved (options: {
12 server: PeerTubeServer
13 video: VideoDetails
14 captions?: VideoCaption[]
15 onlyVideoFiles?: boolean // default false
16}) {
17 const { video, server, captions = [], onlyVideoFiles = false } = options
18
19 const webtorrentFiles = video.files || []
20 const hlsFiles = video.streamingPlaylists[0]?.files || []
21
22 const thumbnailName = basename(video.thumbnailPath)
23 const previewName = basename(video.previewPath)
24
25 const torrentNames = webtorrentFiles.concat(hlsFiles).map(f => basename(f.torrentUrl))
26
27 const captionNames = captions.map(c => basename(c.captionPath))
28
29 const webtorrentFilenames = webtorrentFiles.map(f => basename(f.fileUrl))
30 const hlsFilenames = hlsFiles.map(f => basename(f.fileUrl))
31
32 let directories: { [ directory: string ]: string[] } = {
33 videos: webtorrentFilenames,
34 redundancy: webtorrentFilenames,
35 [join('playlists', 'hls')]: hlsFilenames,
36 [join('redundancy', 'hls')]: hlsFilenames
37 }
38
39 if (onlyVideoFiles !== true) {
40 directories = {
41 ...directories,
42
43 thumbnails: [ thumbnailName ],
44 previews: [ previewName ],
45 torrents: torrentNames,
46 captions: captionNames
47 }
48 }
49
50 for (const directory of Object.keys(directories)) {
51 const directoryPath = server.servers.buildDirectory(directory)
52
53 const directoryExists = await pathExists(directoryPath)
54 if (directoryExists === false) continue
55
56 const existingFiles = await readdir(directoryPath)
57 for (const existingFile of existingFiles) {
58 for (const shouldNotExist of directories[directory]) {
59 expect(existingFile, `File ${existingFile} should not exist in ${directoryPath}`).to.not.contain(shouldNotExist)
60 }
61 }
62 }
63}
64
65async function saveVideoInServers (servers: PeerTubeServer[], uuid: string) {
66 for (const server of servers) {
67 server.store.videoDetails = await server.videos.get({ id: uuid })
68 }
69}
70
71function checkUploadVideoParam (
72 server: PeerTubeServer,
73 token: string,
74 attributes: Partial<VideoEdit>,
75 expectedStatus = HttpStatusCode.OK_200,
76 mode: 'legacy' | 'resumable' = 'legacy'
77) {
78 return mode === 'legacy'
79 ? server.videos.buildLegacyUpload({ token, attributes, expectedStatus })
80 : server.videos.buildResumeUpload({ token, attributes, expectedStatus })
81}
82
83// serverNumber starts from 1
84async function uploadRandomVideoOnServers (
85 servers: PeerTubeServer[],
86 serverNumber: number,
87 additionalParams?: VideoEdit & { prefixName?: string }
88) {
89 const server = servers.find(s => s.serverNumber === serverNumber)
90 const res = await server.videos.randomUpload({ wait: false, additionalParams })
91
92 await waitJobs(servers)
93
94 return res
95}
96
97// ---------------------------------------------------------------------------
98
99export {
100 checkUploadVideoParam,
101 uploadRandomVideoOnServers,
102 checkVideoFilesWereRemoved,
103 saveVideoInServers
104}