aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xscripts/plugin/install.ts11
-rwxr-xr-xscripts/travis.sh3
-rw-r--r--server/helpers/core-utils.ts14
-rw-r--r--server/helpers/ffmpeg-utils.ts25
-rw-r--r--server/lib/plugins/yarn.ts6
-rw-r--r--server/tests/api/check-params/users.ts13
-rw-r--r--server/tests/api/server/index.ts1
-rw-r--r--server/tests/api/server/plugins.ts130
-rw-r--r--server/tests/api/users/users.ts19
-rw-r--r--server/tests/cli/index.ts1
-rw-r--r--server/tests/cli/peertube.ts202
-rw-r--r--server/tests/cli/plugins.ts87
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js39
-rw-r--r--server/tests/fixtures/peertube-plugin-test/package.json19
-rw-r--r--server/tests/index.ts1
-rw-r--r--server/tests/plugins/action-hooks.ts27
-rw-r--r--server/tests/plugins/filter-hooks.ts27
-rw-r--r--server/tests/plugins/index.ts2
-rw-r--r--server/tools/peertube.ts2
-rw-r--r--shared/extra-utils/users/users.ts21
-rw-r--r--shared/models/videos/video-resolution.enum.ts53
-rw-r--r--support/doc/tools.md34
22 files changed, 589 insertions, 148 deletions
diff --git a/scripts/plugin/install.ts b/scripts/plugin/install.ts
index 1725cbeb6..5d7fe4ba0 100755
--- a/scripts/plugin/install.ts
+++ b/scripts/plugin/install.ts
@@ -4,21 +4,16 @@ import { PluginManager } from '../../server/lib/plugins/plugin-manager'
4import { isAbsolute } from 'path' 4import { isAbsolute } from 'path'
5 5
6program 6program
7 .option('-n, --plugin-name [pluginName]', 'Plugin name to install') 7 .option('-n, --npm-name [npmName]', 'Plugin to install')
8 .option('-v, --plugin-version [pluginVersion]', 'Plugin version to install') 8 .option('-v, --plugin-version [pluginVersion]', 'Plugin version to install')
9 .option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install') 9 .option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install')
10 .parse(process.argv) 10 .parse(process.argv)
11 11
12if (!program['pluginName'] && !program['pluginPath']) { 12if (!program['npmName'] && !program['pluginPath']) {
13 console.error('You need to specify a plugin name with the desired version, or a plugin path.') 13 console.error('You need to specify a plugin name with the desired version, or a plugin path.')
14 process.exit(-1) 14 process.exit(-1)
15} 15}
16 16
17if (program['pluginName'] && !program['pluginVersion']) {
18 console.error('You need to specify a the version of the plugin you want to install.')
19 process.exit(-1)
20}
21
22if (program['pluginPath'] && !isAbsolute(program['pluginPath'])) { 17if (program['pluginPath'] && !isAbsolute(program['pluginPath'])) {
23 console.error('Plugin path should be absolute.') 18 console.error('Plugin path should be absolute.')
24 process.exit(-1) 19 process.exit(-1)
@@ -34,6 +29,6 @@ run()
34async function run () { 29async function run () {
35 await initDatabaseModels(true) 30 await initDatabaseModels(true)
36 31
37 const toInstall = program['pluginName'] || program['pluginPath'] 32 const toInstall = program['npmName'] || program['pluginPath']
38 await PluginManager.Instance.install(toInstall, program['pluginVersion'], !!program['pluginPath']) 33 await PluginManager.Instance.install(toInstall, program['pluginVersion'], !!program['pluginPath'])
39} 34}
diff --git a/scripts/travis.sh b/scripts/travis.sh
index 664d9fd6c..42e2329c6 100755
--- a/scripts/travis.sh
+++ b/scripts/travis.sh
@@ -14,7 +14,8 @@ if [ "$1" = "misc" ]; then
14 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/client.ts \ 14 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/client.ts \
15 server/tests/feeds/index.ts \ 15 server/tests/feeds/index.ts \
16 server/tests/misc-endpoints.ts \ 16 server/tests/misc-endpoints.ts \
17 server/tests/helpers/index.ts 17 server/tests/helpers/index.ts \
18 server/tests/plugins/index.ts
18elif [ "$1" = "cli" ]; then 19elif [ "$1" = "cli" ]; then
19 npm run build:server 20 npm run build:server
20 CC=gcc-4.9 CXX=g++-4.9 npm run setup:cli 21 CC=gcc-4.9 CXX=g++-4.9 npm run setup:cli
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 38b6f63f8..9ff67c43a 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -4,7 +4,7 @@
4*/ 4*/
5 5
6import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' 6import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
7import { isAbsolute, join } from 'path' 7import { basename, isAbsolute, join, resolve } from 'path'
8import * as pem from 'pem' 8import * as pem from 'pem'
9import { URL } from 'url' 9import { URL } from 'url'
10import { truncate } from 'lodash' 10import { truncate } from 'lodash'
@@ -136,16 +136,16 @@ function getAppNumber () {
136 return process.env.NODE_APP_INSTANCE 136 return process.env.NODE_APP_INSTANCE
137} 137}
138 138
139let rootPath: string
139function root () { 140function root () {
141 if (rootPath) return rootPath
142
140 // We are in /helpers/utils.js 143 // We are in /helpers/utils.js
141 const paths = [ __dirname, '..', '..' ] 144 rootPath = join(__dirname, '..', '..')
142 145
143 // We are under /dist directory 146 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
144 if (process.mainModule && process.mainModule.filename.endsWith('_mocha') === false) {
145 paths.push('..')
146 }
147 147
148 return join.apply(null, paths) 148 return rootPath
149} 149}
150 150
151// Thanks: https://stackoverflow.com/a/12034334 151// Thanks: https://stackoverflow.com/a/12034334
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 8041e7b3b..914ecc51a 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -387,14 +387,15 @@ namespace audio {
387 export namespace bitrate { 387 export namespace bitrate {
388 const baseKbitrate = 384 388 const baseKbitrate = 384
389 389
390 const toBits = (kbits: number): number => { return kbits * 8000 } 390 const toBits = (kbits: number) => kbits * 8000
391 391
392 export const aac = (bitrate: number): number => { 392 export const aac = (bitrate: number): number => {
393 switch (true) { 393 switch (true) {
394 case bitrate > toBits(baseKbitrate): 394 case bitrate > toBits(baseKbitrate):
395 return baseKbitrate 395 return baseKbitrate
396 default: 396
397 return -1 // we interpret it as a signal to copy the audio stream as is 397 default:
398 return -1 // we interpret it as a signal to copy the audio stream as is
398 } 399 }
399 } 400 }
400 401
@@ -405,12 +406,14 @@ namespace audio {
405 made here are not made to be accurate, especially with good mp3 encoders. 406 made here are not made to be accurate, especially with good mp3 encoders.
406 */ 407 */
407 switch (true) { 408 switch (true) {
408 case bitrate <= toBits(192): 409 case bitrate <= toBits(192):
409 return 128 410 return 128
410 case bitrate <= toBits(384): 411
411 return 256 412 case bitrate <= toBits(384):
412 default: 413 return 256
413 return baseKbitrate 414
415 default:
416 return baseKbitrate
414 } 417 }
415 } 418 }
416 } 419 }
diff --git a/server/lib/plugins/yarn.ts b/server/lib/plugins/yarn.ts
index 5fe1c5046..74c67653c 100644
--- a/server/lib/plugins/yarn.ts
+++ b/server/lib/plugins/yarn.ts
@@ -5,12 +5,12 @@ import { CONFIG } from '../../initializers/config'
5import { outputJSON, pathExists } from 'fs-extra' 5import { outputJSON, pathExists } from 'fs-extra'
6import { join } from 'path' 6import { join } from 'path'
7 7
8async function installNpmPlugin (name: string, version?: string) { 8async function installNpmPlugin (npmName: string, version?: string) {
9 // Security check 9 // Security check
10 checkNpmPluginNameOrThrow(name) 10 checkNpmPluginNameOrThrow(npmName)
11 if (version) checkPluginVersionOrThrow(version) 11 if (version) checkPluginVersionOrThrow(version)
12 12
13 let toInstall = name 13 let toInstall = npmName
14 if (version) toInstall += `@${version}` 14 if (version) toInstall += `@${version}`
15 15
16 await execYarn('add ' + toInstall) 16 await execYarn('add ' + toInstall)
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5d62fe2b3..5b788e328 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -387,13 +387,24 @@ describe('Test users API validators', function () {
387 } 387 }
388 }) 388 })
389 389
390 it('Should fail with an invalid theme', async function () {
391 const fields = { theme: 'invalid' }
392 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
393 })
394
395 it('Should fail with an unknown theme', async function () {
396 const fields = { theme: 'peertube-theme-unknown' }
397 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
398 })
399
390 it('Should succeed to change password with the correct params', async function () { 400 it('Should succeed to change password with the correct params', async function () {
391 const fields = { 401 const fields = {
392 currentPassword: 'my super password', 402 currentPassword: 'my super password',
393 password: 'my super password', 403 password: 'my super password',
394 nsfwPolicy: 'blur', 404 nsfwPolicy: 'blur',
395 autoPlayVideo: false, 405 autoPlayVideo: false,
396 email: 'super_email@example.com' 406 email: 'super_email@example.com',
407 theme: 'default'
397 } 408 }
398 409
399 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) 410 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 })
diff --git a/server/tests/api/server/index.ts b/server/tests/api/server/index.ts
index 94c15e0d0..3daeeb49a 100644
--- a/server/tests/api/server/index.ts
+++ b/server/tests/api/server/index.ts
@@ -11,3 +11,4 @@ import './reverse-proxy'
11import './stats' 11import './stats'
12import './tracker' 12import './tracker'
13import './no-client' 13import './no-client'
14import './plugins'
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
new file mode 100644
index 000000000..9a623c553
--- /dev/null
+++ b/server/tests/api/server/plugins.ts
@@ -0,0 +1,130 @@
1/* tslint:disable:no-unused-expression */
2
3import 'mocha'
4import * as chai from 'chai'
5import { About } from '../../../../shared/models/server/about.model'
6import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
7import {
8 cleanupTests,
9 deleteCustomConfig,
10 flushAndRunServer,
11 getAbout,
12 getConfig,
13 getCustomConfig, installPlugin,
14 killallServers, parallelTests,
15 registerUser,
16 reRunServer, ServerInfo,
17 setAccessTokensToServers,
18 updateCustomConfig, uploadVideo
19} from '../../../../shared/extra-utils'
20import { ServerConfig } from '../../../../shared/models'
21import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model'
22
23const expect = chai.expect
24
25describe('Test plugins', function () {
26 let server = null
27
28 before(async function () {
29 this.timeout(30000)
30
31 server = await flushAndRunServer(1)
32 await setAccessTokensToServers([ server ])
33
34 {
35 await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-hello-world' })
36 }
37
38 {
39 await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-background-color' })
40 }
41 })
42
43 it('Should list available plugins and themes', async function () {
44 // List without filter
45 // List with filter (plugin and theme)
46 })
47
48 it('Should search available plugins', async function () {
49 // Search with filter (plugin and theme)
50 // Add pagination
51 // Add sort
52 // Add peertube engine
53 })
54
55 it('Should have an empty global css', async function () {
56 // get /global.css
57 })
58
59 it('Should install a plugin and a theme', async function () {
60
61 })
62
63 it('Should have the correct global css', async function () {
64 // get /global.css
65 })
66
67 it('Should have the plugin loaded in the configuration', async function () {
68 // Check registered themes/plugins
69 })
70
71 it('Should update the default theme in the configuration', async function () {
72 // Update config
73 })
74
75 it('Should list plugins and themes', async function () {
76 // List without filter
77 // List with filter (theme/plugin)
78 // List with pagination
79 // List with sort
80 })
81
82 it('Should get a plugin and a theme', async function () {
83 // Get plugin
84 // Get theme
85 })
86
87 it('Should get registered settings', async function () {
88 // Get plugin
89 })
90
91 it('Should update the settings', async function () {
92 // Update /settings
93
94 // get /plugin
95 })
96
97 it('Should update the plugin and the theme', async function () {
98 // update BDD -> 0.0.1
99 // update package.json (theme + plugin)
100 // list to check versions
101 // update plugin + theme
102 // list to check they have been updated
103 // check package.json are upgraded too
104 })
105
106 it('Should uninstall the plugin', async function () {
107 // uninstall
108 // list
109 })
110
111 it('Should have an empty global css', async function () {
112 // get /global.css
113 })
114
115 it('Should list uninstalled plugins', async function () {
116 // { uninstalled: true }
117 })
118
119 it('Should uninstall the theme', async function () {
120 // Uninstall
121 })
122
123 it('Should have updated the configuration', async function () {
124 // get /config (default theme + registered themes + registered plugins)
125 })
126
127 after(async function () {
128 await cleanupTests([ server ])
129 })
130})
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 6fc2a070f..3a3fabb4c 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -18,7 +18,7 @@ import {
18 getUsersList, 18 getUsersList,
19 getUsersListPaginationAndSort, 19 getUsersListPaginationAndSort,
20 getVideoChannel, 20 getVideoChannel,
21 getVideosList, 21 getVideosList, installPlugin,
22 login, 22 login,
23 makePutBodyRequest, 23 makePutBodyRequest,
24 rateVideo, 24 rateVideo,
@@ -57,6 +57,8 @@ describe('Test users', function () {
57 server = await flushAndRunServer(1) 57 server = await flushAndRunServer(1)
58 58
59 await setAccessTokensToServers([ server ]) 59 await setAccessTokensToServers([ server ])
60
61 await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-theme-background-red' })
60 }) 62 })
61 63
62 describe('OAuth client', function () { 64 describe('OAuth client', function () {
@@ -551,6 +553,21 @@ describe('Test users', function () {
551 expect(user.account.displayName).to.equal('new display name') 553 expect(user.account.displayName).to.equal('new display name')
552 expect(user.account.description).to.equal('my super description updated') 554 expect(user.account.description).to.equal('my super description updated')
553 }) 555 })
556
557 it('Should be able to update my theme', async function () {
558 for (const theme of [ 'background-red', 'default', 'instance-default' ]) {
559 await updateMyUser({
560 url: server.url,
561 accessToken: accessTokenUser,
562 theme
563 })
564
565 const res = await getMyUserInformation(server.url, accessTokenUser)
566 const body: User = res.body
567
568 expect(body.theme).to.equal(theme)
569 }
570 })
554 }) 571 })
555 572
556 describe('Updating another user', function () { 573 describe('Updating another user', function () {
diff --git a/server/tests/cli/index.ts b/server/tests/cli/index.ts
index c6b7ec078..5af286fe2 100644
--- a/server/tests/cli/index.ts
+++ b/server/tests/cli/index.ts
@@ -3,5 +3,6 @@ import './create-import-video-file-job'
3import './create-transcoding-job' 3import './create-transcoding-job'
4import './optimize-old-videos' 4import './optimize-old-videos'
5import './peertube' 5import './peertube'
6import './plugins'
6import './reset-password' 7import './reset-password'
7import './update-host' 8import './update-host'
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts
index d73e27564..b8c0b1f79 100644
--- a/server/tests/cli/peertube.ts
+++ b/server/tests/cli/peertube.ts
@@ -43,133 +43,171 @@ describe('Test CLI wrapper', function () {
43 } 43 }
44 }) 44 })
45 45
46 it('Should display no selected instance', async function () { 46 describe('Authentication and instance selection', function () {
47 this.timeout(60000)
48 47
49 const env = getEnvCli(server) 48 it('Should display no selected instance', async function () {
50 const stdout = await execCLI(`${env} ${cmd} --help`) 49 this.timeout(60000)
51 50
52 expect(stdout).to.contain('no instance selected') 51 const env = getEnvCli(server)
53 }) 52 const stdout = await execCLI(`${env} ${cmd} --help`)
54 53
55 it('Should add a user', async function () { 54 expect(stdout).to.contain('no instance selected')
56 this.timeout(60000) 55 })
57 56
58 const env = getEnvCli(server) 57 it('Should add a user', async function () {
59 await execCLI(`${env} ${cmd} auth add -u ${server.url} -U user_1 -p super_password`) 58 this.timeout(60000)
60 })
61 59
62 it('Should default to this user', async function () { 60 const env = getEnvCli(server)
63 this.timeout(60000) 61 await execCLI(`${env} ${cmd} auth add -u ${server.url} -U user_1 -p super_password`)
62 })
64 63
65 const env = getEnvCli(server) 64 it('Should default to this user', async function () {
66 const stdout = await execCLI(`${env} ${cmd} --help`) 65 this.timeout(60000)
67 66
68 expect(stdout).to.contain(`instance ${server.url} selected`) 67 const env = getEnvCli(server)
69 }) 68 const stdout = await execCLI(`${env} ${cmd} --help`)
70 69
71 it('Should remember the user', async function () { 70 expect(stdout).to.contain(`instance ${server.url} selected`)
72 this.timeout(60000) 71 })
73 72
74 const env = getEnvCli(server) 73 it('Should remember the user', async function () {
75 const stdout = await execCLI(`${env} ${cmd} auth list`) 74 this.timeout(60000)
76 75
77 expect(stdout).to.contain(server.url) 76 const env = getEnvCli(server)
77 const stdout = await execCLI(`${env} ${cmd} auth list`)
78
79 expect(stdout).to.contain(server.url)
80 })
78 }) 81 })
79 82
80 it('Should upload a video', async function () { 83 describe('Video upload/import', function () {
81 this.timeout(60000)
82 84
83 const env = getEnvCli(server) 85 it('Should upload a video', async function () {
86 this.timeout(60000)
84 87
85 const fixture = buildAbsoluteFixturePath('60fps_720p_small.mp4') 88 const env = getEnvCli(server)
86 89
87 const params = `-f ${fixture} --video-name 'test upload' --channel-name user_channel --support 'support_text'` 90 const fixture = buildAbsoluteFixturePath('60fps_720p_small.mp4')
88 91
89 await execCLI(`${env} ${cmd} upload ${params}`) 92 const params = `-f ${fixture} --video-name 'test upload' --channel-name user_channel --support 'support_text'`
90 })
91 93
92 it('Should have the video uploaded', async function () { 94 await execCLI(`${env} ${cmd} upload ${params}`)
93 const res = await getVideosList(server.url) 95 })
94 96
95 expect(res.body.total).to.equal(1) 97 it('Should have the video uploaded', async function () {
98 const res = await getVideosList(server.url)
96 99
97 const videos: Video[] = res.body.data 100 expect(res.body.total).to.equal(1)
98 101
99 const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body 102 const videos: Video[] = res.body.data
100 103
101 expect(video.name).to.equal('test upload') 104 const video: VideoDetails = (await getVideo(server.url, videos[ 0 ].uuid)).body
102 expect(video.support).to.equal('support_text')
103 expect(video.channel.name).to.equal('user_channel')
104 })
105 105
106 it('Should import a video', async function () { 106 expect(video.name).to.equal('test upload')
107 this.timeout(60000) 107 expect(video.support).to.equal('support_text')
108 expect(video.channel.name).to.equal('user_channel')
109 })
108 110
109 const env = getEnvCli(server) 111 it('Should import a video', async function () {
112 this.timeout(60000)
110 113
111 const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel` 114 const env = getEnvCli(server)
112 115
113 await execCLI(`${env} ${cmd} import ${params}`) 116 const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel`
114 })
115 117
116 it('Should have imported the video', async function () { 118 await execCLI(`${env} ${cmd} import ${params}`)
117 this.timeout(60000) 119 })
118 120
119 await waitJobs([ server ]) 121 it('Should have imported the video', async function () {
122 this.timeout(60000)
120 123
121 const res = await getVideosList(server.url) 124 await waitJobs([ server ])
122 125
123 expect(res.body.total).to.equal(2) 126 const res = await getVideosList(server.url)
124 127
125 const videos: Video[] = res.body.data 128 expect(res.body.total).to.equal(2)
126 const video = videos.find(v => v.name === 'small video - youtube')
127 expect(video).to.not.be.undefined
128 129
129 const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body 130 const videos: Video[] = res.body.data
130 expect(videoDetails.channel.name).to.equal('user_channel') 131 const video = videos.find(v => v.name === 'small video - youtube')
131 expect(videoDetails.support).to.equal('super support text') 132 expect(video).to.not.be.undefined
132 expect(videoDetails.nsfw).to.be.false
133 133
134 // So we can reimport it 134 const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
135 await removeVideo(server.url, userAccessToken, video.id) 135 expect(videoDetails.channel.name).to.equal('user_channel')
136 }) 136 expect(videoDetails.support).to.equal('super support text')
137 expect(videoDetails.nsfw).to.be.false
137 138
138 it('Should import and override some imported attributes', async function () { 139 // So we can reimport it
139 this.timeout(60000) 140 await removeVideo(server.url, userAccessToken, video.id)
141 })
140 142
141 const env = getEnvCli(server) 143 it('Should import and override some imported attributes', async function () {
144 this.timeout(60000)
142 145
143 const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel --video-name toto --nsfw --support support` 146 const env = getEnvCli(server)
144 147
145 await execCLI(`${env} ${cmd} import ${params}`) 148 const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel --video-name toto --nsfw --support support`
146 149
147 await waitJobs([ server ]) 150 await execCLI(`${env} ${cmd} import ${params}`)
148 151
149 { 152 await waitJobs([ server ])
150 const res = await getVideosList(server.url)
151 expect(res.body.total).to.equal(2)
152 153
153 const videos: Video[] = res.body.data 154 {
154 const video = videos.find(v => v.name === 'toto') 155 const res = await getVideosList(server.url)
155 expect(video).to.not.be.undefined 156 expect(res.body.total).to.equal(2)
156 157
157 const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body 158 const videos: Video[] = res.body.data
158 expect(videoDetails.channel.name).to.equal('user_channel') 159 const video = videos.find(v => v.name === 'toto')
159 expect(videoDetails.support).to.equal('support') 160 expect(video).to.not.be.undefined
160 expect(videoDetails.nsfw).to.be.true 161
161 expect(videoDetails.commentsEnabled).to.be.true 162 const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
162 } 163 expect(videoDetails.channel.name).to.equal('user_channel')
164 expect(videoDetails.support).to.equal('support')
165 expect(videoDetails.nsfw).to.be.true
166 expect(videoDetails.commentsEnabled).to.be.true
167 }
168 })
163 }) 169 })
164 170
165 it('Should remove the auth user', async function () { 171 describe('Admin auth', function () {
166 const env = getEnvCli(server) 172
173 it('Should remove the auth user', async function () {
174 const env = getEnvCli(server)
175
176 await execCLI(`${env} ${cmd} auth del ${server.url}`)
177
178 const stdout = await execCLI(`${env} ${cmd} --help`)
179
180 expect(stdout).to.contain('no instance selected')
181 })
182
183 it('Should add the admin user', async function () {
184 const env = getEnvCli(server)
185 await execCLI(`${env} ${cmd} auth add -u ${server.url} -U root -p test${server.internalServerNumber}`)
186 })
187 })
188
189 describe('Manage plugins', function () {
190
191 it('Should install a plugin', async function () {
192 this.timeout(60000)
193
194 const env = getEnvCli(server)
195 await execCLI(`${env} ${cmd} plugins install --npm-name peertube-plugin-hello-world`)
196 })
197
198 it('Should list installed plugins', async function () {
199 const env = getEnvCli(server)
200 const res = await execCLI(`${env} ${cmd} plugins list`)
167 201
168 await execCLI(`${env} ${cmd} auth del ${server.url}`) 202 expect(res).to.contain('peertube-plugin-hello-world')
203 })
169 204
170 const stdout = await execCLI(`${env} ${cmd} --help`) 205 it('Should uninstall the plugin', async function () {
206 const env = getEnvCli(server)
207 const res = await execCLI(`${env} ${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`)
171 208
172 expect(stdout).to.contain('no instance selected') 209 expect(res).to.not.contain('peertube-plugin-hello-world')
210 })
173 }) 211 })
174 212
175 after(async function () { 213 after(async function () {
diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts
new file mode 100644
index 000000000..d7bf8a690
--- /dev/null
+++ b/server/tests/cli/plugins.ts
@@ -0,0 +1,87 @@
1/* tslint:disable:no-unused-expression */
2
3import 'mocha'
4import {
5 cleanupTests,
6 execCLI,
7 flushAndRunServer,
8 getConfig,
9 getEnvCli, killallServers,
10 reRunServer,
11 root,
12 ServerInfo,
13 setAccessTokensToServers
14} from '../../../shared/extra-utils'
15import { join } from 'path'
16import { ServerConfig } from '../../../shared/models/server'
17import { expect } from 'chai'
18
19describe('Test plugin scripts', function () {
20 let server: ServerInfo
21
22 before(async function () {
23 this.timeout(30000)
24
25 server = await flushAndRunServer(1)
26 await setAccessTokensToServers([ server ])
27 })
28
29 it('Should install a plugin from stateless CLI', async function () {
30 this.timeout(60000)
31
32 const packagePath = join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test')
33
34 const env = getEnvCli(server)
35 await execCLI(`${env} npm run plugin:install -- --plugin-path ${packagePath}`)
36 })
37
38 it('Should install a theme from stateless CLI', async function () {
39 this.timeout(60000)
40
41 const env = getEnvCli(server)
42 await execCLI(`${env} npm run plugin:install -- --npm-name peertube-theme-background-red`)
43 })
44
45 it('Should have the theme and the plugin registered when we restart peertube', async function () {
46 this.timeout(30000)
47
48 killallServers([ server ])
49 await reRunServer(server)
50
51 const res = await getConfig(server.url)
52 const config: ServerConfig = res.body
53
54 const plugin = config.plugin.registered
55 .find(p => p.name === 'test')
56 expect(plugin).to.not.be.undefined
57
58 const theme = config.theme.registered
59 .find(t => t.name === 'background-red')
60 expect(theme).to.not.be.undefined
61 })
62
63 it('Should uninstall a plugin from stateless CLI', async function () {
64 this.timeout(60000)
65
66 const env = getEnvCli(server)
67 await execCLI(`${env} npm run plugin:uninstall -- --npm-name peertube-plugin-test`)
68 })
69
70 it('Should have removed the plugin on another peertube restart', async function () {
71 this.timeout(30000)
72
73 killallServers([ server ])
74 await reRunServer(server)
75
76 const res = await getConfig(server.url)
77 const config: ServerConfig = res.body
78
79 const plugin = config.plugin.registered
80 .find(p => p.name === 'test')
81 expect(plugin).to.be.undefined
82 })
83
84 after(async function () {
85 await cleanupTests([ server ])
86 })
87})
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
new file mode 100644
index 000000000..fae0ef948
--- /dev/null
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -0,0 +1,39 @@
1async function register ({ registerHook, registerSetting, settingsManager, storageManager }) {
2 const defaultAdmin = 'PeerTube admin'
3
4 registerHook({
5 target: 'action:application.listening',
6 handler: () => displayHelloWorld(settingsManager, defaultAdmin)
7 })
8
9 registerSetting({
10 name: 'admin-name',
11 label: 'Admin name',
12 type: 'input',
13 default: defaultAdmin
14 })
15
16 const value = await storageManager.getData('toto')
17 console.log(value)
18 console.log(value.coucou)
19
20 await storageManager.storeData('toto', { coucou: 'hello' + new Date() })
21}
22
23async function unregister () {
24 return
25}
26
27module.exports = {
28 register,
29 unregister
30}
31
32// ############################################################################
33
34async function displayHelloWorld (settingsManager, defaultAdmin) {
35 let value = await settingsManager.getSetting('admin-name')
36 if (!value) value = defaultAdmin
37
38 console.log('hello world ' + value)
39}
diff --git a/server/tests/fixtures/peertube-plugin-test/package.json b/server/tests/fixtures/peertube-plugin-test/package.json
new file mode 100644
index 000000000..9d6fe5c90
--- /dev/null
+++ b/server/tests/fixtures/peertube-plugin-test/package.json
@@ -0,0 +1,19 @@
1{
2 "name": "peertube-plugin-test",
3 "version": "0.0.1",
4 "description": "Plugin test",
5 "engine": {
6 "peertube": ">=1.3.0"
7 },
8 "keywords": [
9 "peertube",
10 "plugin"
11 ],
12 "homepage": "https://github.com/Chocobozzz/PeerTube",
13 "author": "Chocobozzz",
14 "bugs": "https://github.com/Chocobozzz/PeerTube/issues",
15 "library": "./main.js",
16 "staticDirs": {},
17 "css": [],
18 "clientScripts": []
19}
diff --git a/server/tests/index.ts b/server/tests/index.ts
index ed16d65dd..8bddcfc7c 100644
--- a/server/tests/index.ts
+++ b/server/tests/index.ts
@@ -3,3 +3,4 @@ import './client'
3import './feeds/' 3import './feeds/'
4import './cli/' 4import './cli/'
5import './api/' 5import './api/'
6import './plugins/'
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
new file mode 100644
index 000000000..8abab98c2
--- /dev/null
+++ b/server/tests/plugins/action-hooks.ts
@@ -0,0 +1,27 @@
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import 'mocha'
5import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
6import { setAccessTokensToServers } from '../../../shared/extra-utils'
7
8const expect = chai.expect
9
10describe('Test plugin filter hooks', function () {
11 let server: ServerInfo
12
13 before(async function () {
14 this.timeout(30000)
15 server = await flushAndRunServer(1)
16
17 await setAccessTokensToServers([ server ])
18 })
19
20 it('Should execute ', async function () {
21
22 })
23
24 after(async function () {
25 await cleanupTests([ server ])
26 })
27})
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
new file mode 100644
index 000000000..8abab98c2
--- /dev/null
+++ b/server/tests/plugins/filter-hooks.ts
@@ -0,0 +1,27 @@
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import 'mocha'
5import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
6import { setAccessTokensToServers } from '../../../shared/extra-utils'
7
8const expect = chai.expect
9
10describe('Test plugin filter hooks', function () {
11 let server: ServerInfo
12
13 before(async function () {
14 this.timeout(30000)
15 server = await flushAndRunServer(1)
16
17 await setAccessTokensToServers([ server ])
18 })
19
20 it('Should execute ', async function () {
21
22 })
23
24 after(async function () {
25 await cleanupTests([ server ])
26 })
27})
diff --git a/server/tests/plugins/index.ts b/server/tests/plugins/index.ts
new file mode 100644
index 000000000..b640ecc9e
--- /dev/null
+++ b/server/tests/plugins/index.ts
@@ -0,0 +1,2 @@
1export * from './action-hooks'
2export * from './filter-hooks'
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index e79a7e041..ddfe5b771 100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -18,7 +18,7 @@ program
18 .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') 18 .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
19 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') 19 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
20 .command('repl', 'initiate a REPL to access internals') 20 .command('repl', 'initiate a REPL to access internals')
21 .command('plugins [action]', 'manage plugins on a local instance').alias('p') 21 .command('plugins [action]', 'manage instance plugins/themes').alias('p')
22 22
23/* Not Yet Implemented */ 23/* Not Yet Implemented */
24program 24program
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 5fa8cde0c..30ed1bf4a 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -6,6 +6,7 @@ import { UserRegister } from '../../models/users/user-register.model'
6import { UserRole } from '../../models/users/user-role' 6import { UserRole } from '../../models/users/user-role'
7import { ServerInfo } from '../server/servers' 7import { ServerInfo } from '../server/servers'
8import { userLogin } from './login' 8import { userLogin } from './login'
9import { UserUpdateMe } from '../../models/users'
9 10
10type CreateUserArgs = { url: string, 11type CreateUserArgs = { url: string,
11 accessToken: string, 12 accessToken: string,
@@ -224,19 +225,21 @@ function updateMyUser (options: {
224 displayName?: string 225 displayName?: string
225 description?: string 226 description?: string
226 videosHistoryEnabled?: boolean 227 videosHistoryEnabled?: boolean
228 theme?: string
227}) { 229}) {
228 const path = '/api/v1/users/me' 230 const path = '/api/v1/users/me'
229 231
230 const toSend = {} 232 const toSend: UserUpdateMe = {}
231 if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword 233 if (options.currentPassword !== undefined && options.currentPassword !== null) toSend.currentPassword = options.currentPassword
232 if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword 234 if (options.newPassword !== undefined && options.newPassword !== null) toSend.password = options.newPassword
233 if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy 235 if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend.nsfwPolicy = options.nsfwPolicy
234 if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo 236 if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend.autoPlayVideo = options.autoPlayVideo
235 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email 237 if (options.email !== undefined && options.email !== null) toSend.email = options.email
236 if (options.description !== undefined && options.description !== null) toSend['description'] = options.description 238 if (options.description !== undefined && options.description !== null) toSend.description = options.description
237 if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName 239 if (options.displayName !== undefined && options.displayName !== null) toSend.displayName = options.displayName
240 if (options.theme !== undefined && options.theme !== null) toSend.theme = options.theme
238 if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { 241 if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) {
239 toSend['videosHistoryEnabled'] = options.videosHistoryEnabled 242 toSend.videosHistoryEnabled = options.videosHistoryEnabled
240 } 243 }
241 244
242 return makePutBodyRequest({ 245 return makePutBodyRequest({
diff --git a/shared/models/videos/video-resolution.enum.ts b/shared/models/videos/video-resolution.enum.ts
index 51efa2e8b..fa26fc3cc 100644
--- a/shared/models/videos/video-resolution.enum.ts
+++ b/shared/models/videos/video-resolution.enum.ts
@@ -18,30 +18,35 @@ export enum VideoResolution {
18 */ 18 */
19function getBaseBitrate (resolution: VideoResolution) { 19function getBaseBitrate (resolution: VideoResolution) {
20 switch (resolution) { 20 switch (resolution) {
21 case VideoResolution.H_240P: 21 case VideoResolution.H_240P:
22 // quality according to Google Live Encoder: 300 - 700 Kbps 22 // quality according to Google Live Encoder: 300 - 700 Kbps
23 // Quality according to YouTube Video Info: 186 Kbps 23 // Quality according to YouTube Video Info: 186 Kbps
24 return 250 * 1000 24 return 250 * 1000
25 case VideoResolution.H_360P: 25
26 // quality according to Google Live Encoder: 400 - 1,000 Kbps 26 case VideoResolution.H_360P:
27 // Quality according to YouTube Video Info: 480 Kbps 27 // quality according to Google Live Encoder: 400 - 1,000 Kbps
28 return 500 * 1000 28 // Quality according to YouTube Video Info: 480 Kbps
29 case VideoResolution.H_480P: 29 return 500 * 1000
30 // quality according to Google Live Encoder: 500 - 2,000 Kbps 30
31 // Quality according to YouTube Video Info: 879 Kbps 31 case VideoResolution.H_480P:
32 return 900 * 1000 32 // quality according to Google Live Encoder: 500 - 2,000 Kbps
33 case VideoResolution.H_720P: 33 // Quality according to YouTube Video Info: 879 Kbps
34 // quality according to Google Live Encoder: 1,500 - 4,000 Kbps 34 return 900 * 1000
35 // Quality according to YouTube Video Info: 1752 Kbps 35
36 return 1750 * 1000 36 case VideoResolution.H_720P:
37 case VideoResolution.H_1080P: 37 // quality according to Google Live Encoder: 1,500 - 4,000 Kbps
38 // quality according to Google Live Encoder: 3000 - 6000 Kbps 38 // Quality according to YouTube Video Info: 1752 Kbps
39 // Quality according to YouTube Video Info: 3277 Kbps 39 return 1750 * 1000
40 return 3300 * 1000 40
41 case VideoResolution.H_4K: // fallthrough 41 case VideoResolution.H_1080P:
42 default: 42 // quality according to Google Live Encoder: 3000 - 6000 Kbps
43 // quality according to Google Live Encoder: 13000 - 34000 Kbps 43 // Quality according to YouTube Video Info: 3277 Kbps
44 return 15000 * 1000 44 return 3300 * 1000
45
46 case VideoResolution.H_4K: // fallthrough
47 default:
48 // quality according to Google Live Encoder: 13000 - 34000 Kbps
49 return 15000 * 1000
45 } 50 }
46} 51}
47 52
diff --git a/support/doc/tools.md b/support/doc/tools.md
index ba6e2b12d..f0d3b15b2 100644
--- a/support/doc/tools.md
+++ b/support/doc/tools.md
@@ -75,6 +75,7 @@ You can access it as `peertube` via an alias in your `.bashrc` like `alias peert
75 import-videos|import import a video from a streaming platform 75 import-videos|import import a video from a streaming platform
76 watch|w watch a video in the terminal ✩°。⋆ 76 watch|w watch a video in the terminal ✩°。⋆
77 repl initiate a REPL to access internals 77 repl initiate a REPL to access internals
78 plugins|p [action] manag instance plugins
78 help [cmd] display help for [cmd] 79 help [cmd] display help for [cmd]
79``` 80```
80 81
@@ -102,6 +103,15 @@ And now that your video is online, you can watch it from the confort of your ter
102$ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10 103$ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10
103``` 104```
104 105
106To list, install, uninstall dynamically plugins/themes of an instance:
107
108```bash
109$ peertube plugins list
110$ peertube plugins install --path /local/plugin/path
111$ peertube plugins install --npm-name peertube-plugin-myplugin
112$ peertube plugins uninstall --npm-name peertube-plugin-myplugin
113```
114
105#### peertube-import-videos.js 115#### peertube-import-videos.js
106 116
107You can use this script to import videos from all [supported sites of youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) into PeerTube. 117You can use this script to import videos from all [supported sites of youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) into PeerTube.
@@ -233,6 +243,30 @@ To reset a user password from CLI, run:
233$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u target_username 243$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u target_username
234``` 244```
235 245
246
247### plugin install/uninstall
248
249The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running.
250If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server).
251
252To install a plugin or a theme from the disk:
253
254```
255$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path
256```
257
258From NPM:
259
260```
261$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --npm-name peertube-plugin-myplugin
262```
263
264To uninstall a plugin or a theme:
265
266```
267$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin
268```
269
236### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v8.x/api/repl.html)) 270### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v8.x/api/repl.html))
237 271
238If you want to interact with the application libraries and objects even when PeerTube is not running, there is a REPL for that. 272If you want to interact with the application libraries and objects even when PeerTube is not running, there is a REPL for that.