aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--config/test-1.yaml1
-rw-r--r--config/test-2.yaml1
-rw-r--r--config/test-3.yaml1
-rw-r--r--config/test-4.yaml1
-rw-r--r--config/test-5.yaml1
-rw-r--r--config/test-6.yaml1
-rw-r--r--server/controllers/api/plugins.ts9
-rw-r--r--server/lib/plugins/plugin-index.ts2
-rw-r--r--server/lib/plugins/plugin-manager.ts13
-rw-r--r--server/lib/plugins/yarn.ts6
-rw-r--r--server/middlewares/validators/plugins.ts3
-rw-r--r--server/tests/api/check-params/plugins.ts14
-rw-r--r--server/tests/api/server/plugins.ts389
-rw-r--r--server/tests/plugins/action-hooks.ts4
-rw-r--r--server/tests/plugins/filter-hooks.ts2
-rw-r--r--server/tools/peertube-plugins.ts8
-rw-r--r--shared/extra-utils/miscs/sql.ts10
-rw-r--r--shared/extra-utils/server/plugins.ts90
-rw-r--r--shared/models/plugins/register-setting.model.ts4
19 files changed, 481 insertions, 79 deletions
diff --git a/config/test-1.yaml b/config/test-1.yaml
index 413390ecd..7b25f5cf3 100644
--- a/config/test-1.yaml
+++ b/config/test-1.yaml
@@ -21,6 +21,7 @@ storage:
21 torrents: 'test1/torrents/' 21 torrents: 'test1/torrents/'
22 captions: 'test1/captions/' 22 captions: 'test1/captions/'
23 cache: 'test1/cache/' 23 cache: 'test1/cache/'
24 plugins: 'test1/plugins/'
24 25
25admin: 26admin:
26 email: 'admin1@example.com' 27 email: 'admin1@example.com'
diff --git a/config/test-2.yaml b/config/test-2.yaml
index de7300366..82d4aa35f 100644
--- a/config/test-2.yaml
+++ b/config/test-2.yaml
@@ -21,6 +21,7 @@ storage:
21 torrents: 'test2/torrents/' 21 torrents: 'test2/torrents/'
22 captions: 'test2/captions/' 22 captions: 'test2/captions/'
23 cache: 'test2/cache/' 23 cache: 'test2/cache/'
24 plugins: 'test2/plugins/'
24 25
25admin: 26admin:
26 email: 'admin2@example.com' 27 email: 'admin2@example.com'
diff --git a/config/test-3.yaml b/config/test-3.yaml
index ef6780328..d2734f469 100644
--- a/config/test-3.yaml
+++ b/config/test-3.yaml
@@ -21,6 +21,7 @@ storage:
21 torrents: 'test3/torrents/' 21 torrents: 'test3/torrents/'
22 captions: 'test3/captions/' 22 captions: 'test3/captions/'
23 cache: 'test3/cache/' 23 cache: 'test3/cache/'
24 plugins: 'test3/plugins/'
24 25
25admin: 26admin:
26 email: 'admin3@example.com' 27 email: 'admin3@example.com'
diff --git a/config/test-4.yaml b/config/test-4.yaml
index e84b2d118..9ec45b024 100644
--- a/config/test-4.yaml
+++ b/config/test-4.yaml
@@ -21,6 +21,7 @@ storage:
21 torrents: 'test4/torrents/' 21 torrents: 'test4/torrents/'
22 captions: 'test4/captions/' 22 captions: 'test4/captions/'
23 cache: 'test4/cache/' 23 cache: 'test4/cache/'
24 plugins: 'test4/plugins/'
24 25
25admin: 26admin:
26 email: 'admin4@example.com' 27 email: 'admin4@example.com'
diff --git a/config/test-5.yaml b/config/test-5.yaml
index e25d4cfee..92cc113b9 100644
--- a/config/test-5.yaml
+++ b/config/test-5.yaml
@@ -21,6 +21,7 @@ storage:
21 torrents: 'test5/torrents/' 21 torrents: 'test5/torrents/'
22 captions: 'test5/captions/' 22 captions: 'test5/captions/'
23 cache: 'test5/cache/' 23 cache: 'test5/cache/'
24 plugins: 'test5/plugins/'
24 25
25admin: 26admin:
26 email: 'admin5@example.com' 27 email: 'admin5@example.com'
diff --git a/config/test-6.yaml b/config/test-6.yaml
index a020fe869..205d99797 100644
--- a/config/test-6.yaml
+++ b/config/test-6.yaml
@@ -21,6 +21,7 @@ storage:
21 torrents: 'test6/torrents/' 21 torrents: 'test6/torrents/'
22 captions: 'test6/captions/' 22 captions: 'test6/captions/'
23 cache: 'test6/cache/' 23 cache: 'test6/cache/'
24 plugins: 'test6/plugins/'
24 25
25admin: 26admin:
26 email: 'admin6@example.com' 27 email: 'admin6@example.com'
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index bb410e891..de58a7350 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -25,6 +25,7 @@ import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model
25import { logger } from '../../helpers/logger' 25import { logger } from '../../helpers/logger'
26import { listAvailablePluginsFromIndex } from '../../lib/plugins/plugin-index' 26import { listAvailablePluginsFromIndex } from '../../lib/plugins/plugin-index'
27import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model' 27import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model'
28import { RegisteredSettings } from '../../../shared/models/plugins/register-setting.model'
28 29
29const pluginRouter = express.Router() 30const pluginRouter = express.Router()
30 31
@@ -103,9 +104,11 @@ export {
103 104
104async function listPlugins (req: express.Request, res: express.Response) { 105async function listPlugins (req: express.Request, res: express.Response) {
105 const pluginType = req.query.pluginType 106 const pluginType = req.query.pluginType
107 const uninstalled = req.query.uninstalled
106 108
107 const resultList = await PluginModel.listForApi({ 109 const resultList = await PluginModel.listForApi({
108 pluginType, 110 pluginType,
111 uninstalled,
109 start: req.query.start, 112 start: req.query.start,
110 count: req.query.count, 113 count: req.query.count,
111 sort: req.query.sort 114 sort: req.query.sort
@@ -161,9 +164,9 @@ async function uninstallPlugin (req: express.Request, res: express.Response) {
161function getPluginRegisteredSettings (req: express.Request, res: express.Response) { 164function getPluginRegisteredSettings (req: express.Request, res: express.Response) {
162 const settings = PluginManager.Instance.getRegisteredSettings(req.params.npmName) 165 const settings = PluginManager.Instance.getRegisteredSettings(req.params.npmName)
163 166
164 return res.json({ 167 const json: RegisteredSettings = { settings }
165 settings 168
166 }) 169 return res.json(json)
167} 170}
168 171
169async function updatePluginSettings (req: express.Request, res: express.Response) { 172async function updatePluginSettings (req: express.Request, res: express.Response) {
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts
index 6b7810618..25b4f3c61 100644
--- a/server/lib/plugins/plugin-index.ts
+++ b/server/lib/plugins/plugin-index.ts
@@ -21,7 +21,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList)
21 sort, 21 sort,
22 pluginType, 22 pluginType,
23 search, 23 search,
24 currentPeerTubeEngine: PEERTUBE_VERSION 24 currentPeerTubeEngine: options.currentPeerTubeEngine || PEERTUBE_VERSION
25 } 25 }
26 26
27 const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins' 27 const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins'
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index ac31b06dc..9afda97ea 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -8,7 +8,7 @@ import { createReadStream, createWriteStream } from 'fs'
8import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' 8import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants'
9import { PluginType } from '../../../shared/models/plugins/plugin.type' 9import { PluginType } from '../../../shared/models/plugins/plugin.type'
10import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' 10import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn'
11import { outputFile } from 'fs-extra' 11import { outputFile, readJSON } from 'fs-extra'
12import { RegisterSettingOptions } from '../../../shared/models/plugins/register-setting.model' 12import { RegisterSettingOptions } from '../../../shared/models/plugins/register-setting.model'
13import { RegisterHookOptions } from '../../../shared/models/plugins/register-hook.model' 13import { RegisterHookOptions } from '../../../shared/models/plugins/register-hook.model'
14import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' 14import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model'
@@ -174,7 +174,7 @@ export class PluginManager implements ServerHook {
174 const pluginType = PluginModel.getTypeFromNpmName(npmName) 174 const pluginType = PluginModel.getTypeFromNpmName(npmName)
175 const pluginName = PluginModel.normalizePluginName(npmName) 175 const pluginName = PluginModel.normalizePluginName(npmName)
176 176
177 const packageJSON = this.getPackageJSON(pluginName, pluginType) 177 const packageJSON = await this.getPackageJSON(pluginName, pluginType)
178 if (!isPackageJSONValid(packageJSON, pluginType)) { 178 if (!isPackageJSONValid(packageJSON, pluginType)) {
179 throw new Error('PackageJSON is invalid.') 179 throw new Error('PackageJSON is invalid.')
180 } 180 }
@@ -251,7 +251,7 @@ export class PluginManager implements ServerHook {
251 251
252 logger.info('Registering plugin or theme %s.', npmName) 252 logger.info('Registering plugin or theme %s.', npmName)
253 253
254 const packageJSON = this.getPackageJSON(plugin.name, plugin.type) 254 const packageJSON = await this.getPackageJSON(plugin.name, plugin.type)
255 const pluginPath = this.getPluginPath(plugin.name, plugin.type) 255 const pluginPath = this.getPluginPath(plugin.name, plugin.type)
256 256
257 if (!isPackageJSONValid(packageJSON, plugin.type)) { 257 if (!isPackageJSONValid(packageJSON, plugin.type)) {
@@ -286,7 +286,10 @@ export class PluginManager implements ServerHook {
286 private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) { 286 private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) {
287 const npmName = PluginModel.buildNpmName(plugin.name, plugin.type) 287 const npmName = PluginModel.buildNpmName(plugin.name, plugin.type)
288 288
289 const library: PluginLibrary = require(join(pluginPath, packageJSON.library)) 289 // Delete cache if needed
290 const modulePath = join(pluginPath, packageJSON.library)
291 delete require.cache[modulePath]
292 const library: PluginLibrary = require(modulePath)
290 293
291 if (!isLibraryCodeValid(library)) { 294 if (!isLibraryCodeValid(library)) {
292 throw new Error('Library code is not valid (miss register or unregister function)') 295 throw new Error('Library code is not valid (miss register or unregister function)')
@@ -350,7 +353,7 @@ export class PluginManager implements ServerHook {
350 private getPackageJSON (pluginName: string, pluginType: PluginType) { 353 private getPackageJSON (pluginName: string, pluginType: PluginType) {
351 const pluginPath = join(this.getPluginPath(pluginName, pluginType), 'package.json') 354 const pluginPath = join(this.getPluginPath(pluginName, pluginType), 'package.json')
352 355
353 return require(pluginPath) as PluginPackageJson 356 return readJSON(pluginPath) as Promise<PluginPackageJson>
354 } 357 }
355 358
356 private getPluginPath (pluginName: string, pluginType: PluginType) { 359 private getPluginPath (pluginName: string, pluginType: PluginType) {
diff --git a/server/lib/plugins/yarn.ts b/server/lib/plugins/yarn.ts
index 74c67653c..e40351b6e 100644
--- a/server/lib/plugins/yarn.ts
+++ b/server/lib/plugins/yarn.ts
@@ -13,7 +13,9 @@ async function installNpmPlugin (npmName: string, version?: string) {
13 let toInstall = npmName 13 let toInstall = npmName
14 if (version) toInstall += `@${version}` 14 if (version) toInstall += `@${version}`
15 15
16 await execYarn('add ' + toInstall) 16 const { stdout } = await execYarn('add ' + toInstall)
17
18 logger.debug('Added a yarn package.', { yarnStdout: stdout })
17} 19}
18 20
19async function installNpmPluginFromDisk (path: string) { 21async function installNpmPluginFromDisk (path: string) {
@@ -46,7 +48,7 @@ async function execYarn (command: string) {
46 await outputJSON(pluginPackageJSON, {}) 48 await outputJSON(pluginPackageJSON, {})
47 } 49 }
48 50
49 await execShell(`yarn ${command}`, { cwd: pluginDirectory }) 51 return execShell(`yarn ${command}`, { cwd: pluginDirectory })
50 } catch (result) { 52 } catch (result) {
51 logger.error('Cannot exec yarn.', { command, err: result.err, stderr: result.stderr }) 53 logger.error('Cannot exec yarn.', { command, err: result.err, stderr: result.stderr })
52 54
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts
index 68704bf56..dc3f1454a 100644
--- a/server/middlewares/validators/plugins.ts
+++ b/server/middlewares/validators/plugins.ts
@@ -127,6 +127,9 @@ const listAvailablePluginsValidator = [
127 query('pluginType') 127 query('pluginType')
128 .optional() 128 .optional()
129 .custom(isPluginTypeValid).withMessage('Should have a valid plugin type'), 129 .custom(isPluginTypeValid).withMessage('Should have a valid plugin type'),
130 query('currentPeerTubeEngine')
131 .optional()
132 .custom(isPluginVersionValid).withMessage('Should have a valid current peertube engine'),
130 133
131 (req: express.Request, res: express.Response, next: express.NextFunction) => { 134 (req: express.Request, res: express.Response, next: express.NextFunction) => {
132 logger.debug('Checking enabledPluginValidator parameters', { parameters: req.query }) 135 logger.debug('Checking enabledPluginValidator parameters', { parameters: req.query })
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts
index dd03766c9..83ce6f451 100644
--- a/server/tests/api/check-params/plugins.ts
+++ b/server/tests/api/check-params/plugins.ts
@@ -152,7 +152,8 @@ describe('Test server plugins API validators', function () {
152 const path = '/api/v1/plugins/available' 152 const path = '/api/v1/plugins/available'
153 const baseQuery = { 153 const baseQuery = {
154 search: 'super search', 154 search: 'super search',
155 pluginType: PluginType.PLUGIN 155 pluginType: PluginType.PLUGIN,
156 currentPeerTubeEngine: '1.2.3'
156 } 157 }
157 158
158 it('Should fail with an invalid token', async function () { 159 it('Should fail with an invalid token', async function () {
@@ -198,6 +199,17 @@ describe('Test server plugins API validators', function () {
198 }) 199 })
199 }) 200 })
200 201
202 it('Should fail with an invalid current peertube engine', async function () {
203 const query = immutableAssign(baseQuery, { currentPeerTubeEngine: '1.0' })
204
205 await makeGetRequest({
206 url: server.url,
207 path,
208 token: server.accessToken,
209 query
210 })
211 })
212
201 it('Should success with the correct parameters', async function () { 213 it('Should success with the correct parameters', async function () {
202 await makeGetRequest({ 214 await makeGetRequest({
203 url: server.url, 215 url: server.url,
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
index 9a623c553..b3d003f45 100644
--- a/server/tests/api/server/plugins.ts
+++ b/server/tests/api/server/plugins.ts
@@ -2,129 +2,416 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { About } from '../../../../shared/models/server/about.model'
6import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
7import { 5import {
8 cleanupTests, 6 cleanupTests,
9 deleteCustomConfig, 7 closeAllSequelize,
10 flushAndRunServer, 8 flushAndRunServer,
11 getAbout, 9 getConfig, getMyUserInformation, getPluginPackageJSON,
12 getConfig, 10 getPlugin,
13 getCustomConfig, installPlugin, 11 getPluginRegisteredSettings,
14 killallServers, parallelTests, 12 getPluginsCSS,
15 registerUser, 13 installPlugin, killallServers,
16 reRunServer, ServerInfo, 14 listAvailablePlugins,
15 listPlugins, reRunServer,
16 ServerInfo,
17 setAccessTokensToServers, 17 setAccessTokensToServers,
18 updateCustomConfig, uploadVideo 18 setPluginVersion, uninstallPlugin,
19 updateCustomSubConfig, updateMyUser, updatePluginPackageJSON, updatePlugin,
20 updatePluginSettings,
21 wait
19} from '../../../../shared/extra-utils' 22} from '../../../../shared/extra-utils'
20import { ServerConfig } from '../../../../shared/models' 23import { PluginType } from '../../../../shared/models/plugins/plugin.type'
24import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model'
25import { ServerConfig } from '../../../../shared/models/server'
26import { RegisteredSettings } from '../../../../shared/models/plugins/register-setting.model'
21import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model' 27import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model'
28import { User } from '../../../../shared/models/users'
29import { PluginPackageJson } from '../../../../shared/models/plugins/plugin-package-json.model'
22 30
23const expect = chai.expect 31const expect = chai.expect
24 32
25describe('Test plugins', function () { 33describe('Test plugins', function () {
26 let server = null 34 let server: ServerInfo = null
27 35
28 before(async function () { 36 before(async function () {
29 this.timeout(30000) 37 this.timeout(30000)
30 38
31 server = await flushAndRunServer(1) 39 const configOverride = {
40 plugins: {
41 index: { check_latest_versions_interval: '5 seconds' }
42 }
43 }
44 server = await flushAndRunServer(1, configOverride)
32 await setAccessTokensToServers([ server ]) 45 await setAccessTokensToServers([ server ])
46 })
47
48 it('Should list and search available plugins and themes', async function () {
49 this.timeout(30000)
33 50
34 { 51 {
35 await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-hello-world' }) 52 const res = await listAvailablePlugins({
53 url: server.url,
54 accessToken: server.accessToken,
55 count: 1,
56 start: 0,
57 pluginType: PluginType.THEME,
58 search: 'background-red'
59 })
60
61 expect(res.body.total).to.be.at.least(1)
62 expect(res.body.data).to.have.lengthOf(1)
36 } 63 }
37 64
38 { 65 {
39 await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-background-color' }) 66 const res1 = await listAvailablePlugins({
67 url: server.url,
68 accessToken: server.accessToken,
69 count: 2,
70 start: 0,
71 sort: 'npmName'
72 })
73 const data1: PeerTubePluginIndex[] = res1.body.data
74
75 expect(res1.body.total).to.be.at.least(2)
76 expect(data1).to.have.lengthOf(2)
77
78 const res2 = await listAvailablePlugins({
79 url: server.url,
80 accessToken: server.accessToken,
81 count: 2,
82 start: 0,
83 sort: '-npmName'
84 })
85 const data2: PeerTubePluginIndex[] = res2.body.data
86
87 expect(res2.body.total).to.be.at.least(2)
88 expect(data2).to.have.lengthOf(2)
89
90 expect(data1[0].npmName).to.not.equal(data2[ 0 ].npmName)
40 } 91 }
41 })
42 92
43 it('Should list available plugins and themes', async function () { 93 {
44 // List without filter 94 const res = await listAvailablePlugins({
45 // List with filter (plugin and theme) 95 url: server.url,
46 }) 96 accessToken: server.accessToken,
97 count: 10,
98 start: 0,
99 pluginType: PluginType.THEME,
100 search: 'background-red',
101 currentPeerTubeEngine: '1.0.0'
102 })
103 const data: PeerTubePluginIndex[] = res.body.data
47 104
48 it('Should search available plugins', async function () { 105 const p = data.find(p => p.npmName === 'peertube-theme-background-red')
49 // Search with filter (plugin and theme) 106 expect(p).to.be.undefined
50 // Add pagination 107 }
51 // Add sort
52 // Add peertube engine
53 }) 108 })
54 109
55 it('Should have an empty global css', async function () { 110 it('Should have an empty global css', async function () {
56 // get /global.css 111 const res = await getPluginsCSS(server.url)
112
113 expect(res.text).to.be.empty
57 }) 114 })
58 115
59 it('Should install a plugin and a theme', async function () { 116 it('Should install a plugin and a theme', async function () {
117 this.timeout(30000)
118
119 await installPlugin({
120 url: server.url,
121 accessToken: server.accessToken,
122 npmName: 'peertube-plugin-hello-world'
123 })
60 124
125 await installPlugin({
126 url: server.url,
127 accessToken: server.accessToken,
128 npmName: 'peertube-theme-background-red'
129 })
61 }) 130 })
62 131
63 it('Should have the correct global css', async function () { 132 it('Should have the correct global css', async function () {
64 // get /global.css 133 const res = await getPluginsCSS(server.url)
134
135 expect(res.text).to.contain('--mainBackgroundColor')
65 }) 136 })
66 137
67 it('Should have the plugin loaded in the configuration', async function () { 138 it('Should have the plugin loaded in the configuration', async function () {
68 // Check registered themes/plugins 139 const res = await getConfig(server.url)
140 const config: ServerConfig = res.body
141
142 const theme = config.theme.registered.find(r => r.name === 'background-red')
143 expect(theme).to.not.be.undefined
144
145 const plugin = config.plugin.registered.find(r => r.name === 'hello-world')
146 expect(plugin).to.not.be.undefined
69 }) 147 })
70 148
71 it('Should update the default theme in the configuration', async function () { 149 it('Should update the default theme in the configuration', async function () {
72 // Update config 150 await updateCustomSubConfig(server.url, server.accessToken, { theme: { default: 'background-red' } })
151
152 const res = await getConfig(server.url)
153 const config: ServerConfig = res.body
154
155 expect(config.theme.default).to.equal('background-red')
73 }) 156 })
74 157
75 it('Should list plugins and themes', async function () { 158 it('Should update my default theme', async function () {
76 // List without filter 159 await updateMyUser({
77 // List with filter (theme/plugin) 160 url: server.url,
78 // List with pagination 161 accessToken: server.accessToken,
79 // List with sort 162 theme: 'background-red'
163 })
164
165 const res = await getMyUserInformation(server.url, server.accessToken)
166 expect((res.body as User).theme).to.equal('background-red')
80 }) 167 })
81 168
82 it('Should get a plugin and a theme', async function () { 169 it('Should list plugins and themes', async function () {
83 // Get plugin 170 {
84 // Get theme 171 const res = await listPlugins({
172 url: server.url,
173 accessToken: server.accessToken,
174 count: 1,
175 start: 0,
176 pluginType: PluginType.THEME
177 })
178 const data: PeerTubePlugin[] = res.body.data
179
180 expect(res.body.total).to.be.at.least(1)
181 expect(data).to.have.lengthOf(1)
182 expect(data[0].name).to.equal('background-red')
183 }
184
185 {
186 const res = await listPlugins({
187 url: server.url,
188 accessToken: server.accessToken,
189 count: 2,
190 start: 0,
191 sort: 'name'
192 })
193 const data: PeerTubePlugin[] = res.body.data
194
195 expect(data[0].name).to.equal('background-red')
196 expect(data[1].name).to.equal('hello-world')
197 }
198
199 {
200 const res = await listPlugins({
201 url: server.url,
202 accessToken: server.accessToken,
203 count: 2,
204 start: 1,
205 sort: 'name'
206 })
207 const data: PeerTubePlugin[] = res.body.data
208
209 expect(data[0].name).to.equal('hello-world')
210 }
85 }) 211 })
86 212
87 it('Should get registered settings', async function () { 213 it('Should get registered settings', async function () {
88 // Get plugin 214 const res = await getPluginRegisteredSettings({
215 url: server.url,
216 accessToken: server.accessToken,
217 npmName: 'peertube-plugin-hello-world'
218 })
219
220 const settings = (res.body as RegisteredSettings).settings
221
222 expect(settings).to.have.length.at.least(1)
223
224 const adminNameSettings = settings.find(s => s.name === 'admin-name')
225 expect(adminNameSettings).to.not.be.undefined
89 }) 226 })
90 227
91 it('Should update the settings', async function () { 228 it('Should update the settings', async function () {
92 // Update /settings 229 const settings = {
230 'admin-name': 'Cid'
231 }
232
233 await updatePluginSettings({
234 url: server.url,
235 accessToken: server.accessToken,
236 npmName: 'peertube-plugin-hello-world',
237 settings
238 })
239 })
240
241 it('Should get a plugin and a theme', async function () {
242 {
243 const res = await getPlugin({
244 url: server.url,
245 accessToken: server.accessToken,
246 npmName: 'peertube-plugin-hello-world'
247 })
248
249 const plugin: PeerTubePlugin = res.body
250
251 expect(plugin.type).to.equal(PluginType.PLUGIN)
252 expect(plugin.name).to.equal('hello-world')
253 expect(plugin.description).to.exist
254 expect(plugin.homepage).to.exist
255 expect(plugin.uninstalled).to.be.false
256 expect(plugin.enabled).to.be.true
257 expect(plugin.description).to.exist
258 expect(plugin.version).to.exist
259 expect(plugin.peertubeEngine).to.exist
260 expect(plugin.createdAt).to.exist
261
262 expect(plugin.settings).to.not.be.undefined
263 expect(plugin.settings['admin-name']).to.equal('Cid')
264 }
265
266 {
267 const res = await getPlugin({
268 url: server.url,
269 accessToken: server.accessToken,
270 npmName: 'peertube-theme-background-red'
271 })
272
273 const plugin: PeerTubePlugin = res.body
93 274
94 // get /plugin 275 expect(plugin.type).to.equal(PluginType.THEME)
276 expect(plugin.name).to.equal('background-red')
277 expect(plugin.description).to.exist
278 expect(plugin.homepage).to.exist
279 expect(plugin.uninstalled).to.be.false
280 expect(plugin.enabled).to.be.true
281 expect(plugin.description).to.exist
282 expect(plugin.version).to.exist
283 expect(plugin.peertubeEngine).to.exist
284 expect(plugin.createdAt).to.exist
285
286 expect(plugin.settings).to.be.null
287 }
95 }) 288 })
96 289
97 it('Should update the plugin and the theme', async function () { 290 it('Should update the plugin and the theme', async function () {
98 // update BDD -> 0.0.1 291 this.timeout(30000)
99 // update package.json (theme + plugin) 292
100 // list to check versions 293 // Wait the scheduler that get the latest plugins versions
101 // update plugin + theme 294 await wait(6000)
102 // list to check they have been updated 295
103 // check package.json are upgraded too 296 // Fake update our plugin version
297 await setPluginVersion(server.internalServerNumber, 'hello-world', '0.0.1')
298
299 // Fake update package.json
300 const packageJSON: PluginPackageJson = await getPluginPackageJSON(server, 'peertube-plugin-hello-world')
301 const oldVersion = packageJSON.version
302
303 packageJSON.version = '0.0.1'
304 await updatePluginPackageJSON(server, 'peertube-plugin-hello-world', packageJSON)
305
306 // Restart the server to take into account this change
307 killallServers([ server ])
308 await reRunServer(server)
309
310 {
311 const res = await listPlugins({
312 url: server.url,
313 accessToken: server.accessToken,
314 pluginType: PluginType.PLUGIN
315 })
316
317 const plugin: PeerTubePlugin = res.body.data[0]
318
319 expect(plugin.version).to.equal('0.0.1')
320 expect(plugin.latestVersion).to.exist
321 expect(plugin.latestVersion).to.not.equal('0.0.1')
322 }
323
324 {
325 await updatePlugin({
326 url: server.url,
327 accessToken: server.accessToken,
328 npmName: 'peertube-plugin-hello-world'
329 })
330
331 const res = await listPlugins({
332 url: server.url,
333 accessToken: server.accessToken,
334 pluginType: PluginType.PLUGIN
335 })
336
337 const plugin: PeerTubePlugin = res.body.data[0]
338
339 expect(plugin.version).to.equal(oldVersion)
340
341 const updatedPackageJSON: PluginPackageJson = await getPluginPackageJSON(server, 'peertube-plugin-hello-world')
342 expect(updatedPackageJSON.version).to.equal(oldVersion)
343 }
104 }) 344 })
105 345
106 it('Should uninstall the plugin', async function () { 346 it('Should uninstall the plugin', async function () {
107 // uninstall 347 await uninstallPlugin({
108 // list 348 url: server.url,
349 accessToken: server.accessToken,
350 npmName: 'peertube-plugin-hello-world'
351 })
352
353 const res = await listPlugins({
354 url: server.url,
355 accessToken: server.accessToken,
356 pluginType: PluginType.PLUGIN
357 })
358
359 expect(res.body.total).to.equal(0)
360 expect(res.body.data).to.have.lengthOf(0)
109 }) 361 })
110 362
111 it('Should have an empty global css', async function () { 363 it('Should have an empty global css', async function () {
112 // get /global.css 364 const res = await getPluginsCSS(server.url)
365
366 expect(res.text).to.be.empty
113 }) 367 })
114 368
115 it('Should list uninstalled plugins', async function () { 369 it('Should list uninstalled plugins', async function () {
116 // { uninstalled: true } 370 const res = await listPlugins({
371 url: server.url,
372 accessToken: server.accessToken,
373 pluginType: PluginType.PLUGIN,
374 uninstalled: true
375 })
376
377 expect(res.body.total).to.equal(1)
378 expect(res.body.data).to.have.lengthOf(1)
379
380 const plugin: PeerTubePlugin = res.body.data[0]
381 expect(plugin.name).to.equal('hello-world')
382 expect(plugin.enabled).to.be.false
383 expect(plugin.uninstalled).to.be.true
117 }) 384 })
118 385
119 it('Should uninstall the theme', async function () { 386 it('Should uninstall the theme', async function () {
120 // Uninstall 387 await uninstallPlugin({
388 url: server.url,
389 accessToken: server.accessToken,
390 npmName: 'peertube-theme-background-red'
391 })
121 }) 392 })
122 393
123 it('Should have updated the configuration', async function () { 394 it('Should have updated the configuration', async function () {
124 // get /config (default theme + registered themes + registered plugins) 395 // get /config (default theme + registered themes + registered plugins)
396 const res = await getConfig(server.url)
397 const config: ServerConfig = res.body
398
399 expect(config.theme.default).to.equal('default')
400
401 const theme = config.theme.registered.find(r => r.name === 'background-red')
402 expect(theme).to.be.undefined
403
404 const plugin = config.plugin.registered.find(r => r.name === 'hello-world')
405 expect(plugin).to.be.undefined
406 })
407
408 it('Should have updated the user theme', async function () {
409 const res = await getMyUserInformation(server.url, server.accessToken)
410 expect((res.body as User).theme).to.equal('instance-default')
125 }) 411 })
126 412
127 after(async function () { 413 after(async function () {
414 await closeAllSequelize([ server ])
128 await cleanupTests([ server ]) 415 await cleanupTests([ server ])
129 }) 416 })
130}) 417})
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
index 8abab98c2..93dc57d09 100644
--- a/server/tests/plugins/action-hooks.ts
+++ b/server/tests/plugins/action-hooks.ts
@@ -7,7 +7,7 @@ import { setAccessTokensToServers } from '../../../shared/extra-utils'
7 7
8const expect = chai.expect 8const expect = chai.expect
9 9
10describe('Test plugin filter hooks', function () { 10describe('Test plugin action hooks', function () {
11 let server: ServerInfo 11 let server: ServerInfo
12 12
13 before(async function () { 13 before(async function () {
@@ -18,7 +18,7 @@ describe('Test plugin filter hooks', function () {
18 }) 18 })
19 19
20 it('Should execute ', async function () { 20 it('Should execute ', async function () {
21 21 // empty
22 }) 22 })
23 23
24 after(async function () { 24 after(async function () {
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 8abab98c2..500728712 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -18,7 +18,7 @@ describe('Test plugin filter hooks', function () {
18 }) 18 })
19 19
20 it('Should execute ', async function () { 20 it('Should execute ', async function () {
21 21 // empty
22 }) 22 })
23 23
24 after(async function () { 24 after(async function () {
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts
index 10cff7dd7..20254b3b4 100644
--- a/server/tools/peertube-plugins.ts
+++ b/server/tools/peertube-plugins.ts
@@ -65,9 +65,9 @@ async function pluginsListCLI () {
65 const { url, username, password } = await getServerCredentials(program) 65 const { url, username, password } = await getServerCredentials(program)
66 const accessToken = await getAdminTokenOrDie(url, username, password) 66 const accessToken = await getAdminTokenOrDie(url, username, password)
67 67
68 let type: PluginType 68 let pluginType: PluginType
69 if (program['onlyThemes']) type = PluginType.THEME 69 if (program['onlyThemes']) pluginType = PluginType.THEME
70 if (program['onlyPlugins']) type = PluginType.PLUGIN 70 if (program['onlyPlugins']) pluginType = PluginType.PLUGIN
71 71
72 const res = await listPlugins({ 72 const res = await listPlugins({
73 url, 73 url,
@@ -75,7 +75,7 @@ async function pluginsListCLI () {
75 start: 0, 75 start: 0,
76 count: 100, 76 count: 100,
77 sort: 'name', 77 sort: 'name',
78 type 78 pluginType
79 }) 79 })
80 const plugins: PeerTubePlugin[] = res.body.data 80 const plugins: PeerTubePlugin[] = res.body.data
81 81
diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts
index 34477cb78..1961a8762 100644
--- a/shared/extra-utils/miscs/sql.ts
+++ b/shared/extra-utils/miscs/sql.ts
@@ -1,5 +1,6 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { ServerInfo } from '../server/servers' 2import { ServerInfo } from '../server/servers'
3import { PluginType } from '../../models/plugins/plugin.type'
3 4
4let sequelizes: { [ id: number ]: Sequelize } = {} 5let sequelizes: { [ id: number ]: Sequelize } = {}
5 6
@@ -72,10 +73,19 @@ async function closeAllSequelize (servers: ServerInfo[]) {
72 } 73 }
73} 74}
74 75
76function setPluginVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
77 const seq = getSequelize(internalServerNumber)
78
79 const options = { type: QueryTypes.UPDATE }
80
81 return seq.query(`UPDATE "plugin" SET "version" = '${newVersion}' WHERE "name" = '${pluginName}'`, options)
82}
83
75export { 84export {
76 setVideoField, 85 setVideoField,
77 setPlaylistField, 86 setPlaylistField,
78 setActorField, 87 setActorField,
79 countVideoViewsOf, 88 countVideoViewsOf,
89 setPluginVersion,
80 closeAllSequelize 90 closeAllSequelize
81} 91}
diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts
index 1da313ab7..7a5c5344b 100644
--- a/shared/extra-utils/server/plugins.ts
+++ b/shared/extra-utils/server/plugins.ts
@@ -1,5 +1,10 @@
1import { makeGetRequest, makePostBodyRequest } from '../requests/requests' 1import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
2import { PluginType } from '../../models/plugins/plugin.type' 2import { PluginType } from '../../models/plugins/plugin.type'
3import { PeertubePluginIndexList } from '../../models/plugins/peertube-plugin-index-list.model'
4import { readJSON, writeJSON } from 'fs-extra'
5import { ServerInfo } from './servers'
6import { root } from '../miscs/miscs'
7import { join } from 'path'
3 8
4function listPlugins (parameters: { 9function listPlugins (parameters: {
5 url: string, 10 url: string,
@@ -7,10 +12,11 @@ function listPlugins (parameters: {
7 start?: number, 12 start?: number,
8 count?: number, 13 count?: number,
9 sort?: string, 14 sort?: string,
10 type?: PluginType, 15 pluginType?: PluginType,
16 uninstalled?: boolean,
11 expectedStatus?: number 17 expectedStatus?: number
12}) { 18}) {
13 const { url, accessToken, start, count, sort, type, expectedStatus = 200 } = parameters 19 const { url, accessToken, start, count, sort, pluginType, uninstalled, expectedStatus = 200 } = parameters
14 const path = '/api/v1/plugins' 20 const path = '/api/v1/plugins'
15 21
16 return makeGetRequest({ 22 return makeGetRequest({
@@ -21,12 +27,45 @@ function listPlugins (parameters: {
21 start, 27 start,
22 count, 28 count,
23 sort, 29 sort,
24 type 30 pluginType,
31 uninstalled
25 }, 32 },
26 statusCodeExpected: expectedStatus 33 statusCodeExpected: expectedStatus
27 }) 34 })
28} 35}
29 36
37function listAvailablePlugins (parameters: {
38 url: string,
39 accessToken: string,
40 start?: number,
41 count?: number,
42 sort?: string,
43 pluginType?: PluginType,
44 currentPeerTubeEngine?: string,
45 search?: string
46 expectedStatus?: number
47}) {
48 const { url, accessToken, start, count, sort, pluginType, search, currentPeerTubeEngine, expectedStatus = 200 } = parameters
49 const path = '/api/v1/plugins/available'
50
51 const query: PeertubePluginIndexList = {
52 start,
53 count,
54 sort,
55 pluginType,
56 currentPeerTubeEngine,
57 search
58 }
59
60 return makeGetRequest({
61 url,
62 path,
63 token: accessToken,
64 query,
65 statusCodeExpected: expectedStatus
66 })
67}
68
30function getPlugin (parameters: { 69function getPlugin (parameters: {
31 url: string, 70 url: string,
32 accessToken: string, 71 accessToken: string,
@@ -44,19 +83,21 @@ function getPlugin (parameters: {
44 }) 83 })
45} 84}
46 85
47function getPluginSettings (parameters: { 86function updatePluginSettings (parameters: {
48 url: string, 87 url: string,
49 accessToken: string, 88 accessToken: string,
50 npmName: string, 89 npmName: string,
90 settings: any,
51 expectedStatus?: number 91 expectedStatus?: number
52}) { 92}) {
53 const { url, accessToken, npmName, expectedStatus = 200 } = parameters 93 const { url, accessToken, npmName, settings, expectedStatus = 204 } = parameters
54 const path = '/api/v1/plugins/' + npmName + '/settings' 94 const path = '/api/v1/plugins/' + npmName + '/settings'
55 95
56 return makeGetRequest({ 96 return makePutBodyRequest({
57 url, 97 url,
58 path, 98 path,
59 token: accessToken, 99 token: accessToken,
100 fields: { settings },
60 statusCodeExpected: expectedStatus 101 statusCodeExpected: expectedStatus
61 }) 102 })
62} 103}
@@ -134,12 +175,43 @@ function uninstallPlugin (parameters: {
134 }) 175 })
135} 176}
136 177
178function getPluginsCSS (url: string) {
179 const path = '/plugins/global.css'
180
181 return makeGetRequest({
182 url,
183 path,
184 statusCodeExpected: 200
185 })
186}
187
188function getPackageJSONPath (server: ServerInfo, npmName: string) {
189 return join(root(), 'test' + server.internalServerNumber, 'plugins', 'node_modules', npmName, 'package.json')
190}
191
192function updatePluginPackageJSON (server: ServerInfo, npmName: string, json: any) {
193 const path = getPackageJSONPath(server, npmName)
194
195 return writeJSON(path, json)
196}
197
198function getPluginPackageJSON (server: ServerInfo, npmName: string) {
199 const path = getPackageJSONPath(server, npmName)
200
201 return readJSON(path)
202}
203
137export { 204export {
138 listPlugins, 205 listPlugins,
206 listAvailablePlugins,
139 installPlugin, 207 installPlugin,
208 getPluginsCSS,
140 updatePlugin, 209 updatePlugin,
141 getPlugin, 210 getPlugin,
142 uninstallPlugin, 211 uninstallPlugin,
143 getPluginSettings, 212 updatePluginSettings,
144 getPluginRegisteredSettings 213 getPluginRegisteredSettings,
214 getPackageJSONPath,
215 updatePluginPackageJSON,
216 getPluginPackageJSON
145} 217}
diff --git a/shared/models/plugins/register-setting.model.ts b/shared/models/plugins/register-setting.model.ts
index e7af75dca..429ac3aad 100644
--- a/shared/models/plugins/register-setting.model.ts
+++ b/shared/models/plugins/register-setting.model.ts
@@ -4,3 +4,7 @@ export interface RegisterSettingOptions {
4 type: 'input' 4 type: 'input'
5 default?: string 5 default?: string
6} 6}
7
8export interface RegisteredSettings {
9 settings: RegisterSettingOptions[]
10}