Add CLI plugins tests
authorChocobozzz <me@florianbigard.com>
Fri, 19 Jul 2019 08:37:35 +0000 (10:37 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Wed, 24 Jul 2019 08:58:16 +0000 (10:58 +0200)
22 files changed:
scripts/plugin/install.ts
scripts/travis.sh
server/helpers/core-utils.ts
server/helpers/ffmpeg-utils.ts
server/lib/plugins/yarn.ts
server/tests/api/check-params/users.ts
server/tests/api/server/index.ts
server/tests/api/server/plugins.ts [new file with mode: 0644]
server/tests/api/users/users.ts
server/tests/cli/index.ts
server/tests/cli/peertube.ts
server/tests/cli/plugins.ts [new file with mode: 0644]
server/tests/fixtures/peertube-plugin-test/main.js [new file with mode: 0644]
server/tests/fixtures/peertube-plugin-test/package.json [new file with mode: 0644]
server/tests/index.ts
server/tests/plugins/action-hooks.ts [new file with mode: 0644]
server/tests/plugins/filter-hooks.ts [new file with mode: 0644]
server/tests/plugins/index.ts [new file with mode: 0644]
server/tools/peertube.ts
shared/extra-utils/users/users.ts
shared/models/videos/video-resolution.enum.ts
support/doc/tools.md

index 1725cbeb6a437a86f08e56a7f9811164fb666e9f..5d7fe4ba04688f30a155c9c1342edfa1f316a639 100755 (executable)
@@ -4,21 +4,16 @@ import { PluginManager } from '../../server/lib/plugins/plugin-manager'
 import { isAbsolute } from 'path'
 
 program
-  .option('-n, --plugin-name [pluginName]', 'Plugin name to install')
+  .option('-n, --npm-name [npmName]', 'Plugin to install')
   .option('-v, --plugin-version [pluginVersion]', 'Plugin version to install')
   .option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install')
   .parse(process.argv)
 
-if (!program['pluginName'] && !program['pluginPath']) {
+if (!program['npmName'] && !program['pluginPath']) {
   console.error('You need to specify a plugin name with the desired version, or a plugin path.')
   process.exit(-1)
 }
 
-if (program['pluginName'] && !program['pluginVersion']) {
-  console.error('You need to specify a the version of the plugin you want to install.')
-  process.exit(-1)
-}
-
 if (program['pluginPath'] && !isAbsolute(program['pluginPath'])) {
   console.error('Plugin path should be absolute.')
   process.exit(-1)
@@ -34,6 +29,6 @@ run()
 async function run () {
   await initDatabaseModels(true)
 
-  const toInstall = program['pluginName'] || program['pluginPath']
+  const toInstall = program['npmName'] || program['pluginPath']
   await PluginManager.Instance.install(toInstall, program['pluginVersion'], !!program['pluginPath'])
 }
index 664d9fd6c6e11ea55ad0c03ab331d6101d00b933..42e2329c6ba7ab18d32a6ea4b99167b30b777b4f 100755 (executable)
@@ -14,7 +14,8 @@ if [ "$1" = "misc" ]; then
     mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/client.ts \
         server/tests/feeds/index.ts \
         server/tests/misc-endpoints.ts \
-        server/tests/helpers/index.ts
+        server/tests/helpers/index.ts \
+        server/tests/plugins/index.ts
 elif [ "$1" = "cli" ]; then
     npm run build:server
     CC=gcc-4.9 CXX=g++-4.9 npm run setup:cli
index 38b6f63f88668db4a9a9f9767cf8b5f16523919b..9ff67c43af4cb82cba3259a9bebb5dc7f95b780c 100644 (file)
@@ -4,7 +4,7 @@
 */
 
 import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
-import { isAbsolute, join } from 'path'
+import { basename, isAbsolute, join, resolve } from 'path'
 import * as pem from 'pem'
 import { URL } from 'url'
 import { truncate } from 'lodash'
@@ -136,16 +136,16 @@ function getAppNumber () {
   return process.env.NODE_APP_INSTANCE
 }
 
+let rootPath: string
 function root () {
+  if (rootPath) return rootPath
+
   // We are in /helpers/utils.js
-  const paths = [ __dirname, '..', '..' ]
+  rootPath = join(__dirname, '..', '..')
 
-  // We are under /dist directory
-  if (process.mainModule && process.mainModule.filename.endsWith('_mocha') === false) {
-    paths.push('..')
-  }
+  if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
 
-  return join.apply(null, paths)
+  return rootPath
 }
 
 // Thanks: https://stackoverflow.com/a/12034334
index 8041e7b3b494168edbe80e8e0e9e5827168c2feb..914ecc51aa9c45950b94cdd335076f470ac81876 100644 (file)
@@ -387,14 +387,15 @@ namespace audio {
   export namespace bitrate {
     const baseKbitrate = 384
 
-    const toBits = (kbits: number): number => { return kbits * 8000 }
+    const toBits = (kbits: number) => kbits * 8000
 
     export const aac = (bitrate: number): number => {
       switch (true) {
-      case bitrate > toBits(baseKbitrate):
-        return baseKbitrate
-      default:
-        return -1 // we interpret it as a signal to copy the audio stream as is
+        case bitrate > toBits(baseKbitrate):
+          return baseKbitrate
+
+        default:
+          return -1 // we interpret it as a signal to copy the audio stream as is
       }
     }
 
@@ -405,12 +406,14 @@ namespace audio {
       made here are not made to be accurate, especially with good mp3 encoders.
       */
       switch (true) {
-      case bitrate <= toBits(192):
-        return 128
-      case bitrate <= toBits(384):
-        return 256
-      default:
-        return baseKbitrate
+        case bitrate <= toBits(192):
+          return 128
+
+        case bitrate <= toBits(384):
+          return 256
+
+        default:
+          return baseKbitrate
       }
     }
   }
index 5fe1c50463d0f30aafd9d567f7a186a9896a6a1b..74c67653c88e90f2f6aa8b88a4385a7dd318912f 100644 (file)
@@ -5,12 +5,12 @@ import { CONFIG } from '../../initializers/config'
 import { outputJSON, pathExists } from 'fs-extra'
 import { join } from 'path'
 
-async function installNpmPlugin (name: string, version?: string) {
+async function installNpmPlugin (npmName: string, version?: string) {
   // Security check
-  checkNpmPluginNameOrThrow(name)
+  checkNpmPluginNameOrThrow(npmName)
   if (version) checkPluginVersionOrThrow(version)
 
-  let toInstall = name
+  let toInstall = npmName
   if (version) toInstall += `@${version}`
 
   await execYarn('add ' + toInstall)
index 5d62fe2b391da2b38d6b677bfc987c1c66ac2d90..5b788e328fd051728b4d8e2bd5a6352b8b786418 100644 (file)
@@ -387,13 +387,24 @@ describe('Test users API validators', function () {
       }
     })
 
+    it('Should fail with an invalid theme', async function () {
+      const fields = { theme: 'invalid' }
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
+    })
+
+    it('Should fail with an unknown theme', async function () {
+      const fields = { theme: 'peertube-theme-unknown' }
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
+    })
+
     it('Should succeed to change password with the correct params', async function () {
       const fields = {
         currentPassword: 'my super password',
         password: 'my super password',
         nsfwPolicy: 'blur',
         autoPlayVideo: false,
-        email: 'super_email@example.com'
+        email: 'super_email@example.com',
+        theme: 'default'
       }
 
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 })
index 94c15e0d04ea2a07469a64fa5739c80bdcb35b55..3daeeb49aa971a581b23acba73645952f45fcb93 100644 (file)
@@ -11,3 +11,4 @@ import './reverse-proxy'
 import './stats'
 import './tracker'
 import './no-client'
+import './plugins'
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
new file mode 100644 (file)
index 0000000..9a623c5
--- /dev/null
@@ -0,0 +1,130 @@
+/* tslint:disable:no-unused-expression */
+
+import 'mocha'
+import * as chai from 'chai'
+import { About } from '../../../../shared/models/server/about.model'
+import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
+import {
+  cleanupTests,
+  deleteCustomConfig,
+  flushAndRunServer,
+  getAbout,
+  getConfig,
+  getCustomConfig, installPlugin,
+  killallServers, parallelTests,
+  registerUser,
+  reRunServer, ServerInfo,
+  setAccessTokensToServers,
+  updateCustomConfig, uploadVideo
+} from '../../../../shared/extra-utils'
+import { ServerConfig } from '../../../../shared/models'
+import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model'
+
+const expect = chai.expect
+
+describe('Test plugins', function () {
+  let server = null
+
+  before(async function () {
+    this.timeout(30000)
+
+    server = await flushAndRunServer(1)
+    await setAccessTokensToServers([ server ])
+
+    {
+      await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-hello-world' })
+    }
+
+    {
+      await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-background-color' })
+    }
+  })
+
+  it('Should list available plugins and themes', async function () {
+    // List without filter
+    // List with filter (plugin and theme)
+  })
+
+  it('Should search available plugins', async function () {
+    // Search with filter (plugin and theme)
+    // Add pagination
+    // Add sort
+    // Add peertube engine
+  })
+
+  it('Should have an empty global css', async function () {
+    // get /global.css
+  })
+
+  it('Should install a plugin and a theme', async function () {
+
+  })
+
+  it('Should have the correct global css', async function () {
+    // get /global.css
+  })
+
+  it('Should have the plugin loaded in the configuration', async function () {
+    // Check registered themes/plugins
+  })
+
+  it('Should update the default theme in the configuration', async function () {
+    // Update config
+  })
+
+  it('Should list plugins and themes', async function () {
+    // List without filter
+    // List with filter (theme/plugin)
+    // List with pagination
+    // List with sort
+  })
+
+  it('Should get a plugin and a theme', async function () {
+    // Get plugin
+    // Get theme
+  })
+
+  it('Should get registered settings', async function () {
+    // Get plugin
+  })
+
+  it('Should update the settings', async function () {
+    // Update /settings
+
+    // get /plugin
+  })
+
+  it('Should update the plugin and the theme', async function () {
+    // update BDD -> 0.0.1
+    // update package.json (theme + plugin)
+    // list to check versions
+    // update plugin + theme
+    // list to check they have been updated
+    // check package.json are upgraded too
+  })
+
+  it('Should uninstall the plugin', async function () {
+    // uninstall
+    // list
+  })
+
+  it('Should have an empty global css', async function () {
+    // get /global.css
+  })
+
+  it('Should list uninstalled plugins', async function () {
+    // { uninstalled: true }
+  })
+
+  it('Should uninstall the theme', async function () {
+    // Uninstall
+  })
+
+  it('Should have updated the configuration', async function () {
+    // get /config (default theme + registered themes + registered plugins)
+  })
+
+  after(async function () {
+    await cleanupTests([ server ])
+  })
+})
index 6fc2a070f324bd72805171890f282b73ab3c605a..3a3fabb4c5bdb458ca48911e0433a7e1f24a6087 100644 (file)
@@ -18,7 +18,7 @@ import {
   getUsersList,
   getUsersListPaginationAndSort,
   getVideoChannel,
-  getVideosList,
+  getVideosList, installPlugin,
   login,
   makePutBodyRequest,
   rateVideo,
@@ -57,6 +57,8 @@ describe('Test users', function () {
     server = await flushAndRunServer(1)
 
     await setAccessTokensToServers([ server ])
+
+    await installPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-theme-background-red' })
   })
 
   describe('OAuth client', function () {
@@ -551,6 +553,21 @@ describe('Test users', function () {
       expect(user.account.displayName).to.equal('new display name')
       expect(user.account.description).to.equal('my super description updated')
     })
+
+    it('Should be able to update my theme', async function () {
+      for (const theme of [ 'background-red', 'default', 'instance-default' ]) {
+        await updateMyUser({
+          url: server.url,
+          accessToken: accessTokenUser,
+          theme
+        })
+
+        const res = await getMyUserInformation(server.url, accessTokenUser)
+        const body: User = res.body
+
+        expect(body.theme).to.equal(theme)
+      }
+    })
   })
 
   describe('Updating another user', function () {
index c6b7ec0789dff08d2ea7753196d303925b580c86..5af286fe2e11b7d38054c446b3813323c4ca7f8d 100644 (file)
@@ -3,5 +3,6 @@ import './create-import-video-file-job'
 import './create-transcoding-job'
 import './optimize-old-videos'
 import './peertube'
+import './plugins'
 import './reset-password'
 import './update-host'
index d73e275640bb18a99ec61085879c87fd47e78add..b8c0b1f795e541cea386e2d0574793304e791faf 100644 (file)
@@ -43,133 +43,171 @@ describe('Test CLI wrapper', function () {
     }
   })
 
-  it('Should display no selected instance', async function () {
-    this.timeout(60000)
+  describe('Authentication and instance selection', function () {
 
-    const env = getEnvCli(server)
-    const stdout = await execCLI(`${env} ${cmd} --help`)
+    it('Should display no selected instance', async function () {
+      this.timeout(60000)
 
-    expect(stdout).to.contain('no instance selected')
-  })
+      const env = getEnvCli(server)
+      const stdout = await execCLI(`${env} ${cmd} --help`)
 
-  it('Should add a user', async function () {
-    this.timeout(60000)
+      expect(stdout).to.contain('no instance selected')
+    })
 
-    const env = getEnvCli(server)
-    await execCLI(`${env} ${cmd} auth add -u ${server.url} -U user_1 -p super_password`)
-  })
+    it('Should add a user', async function () {
+      this.timeout(60000)
 
-  it('Should default to this user', async function () {
-    this.timeout(60000)
+      const env = getEnvCli(server)
+      await execCLI(`${env} ${cmd} auth add -u ${server.url} -U user_1 -p super_password`)
+    })
 
-    const env = getEnvCli(server)
-    const stdout = await execCLI(`${env} ${cmd} --help`)
+    it('Should default to this user', async function () {
+      this.timeout(60000)
 
-    expect(stdout).to.contain(`instance ${server.url} selected`)
-  })
+      const env = getEnvCli(server)
+      const stdout = await execCLI(`${env} ${cmd} --help`)
 
-  it('Should remember the user', async function () {
-    this.timeout(60000)
+      expect(stdout).to.contain(`instance ${server.url} selected`)
+    })
 
-    const env = getEnvCli(server)
-    const stdout = await execCLI(`${env} ${cmd} auth list`)
+    it('Should remember the user', async function () {
+      this.timeout(60000)
 
-    expect(stdout).to.contain(server.url)
+      const env = getEnvCli(server)
+      const stdout = await execCLI(`${env} ${cmd} auth list`)
+
+      expect(stdout).to.contain(server.url)
+    })
   })
 
-  it('Should upload a video', async function () {
-    this.timeout(60000)
+  describe('Video upload/import', function () {
 
-    const env = getEnvCli(server)
+    it('Should upload a video', async function () {
+      this.timeout(60000)
 
-    const fixture = buildAbsoluteFixturePath('60fps_720p_small.mp4')
+      const env = getEnvCli(server)
 
-    const params = `-f ${fixture} --video-name 'test upload' --channel-name user_channel --support 'support_text'`
+      const fixture = buildAbsoluteFixturePath('60fps_720p_small.mp4')
 
-    await execCLI(`${env} ${cmd} upload ${params}`)
-  })
+      const params = `-f ${fixture} --video-name 'test upload' --channel-name user_channel --support 'support_text'`
 
-  it('Should have the video uploaded', async function () {
-    const res = await getVideosList(server.url)
+      await execCLI(`${env} ${cmd} upload ${params}`)
+    })
 
-    expect(res.body.total).to.equal(1)
+    it('Should have the video uploaded', async function () {
+      const res = await getVideosList(server.url)
 
-    const videos: Video[] = res.body.data
+      expect(res.body.total).to.equal(1)
 
-    const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body
+      const videos: Video[] = res.body.data
 
-    expect(video.name).to.equal('test upload')
-    expect(video.support).to.equal('support_text')
-    expect(video.channel.name).to.equal('user_channel')
-  })
+      const video: VideoDetails = (await getVideo(server.url, videos[ 0 ].uuid)).body
 
-  it('Should import a video', async function () {
-    this.timeout(60000)
+      expect(video.name).to.equal('test upload')
+      expect(video.support).to.equal('support_text')
+      expect(video.channel.name).to.equal('user_channel')
+    })
 
-    const env = getEnvCli(server)
+    it('Should import a video', async function () {
+      this.timeout(60000)
 
-    const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel`
+      const env = getEnvCli(server)
 
-    await execCLI(`${env} ${cmd} import ${params}`)
-  })
+      const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel`
 
-  it('Should have imported the video', async function () {
-    this.timeout(60000)
+      await execCLI(`${env} ${cmd} import ${params}`)
+    })
 
-    await waitJobs([ server ])
+    it('Should have imported the video', async function () {
+      this.timeout(60000)
 
-    const res = await getVideosList(server.url)
+      await waitJobs([ server ])
 
-    expect(res.body.total).to.equal(2)
+      const res = await getVideosList(server.url)
 
-    const videos: Video[] = res.body.data
-    const video = videos.find(v => v.name === 'small video - youtube')
-    expect(video).to.not.be.undefined
+      expect(res.body.total).to.equal(2)
 
-    const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
-    expect(videoDetails.channel.name).to.equal('user_channel')
-    expect(videoDetails.support).to.equal('super support text')
-    expect(videoDetails.nsfw).to.be.false
+      const videos: Video[] = res.body.data
+      const video = videos.find(v => v.name === 'small video - youtube')
+      expect(video).to.not.be.undefined
 
-    // So we can reimport it
-    await removeVideo(server.url, userAccessToken, video.id)
-  })
+      const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
+      expect(videoDetails.channel.name).to.equal('user_channel')
+      expect(videoDetails.support).to.equal('super support text')
+      expect(videoDetails.nsfw).to.be.false
 
-  it('Should import and override some imported attributes', async function () {
-    this.timeout(60000)
+      // So we can reimport it
+      await removeVideo(server.url, userAccessToken, video.id)
+    })
 
-    const env = getEnvCli(server)
+    it('Should import and override some imported attributes', async function () {
+      this.timeout(60000)
 
-    const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel --video-name toto --nsfw --support support`
+      const env = getEnvCli(server)
 
-    await execCLI(`${env} ${cmd} import ${params}`)
+      const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel --video-name toto --nsfw --support support`
 
-    await waitJobs([ server ])
+      await execCLI(`${env} ${cmd} import ${params}`)
 
-    {
-      const res = await getVideosList(server.url)
-      expect(res.body.total).to.equal(2)
+      await waitJobs([ server ])
 
-      const videos: Video[] = res.body.data
-      const video = videos.find(v => v.name === 'toto')
-      expect(video).to.not.be.undefined
+      {
+        const res = await getVideosList(server.url)
+        expect(res.body.total).to.equal(2)
 
-      const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
-      expect(videoDetails.channel.name).to.equal('user_channel')
-      expect(videoDetails.support).to.equal('support')
-      expect(videoDetails.nsfw).to.be.true
-      expect(videoDetails.commentsEnabled).to.be.true
-    }
+        const videos: Video[] = res.body.data
+        const video = videos.find(v => v.name === 'toto')
+        expect(video).to.not.be.undefined
+
+        const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
+        expect(videoDetails.channel.name).to.equal('user_channel')
+        expect(videoDetails.support).to.equal('support')
+        expect(videoDetails.nsfw).to.be.true
+        expect(videoDetails.commentsEnabled).to.be.true
+      }
+    })
   })
 
-  it('Should remove the auth user', async function () {
-    const env = getEnvCli(server)
+  describe('Admin auth', function () {
+
+    it('Should remove the auth user', async function () {
+      const env = getEnvCli(server)
+
+      await execCLI(`${env} ${cmd} auth del ${server.url}`)
+
+      const stdout = await execCLI(`${env} ${cmd} --help`)
+
+      expect(stdout).to.contain('no instance selected')
+    })
+
+    it('Should add the admin user', async function () {
+      const env = getEnvCli(server)
+      await execCLI(`${env} ${cmd} auth add -u ${server.url} -U root -p test${server.internalServerNumber}`)
+    })
+  })
+
+  describe('Manage plugins', function () {
+
+    it('Should install a plugin', async function () {
+      this.timeout(60000)
+
+      const env = getEnvCli(server)
+      await execCLI(`${env} ${cmd} plugins install --npm-name peertube-plugin-hello-world`)
+    })
+
+    it('Should list installed plugins', async function () {
+      const env = getEnvCli(server)
+      const res = await execCLI(`${env} ${cmd} plugins list`)
 
-    await execCLI(`${env} ${cmd} auth del ${server.url}`)
+      expect(res).to.contain('peertube-plugin-hello-world')
+    })
 
-    const stdout = await execCLI(`${env} ${cmd} --help`)
+    it('Should uninstall the plugin', async function () {
+      const env = getEnvCli(server)
+      const res = await execCLI(`${env} ${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`)
 
-    expect(stdout).to.contain('no instance selected')
+      expect(res).to.not.contain('peertube-plugin-hello-world')
+    })
   })
 
   after(async function () {
diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts
new file mode 100644 (file)
index 0000000..d7bf8a6
--- /dev/null
@@ -0,0 +1,87 @@
+/* tslint:disable:no-unused-expression */
+
+import 'mocha'
+import {
+  cleanupTests,
+  execCLI,
+  flushAndRunServer,
+  getConfig,
+  getEnvCli, killallServers,
+  reRunServer,
+  root,
+  ServerInfo,
+  setAccessTokensToServers
+} from '../../../shared/extra-utils'
+import { join } from 'path'
+import { ServerConfig } from '../../../shared/models/server'
+import { expect } from 'chai'
+
+describe('Test plugin scripts', function () {
+  let server: ServerInfo
+
+  before(async function () {
+    this.timeout(30000)
+
+    server = await flushAndRunServer(1)
+    await setAccessTokensToServers([ server ])
+  })
+
+  it('Should install a plugin from stateless CLI', async function () {
+    this.timeout(60000)
+
+    const packagePath = join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test')
+
+    const env = getEnvCli(server)
+    await execCLI(`${env} npm run plugin:install -- --plugin-path ${packagePath}`)
+  })
+
+  it('Should install a theme from stateless CLI', async function () {
+    this.timeout(60000)
+
+    const env = getEnvCli(server)
+    await execCLI(`${env} npm run plugin:install -- --npm-name peertube-theme-background-red`)
+  })
+
+  it('Should have the theme and the plugin registered when we restart peertube', async function () {
+    this.timeout(30000)
+
+    killallServers([ server ])
+    await reRunServer(server)
+
+    const res = await getConfig(server.url)
+    const config: ServerConfig = res.body
+
+    const plugin = config.plugin.registered
+                         .find(p => p.name === 'test')
+    expect(plugin).to.not.be.undefined
+
+    const theme = config.theme.registered
+                        .find(t => t.name === 'background-red')
+    expect(theme).to.not.be.undefined
+  })
+
+  it('Should uninstall a plugin from stateless CLI', async function () {
+    this.timeout(60000)
+
+    const env = getEnvCli(server)
+    await execCLI(`${env} npm run plugin:uninstall -- --npm-name peertube-plugin-test`)
+  })
+
+  it('Should have removed the plugin on another peertube restart', async function () {
+    this.timeout(30000)
+
+    killallServers([ server ])
+    await reRunServer(server)
+
+    const res = await getConfig(server.url)
+    const config: ServerConfig = res.body
+
+    const plugin = config.plugin.registered
+                         .find(p => p.name === 'test')
+    expect(plugin).to.be.undefined
+  })
+
+  after(async function () {
+    await cleanupTests([ server ])
+  })
+})
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
new file mode 100644 (file)
index 0000000..fae0ef9
--- /dev/null
@@ -0,0 +1,39 @@
+async function register ({ registerHook, registerSetting, settingsManager, storageManager }) {
+  const defaultAdmin = 'PeerTube admin'
+
+  registerHook({
+    target: 'action:application.listening',
+    handler: () => displayHelloWorld(settingsManager, defaultAdmin)
+  })
+
+  registerSetting({
+    name: 'admin-name',
+    label: 'Admin name',
+    type: 'input',
+    default: defaultAdmin
+  })
+
+  const value = await storageManager.getData('toto')
+  console.log(value)
+  console.log(value.coucou)
+
+  await storageManager.storeData('toto', { coucou: 'hello' + new Date() })
+}
+
+async function unregister () {
+  return
+}
+
+module.exports = {
+  register,
+  unregister
+}
+
+// ############################################################################
+
+async function displayHelloWorld (settingsManager, defaultAdmin) {
+  let value = await settingsManager.getSetting('admin-name')
+  if (!value) value = defaultAdmin
+
+  console.log('hello world ' + value)
+}
diff --git a/server/tests/fixtures/peertube-plugin-test/package.json b/server/tests/fixtures/peertube-plugin-test/package.json
new file mode 100644 (file)
index 0000000..9d6fe5c
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "name": "peertube-plugin-test",
+  "version": "0.0.1",
+  "description": "Plugin test",
+  "engine": {
+    "peertube": ">=1.3.0"
+  },
+  "keywords": [
+    "peertube",
+    "plugin"
+  ],
+  "homepage": "https://github.com/Chocobozzz/PeerTube",
+  "author": "Chocobozzz",
+  "bugs": "https://github.com/Chocobozzz/PeerTube/issues",
+  "library": "./main.js",
+  "staticDirs": {},
+  "css": [],
+  "clientScripts": []
+}
index ed16d65dd298c76a8b805c5a2492fc8c41c42c7d..8bddcfc7cde2eda67294f9db4dead0092fdfdc35 100644 (file)
@@ -3,3 +3,4 @@ import './client'
 import './feeds/'
 import './cli/'
 import './api/'
+import './plugins/'
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
new file mode 100644 (file)
index 0000000..8abab98
--- /dev/null
@@ -0,0 +1,27 @@
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
+import { setAccessTokensToServers } from '../../../shared/extra-utils'
+
+const expect = chai.expect
+
+describe('Test plugin filter hooks', function () {
+  let server: ServerInfo
+
+  before(async function () {
+    this.timeout(30000)
+    server = await flushAndRunServer(1)
+
+    await setAccessTokensToServers([ server ])
+  })
+
+  it('Should execute ', async function () {
+
+  })
+
+  after(async function () {
+    await cleanupTests([ server ])
+  })
+})
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
new file mode 100644 (file)
index 0000000..8abab98
--- /dev/null
@@ -0,0 +1,27 @@
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
+import { setAccessTokensToServers } from '../../../shared/extra-utils'
+
+const expect = chai.expect
+
+describe('Test plugin filter hooks', function () {
+  let server: ServerInfo
+
+  before(async function () {
+    this.timeout(30000)
+    server = await flushAndRunServer(1)
+
+    await setAccessTokensToServers([ server ])
+  })
+
+  it('Should execute ', async function () {
+
+  })
+
+  after(async function () {
+    await cleanupTests([ server ])
+  })
+})
diff --git a/server/tests/plugins/index.ts b/server/tests/plugins/index.ts
new file mode 100644 (file)
index 0000000..b640ecc
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './action-hooks'
+export * from './filter-hooks'
index e79a7e0419a336ec15ca78b498950c35ebf81222..ddfe5b771a5a22d44f3b7b36158c0e9afeaf42e0 100644 (file)
@@ -18,7 +18,7 @@ program
   .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
   .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
   .command('repl', 'initiate a REPL to access internals')
-  .command('plugins [action]', 'manage plugins on a local instance').alias('p')
+  .command('plugins [action]', 'manage instance plugins/themes').alias('p')
 
 /* Not Yet Implemented */
 program
index 5fa8cde0c6190817a0b1079c024f4eef733065b9..30ed1bf4a325eb1ab96434d36d43a46b1eeda9b9 100644 (file)
@@ -6,6 +6,7 @@ import { UserRegister } from '../../models/users/user-register.model'
 import { UserRole } from '../../models/users/user-role'
 import { ServerInfo } from '../server/servers'
 import { userLogin } from './login'
+import { UserUpdateMe } from '../../models/users'
 
 type CreateUserArgs = { url: string,
   accessToken: string,
@@ -224,19 +225,21 @@ function updateMyUser (options: {
   displayName?: string
   description?: string
   videosHistoryEnabled?: boolean
+  theme?: string
 }) {
   const path = '/api/v1/users/me'
 
-  const toSend = {}
-  if (options.currentPassword !== undefined && options.currentPassword !== null) toSend['currentPassword'] = options.currentPassword
-  if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword
-  if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy
-  if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
-  if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
-  if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
-  if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName
+  const toSend: UserUpdateMe = {}
+  if (options.currentPassword !== undefined && options.currentPassword !== null) toSend.currentPassword = options.currentPassword
+  if (options.newPassword !== undefined && options.newPassword !== null) toSend.password = options.newPassword
+  if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend.nsfwPolicy = options.nsfwPolicy
+  if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend.autoPlayVideo = options.autoPlayVideo
+  if (options.email !== undefined && options.email !== null) toSend.email = options.email
+  if (options.description !== undefined && options.description !== null) toSend.description = options.description
+  if (options.displayName !== undefined && options.displayName !== null) toSend.displayName = options.displayName
+  if (options.theme !== undefined && options.theme !== null) toSend.theme = options.theme
   if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) {
-    toSend['videosHistoryEnabled'] = options.videosHistoryEnabled
+    toSend.videosHistoryEnabled = options.videosHistoryEnabled
   }
 
   return makePutBodyRequest({
index 51efa2e8b569966bd8bb71da837277513e1a94c0..fa26fc3ccad0c7dc6eba6f2344e2df37ed54e2cf 100644 (file)
@@ -18,30 +18,35 @@ export enum VideoResolution {
  */
 function getBaseBitrate (resolution: VideoResolution) {
   switch (resolution) {
-  case VideoResolution.H_240P:
-    // quality according to Google Live Encoder: 300 - 700 Kbps
-    // Quality according to YouTube Video Info: 186 Kbps
-    return 250 * 1000
-  case VideoResolution.H_360P:
-    // quality according to Google Live Encoder: 400 - 1,000 Kbps
-    // Quality according to YouTube Video Info: 480 Kbps
-    return 500 * 1000
-  case VideoResolution.H_480P:
-    // quality according to Google Live Encoder: 500 - 2,000 Kbps
-    // Quality according to YouTube Video Info: 879 Kbps
-    return 900 * 1000
-  case VideoResolution.H_720P:
-    // quality according to Google Live Encoder: 1,500 - 4,000 Kbps
-    // Quality according to YouTube Video Info: 1752 Kbps
-    return 1750 * 1000
-  case VideoResolution.H_1080P:
-    // quality according to Google Live Encoder: 3000 - 6000 Kbps
-    // Quality according to YouTube Video Info: 3277 Kbps
-    return 3300 * 1000
-  case VideoResolution.H_4K: // fallthrough
-  default:
-    // quality according to Google Live Encoder: 13000 - 34000 Kbps
-    return 15000 * 1000
+    case VideoResolution.H_240P:
+      // quality according to Google Live Encoder: 300 - 700 Kbps
+      // Quality according to YouTube Video Info: 186 Kbps
+      return 250 * 1000
+
+    case VideoResolution.H_360P:
+      // quality according to Google Live Encoder: 400 - 1,000 Kbps
+      // Quality according to YouTube Video Info: 480 Kbps
+      return 500 * 1000
+
+    case VideoResolution.H_480P:
+      // quality according to Google Live Encoder: 500 - 2,000 Kbps
+      // Quality according to YouTube Video Info: 879 Kbps
+      return 900 * 1000
+
+    case VideoResolution.H_720P:
+      // quality according to Google Live Encoder: 1,500 - 4,000 Kbps
+      // Quality according to YouTube Video Info: 1752 Kbps
+      return 1750 * 1000
+
+    case VideoResolution.H_1080P:
+      // quality according to Google Live Encoder: 3000 - 6000 Kbps
+      // Quality according to YouTube Video Info: 3277 Kbps
+      return 3300 * 1000
+
+    case VideoResolution.H_4K: // fallthrough
+    default:
+      // quality according to Google Live Encoder: 13000 - 34000 Kbps
+      return 15000 * 1000
   }
 }
 
index ba6e2b12dd7e293b9d901d4d84b0a4fc23025b20..f0d3b15b24144ba30ba56517697434268b1d79e9 100644 (file)
@@ -75,6 +75,7 @@ You can access it as `peertube` via an alias in your `.bashrc` like `alias peert
     import-videos|import  import a video from a streaming platform
     watch|w               watch a video in the terminal ✩°。⋆
     repl                  initiate a REPL to access internals
+    plugins|p [action]    manag instance plugins
     help [cmd]            display help for [cmd]
 ```
 
@@ -102,6 +103,15 @@ And now that your video is online, you can watch it from the confort of your ter
 $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10
 ```
 
+To list, install, uninstall dynamically plugins/themes of an instance:
+
+```bash
+$ peertube plugins list
+$ peertube plugins install --path /local/plugin/path
+$ peertube plugins install --npm-name peertube-plugin-myplugin
+$ peertube plugins uninstall --npm-name peertube-plugin-myplugin
+```
+
 #### peertube-import-videos.js
 
 You 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:
 $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u target_username
 ```
 
+
+### plugin install/uninstall
+
+The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running.
+If 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).
+
+To install a plugin or a theme from the disk:
+
+```
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path
+```
+
+From NPM:
+
+```
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --npm-name peertube-plugin-myplugin
+```
+
+To uninstall a plugin or a theme:
+
+```
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin
+```
+
 ### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v8.x/api/repl.html))
 
 If you want to interact with the application libraries and objects even when PeerTube is not running, there is a REPL for that.