Add hook filters tests

pull/1987/head
Chocobozzz 2019-07-19 17:30:41 +02:00 committed by Chocobozzz
parent 09071200c7
commit 89cd127560
18 changed files with 306 additions and 80 deletions

View File

@ -114,6 +114,7 @@ import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
import { PeerTubeSocket } from './server/lib/peertube-socket' import { PeerTubeSocket } from './server/lib/peertube-socket'
import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls' import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler' import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
import { Hooks } from './server/lib/plugins/hooks'
// ----------- Command line ----------- // ----------- Command line -----------
@ -269,7 +270,7 @@ async function startApplication () {
logger.info('Server listening on %s:%d', hostname, port) logger.info('Server listening on %s:%d', hostname, port)
logger.info('Web server: %s', WEBSERVER.URL) logger.info('Web server: %s', WEBSERVER.URL)
PluginManager.Instance.runHook('action:application.listening') Hooks.runAction('action:application.listening')
}) })
process.on('exit', () => { process.on('exit', () => {

View File

@ -208,7 +208,7 @@ function getAccountVideoRate (rateType: VideoRateType) {
async function videoController (req: express.Request, res: express.Response) { async function videoController (req: express.Request, res: express.Response) {
// We need more attributes // We need more attributes
const video = await VideoModel.loadForGetAPI(res.locals.video.id) const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id })
if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url)

View File

@ -85,8 +85,9 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
user: user user: user
}, 'filter:api.video-threads.list.params') }, 'filter:api.video-threads.list.params')
resultList = await Hooks.wrapPromise( resultList = await Hooks.wrapPromiseFun(
VideoCommentModel.listThreadsForApi(apiOptions), VideoCommentModel.listThreadsForApi,
apiOptions,
'filter:api.video-threads.list.result' 'filter:api.video-threads.list.result'
) )
} else { } else {
@ -112,8 +113,9 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
user: user user: user
}, 'filter:api.video-thread-comments.list.params') }, 'filter:api.video-thread-comments.list.params')
resultList = await Hooks.wrapPromise( resultList = await Hooks.wrapPromiseFun(
VideoCommentModel.listThreadCommentsForApi(apiOptions), VideoCommentModel.listThreadCommentsForApi,
apiOptions,
'filter:api.video-thread-comments.list.result' 'filter:api.video-thread-comments.list.result'
) )
} else { } else {

View File

@ -436,8 +436,9 @@ async function getVideo (req: express.Request, res: express.Response) {
// We need more attributes // We need more attributes
const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null
const video = await Hooks.wrapPromise( const video = await Hooks.wrapPromiseFun(
VideoModel.loadForGetAPI(res.locals.video.id, undefined, userId), VideoModel.loadForGetAPI,
{ id: res.locals.video.id, userId },
'filter:api.video.get.result' 'filter:api.video.get.result'
) )
@ -502,8 +503,9 @@ async function listVideos (req: express.Request, res: express.Response) {
user: res.locals.oauth ? res.locals.oauth.token.User : undefined user: res.locals.oauth ? res.locals.oauth.token.User : undefined
}, 'filter:api.videos.list.params') }, 'filter:api.videos.list.params')
const resultList = await Hooks.wrapPromise( const resultList = await Hooks.wrapPromiseFun(
VideoModel.listForApi(apiOptions), VideoModel.listForApi,
apiOptions,
'filter:api.videos.list.result' 'filter:api.videos.list.result'
) )

View File

@ -3,16 +3,25 @@ import { PluginManager } from './plugin-manager'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import * as Bluebird from 'bluebird' import * as Bluebird from 'bluebird'
type PromiseFunction <U, T> = (params: U) => Promise<T> | Bluebird<T>
type RawFunction <U, T> = (params: U) => T
// Helpers to run hooks // Helpers to run hooks
const Hooks = { const Hooks = {
wrapObject: <T, U extends ServerFilterHookName>(obj: T, hookName: U) => { wrapObject: <T, U extends ServerFilterHookName>(result: T, hookName: U) => {
return PluginManager.Instance.runHook(hookName, obj) as Promise<T> return PluginManager.Instance.runHook(hookName, result) as Promise<T>
}, },
wrapPromise: async <T, U extends ServerFilterHookName>(fun: Promise<T> | Bluebird<T>, hookName: U) => { wrapPromiseFun: async <U, T, V extends ServerFilterHookName>(fun: PromiseFunction<U, T>, params: U, hookName: V) => {
const result = await fun const result = await fun(params)
return PluginManager.Instance.runHook(hookName, result) return PluginManager.Instance.runHook(hookName, result, params)
},
wrapFun: async <U, T, V extends ServerFilterHookName>(fun: RawFunction<U, T>, params: U, hookName: V) => {
const result = fun(params)
return PluginManager.Instance.runHook(hookName, result, params)
}, },
runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => { runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => {

View File

@ -98,15 +98,15 @@ export class PluginManager implements ServerHook {
// ###################### Hooks ###################### // ###################### Hooks ######################
async runHook (hookName: ServerHookName, param?: any) { async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
let result = param if (!this.hooks[hookName]) return Promise.resolve(result)
if (!this.hooks[hookName]) return result
const hookType = getHookType(hookName) const hookType = getHookType(hookName)
for (const hook of this.hooks[hookName]) { for (const hook of this.hooks[hookName]) {
result = await internalRunHook(hook.handler, hookType, param, err => { logger.debug('Running hook %s of plugin %s.', hookName, hook.npmName)
result = await internalRunHook(hook.handler, hookType, result, params, err => {
logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err }) logger.error('Cannot run hook %s of plugin %s.', hookName, hook.pluginName, { err })
}) })
} }

View File

@ -9,8 +9,9 @@ import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
import { Hooks } from './plugins/hooks' import { Hooks } from './plugins/hooks'
async function autoBlacklistVideoIfNeeded (video: VideoModel, user?: UserModel, transaction?: Transaction) { async function autoBlacklistVideoIfNeeded (video: VideoModel, user?: UserModel, transaction?: Transaction) {
const doAutoBlacklist = await Hooks.wrapPromise( const doAutoBlacklist = await Hooks.wrapPromiseFun(
autoBlacklistNeeded({ video, user }), autoBlacklistNeeded,
{ video, user },
'filter:video.auto-blacklist.result' 'filter:video.auto-blacklist.result'
) )

View File

@ -444,8 +444,9 @@ async function isVideoAccepted (req: express.Request, res: express.Response, vid
videoFile, videoFile,
user: res.locals.oauth.token.User user: res.locals.oauth.token.User
} }
const acceptedResult = await Hooks.wrapObject( const acceptedResult = await Hooks.wrapFun(
isLocalVideoAccepted(acceptParameters), isLocalVideoAccepted,
acceptParameters,
'filter:api.video.upload.accept.result' 'filter:api.video.upload.accept.result'
) )

View File

@ -1472,7 +1472,12 @@ export class VideoModel extends Model<VideoModel> {
.findOne(options) .findOne(options)
} }
static loadForGetAPI (id: number | string, t?: Transaction, userId?: number) { static loadForGetAPI (parameters: {
id: number | string,
t?: Transaction,
userId?: number
}) {
const { id, t, userId } = parameters
const where = buildWhereIdOrUUID(id) const where = buildWhereIdOrUUID(id)
const options = { const options = {

View File

@ -6,13 +6,13 @@ import {
execCLI, execCLI,
flushAndRunServer, flushAndRunServer,
getConfig, getConfig,
getEnvCli, killallServers, getEnvCli,
getPluginTestPath,
killallServers,
reRunServer, reRunServer,
root,
ServerInfo, ServerInfo,
setAccessTokensToServers setAccessTokensToServers
} from '../../../shared/extra-utils' } from '../../../shared/extra-utils'
import { join } from 'path'
import { ServerConfig } from '../../../shared/models/server' import { ServerConfig } from '../../../shared/models/server'
import { expect } from 'chai' import { expect } from 'chai'
@ -29,7 +29,7 @@ describe('Test plugin scripts', function () {
it('Should install a plugin from stateless CLI', async function () { it('Should install a plugin from stateless CLI', async function () {
this.timeout(60000) this.timeout(60000)
const packagePath = join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test') const packagePath = getPluginTestPath()
const env = getEnvCli(server) const env = getEnvCli(server)
await execCLI(`${env} npm run plugin:install -- --plugin-path ${packagePath}`) await execCLI(`${env} npm run plugin:install -- --plugin-path ${packagePath}`)

View File

@ -0,0 +1,21 @@
async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) {
registerHook({
target: 'filter:api.videos.list.params',
handler: obj => addToCount(obj)
})
}
async function unregister () {
return
}
module.exports = {
register,
unregister
}
// ############################################################################
function addToCount (obj) {
return Object.assign({}, obj, { count: obj.count + 1 })
}

View File

@ -0,0 +1,19 @@
{
"name": "peertube-plugin-test-two",
"version": "0.0.1",
"description": "Plugin test 2",
"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": []
}

View File

@ -1,23 +1,52 @@
async function register ({ registerHook, registerSetting, settingsManager, storageManager }) { async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) {
const defaultAdmin = 'PeerTube admin' const actionHooks = [
'action:application.listening',
'action:api.video.updated',
'action:api.video.deleted',
'action:api.video.uploaded',
'action:api.video.viewed',
'action:api.video-thread.created',
'action:api.video-comment-reply.created',
'action:api.video-comment.deleted'
]
for (const h of actionHooks) {
registerHook({
target: h,
handler: () => peertubeHelpers.logger.debug('Run hook %s.', h)
})
}
registerHook({ registerHook({
target: 'action:application.listening', target: 'filter:api.videos.list.params',
handler: () => displayHelloWorld(settingsManager, defaultAdmin) handler: obj => addToCount(obj)
}) })
registerSetting({ registerHook({
name: 'admin-name', target: 'filter:api.videos.list.result',
label: 'Admin name', handler: obj => ({ data: obj.data, total: obj.total + 1 })
type: 'input',
default: defaultAdmin
}) })
const value = await storageManager.getData('toto') registerHook({
console.log(value) target: 'filter:api.video.get.result',
console.log(value.coucou) handler: video => {
video.name += ' <3'
await storageManager.storeData('toto', { coucou: 'hello' + new Date() }) return video
}
})
registerHook({
target: 'filter:api.video.upload.accept.result',
handler: ({ accepted }, { videoBody }) => {
if (accepted !== false) return { accepted: true }
if (videoBody.name.indexOf('bad word') !== -1) return { accepted: false, errorMessage: 'bad word '}
return { accepted: true }
}
})
} }
async function unregister () { async function unregister () {
@ -31,9 +60,6 @@ module.exports = {
// ############################################################################ // ############################################################################
async function displayHelloWorld (settingsManager, defaultAdmin) { function addToCount (obj) {
let value = await settingsManager.getSetting('admin-name') return Object.assign({}, obj, { count: obj.count + 1 })
if (!value) value = defaultAdmin
console.log('hello world ' + value)
} }

View File

@ -2,26 +2,101 @@
import * as chai from 'chai' import * as chai from 'chai'
import 'mocha' import 'mocha'
import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' import {
import { setAccessTokensToServers } from '../../../shared/extra-utils' cleanupTests,
flushAndRunMultipleServers,
flushAndRunServer, killallServers, reRunServer,
ServerInfo,
waitUntilLog
} from '../../../shared/extra-utils/server/servers'
import {
addVideoCommentReply,
addVideoCommentThread, deleteVideoComment,
getPluginTestPath,
installPlugin, removeVideo,
setAccessTokensToServers,
updateVideo,
uploadVideo,
viewVideo
} from '../../../shared/extra-utils'
const expect = chai.expect const expect = chai.expect
describe('Test plugin action hooks', function () { describe('Test plugin action hooks', function () {
let server: ServerInfo let servers: ServerInfo[]
let videoUUID: string
let threadId: number
function checkHook (hook: string) {
return waitUntilLog(servers[0], 'Run hook ' + hook)
}
before(async function () { before(async function () {
this.timeout(30000) this.timeout(30000)
server = await flushAndRunServer(1)
await setAccessTokensToServers([ server ]) servers = await flushAndRunMultipleServers(2)
await setAccessTokensToServers(servers)
await installPlugin({
url: servers[0].url,
accessToken: servers[0].accessToken,
path: getPluginTestPath()
}) })
it('Should execute ', async function () { await killallServers([ servers[0] ])
// empty
await reRunServer(servers[0])
})
it('Should run action:application.listening', async function () {
await checkHook('action:application.listening')
})
it('Should run action:api.video.uploaded', async function () {
const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' })
videoUUID = res.body.video.uuid
await checkHook('action:api.video.uploaded')
})
it('Should run action:api.video.updated', async function () {
await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video updated' })
await checkHook('action:api.video.updated')
})
it('Should run action:api.video.viewed', async function () {
await viewVideo(servers[0].url, videoUUID)
await checkHook('action:api.video.viewed')
})
it('Should run action:api.video-thread.created', async function () {
const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread')
threadId = res.body.comment.id
await checkHook('action:api.video-thread.created')
})
it('Should run action:api.video-comment-reply.created', async function () {
await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'reply')
await checkHook('action:api.video-comment-reply.created')
})
it('Should run action:api.video-comment.deleted', async function () {
await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
await checkHook('action:api.video-comment.deleted')
})
it('Should run action:api.video.deleted', async function () {
await removeVideo(servers[0].url, servers[0].accessToken, videoUUID)
await checkHook('action:api.video.deleted')
}) })
after(async function () { after(async function () {
await cleanupTests([ server ]) await cleanupTests(servers)
}) })
}) })

View File

@ -2,26 +2,79 @@
import * as chai from 'chai' import * as chai from 'chai'
import 'mocha' import 'mocha'
import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' import {
import { setAccessTokensToServers } from '../../../shared/extra-utils' cleanupTests,
flushAndRunMultipleServers,
flushAndRunServer, killallServers, reRunServer,
ServerInfo,
waitUntilLog
} from '../../../shared/extra-utils/server/servers'
import {
addVideoCommentReply,
addVideoCommentThread, deleteVideoComment,
getPluginTestPath, getVideosList,
installPlugin, removeVideo,
setAccessTokensToServers,
updateVideo,
uploadVideo,
viewVideo,
getVideosListPagination, getVideo
} from '../../../shared/extra-utils'
const expect = chai.expect const expect = chai.expect
describe('Test plugin filter hooks', function () { describe('Test plugin filter hooks', function () {
let server: ServerInfo let servers: ServerInfo[]
let videoUUID: string
let threadId: number
before(async function () { before(async function () {
this.timeout(30000) this.timeout(30000)
server = await flushAndRunServer(1)
await setAccessTokensToServers([ server ]) servers = await flushAndRunMultipleServers(2)
await setAccessTokensToServers(servers)
await installPlugin({
url: servers[0].url,
accessToken: servers[0].accessToken,
path: getPluginTestPath()
}) })
it('Should execute ', async function () { await installPlugin({
// empty url: servers[0].url,
accessToken: servers[0].accessToken,
path: getPluginTestPath('-two')
})
for (let i = 0; i < 10; i++) {
await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'default video ' + i })
}
const res = await getVideosList(servers[0].url)
videoUUID = res.body.data[0].uuid
})
it('Should run filter:api.videos.list.params hook', async function () {
const res = await getVideosListPagination(servers[0].url, 0, 2)
// 2 plugins do +1 to the count parameter
expect(res.body.data).to.have.lengthOf(4)
})
it('Should run filter:api.videos.list.result', async function () {
const res = await getVideosListPagination(servers[0].url, 0, 0)
// Plugin do +1 to the total result
expect(res.body.total).to.equal(11)
})
it('Should run filter:api.video.get.result', async function () {
const res = await getVideo(servers[0].url, videoUUID)
expect(res.body.name).to.contain('<3')
}) })
after(async function () { after(async function () {
await cleanupTests([ server ]) await cleanupTests(servers)
}) })
}) })

View File

@ -8,25 +8,30 @@ function getHookType (hookName: string) {
return HookType.STATIC return HookType.STATIC
} }
async function internalRunHook (handler: Function, hookType: HookType, param: any, onError: (err: Error) => void) { async function internalRunHook <T>(handler: Function, hookType: HookType, result: T, params: any, onError: (err: Error) => void) {
let result = param
try { try {
const p = handler(result) if (hookType === HookType.FILTER) {
const p = handler(result, params)
switch (hookType) {
case HookType.FILTER:
if (isPromise(p)) result = await p if (isPromise(p)) result = await p
else result = p else result = p
break
case HookType.STATIC: return result
}
// Action/static hooks do not have result value
const p = handler(params)
if (hookType === HookType.STATIC) {
if (isPromise(p)) await p if (isPromise(p)) await p
break
case HookType.ACTION: return undefined
}
if (hookType === HookType.ACTION) {
if (isCatchable(p)) p.catch(err => onError(err)) if (isCatchable(p)) p.catch(err => onError(err))
break
return undefined
} }
} catch (err) { } catch (err) {
onError(err) onError(err)

View File

@ -201,6 +201,10 @@ function getPluginPackageJSON (server: ServerInfo, npmName: string) {
return readJSON(path) return readJSON(path)
} }
function getPluginTestPath (suffix = '') {
return join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test' + suffix)
}
export { export {
listPlugins, listPlugins,
listAvailablePlugins, listAvailablePlugins,
@ -213,5 +217,6 @@ export {
getPluginRegisteredSettings, getPluginRegisteredSettings,
getPackageJSONPath, getPackageJSONPath,
updatePluginPackageJSON, updatePluginPackageJSON,
getPluginPackageJSON getPluginPackageJSON,
getPluginTestPath
} }

View File

@ -171,7 +171,8 @@ async function runServer (server: ServerInfo, configOverrideArg?: any, args = []
thumbnails: `test${server.internalServerNumber}/thumbnails/`, thumbnails: `test${server.internalServerNumber}/thumbnails/`,
torrents: `test${server.internalServerNumber}/torrents/`, torrents: `test${server.internalServerNumber}/torrents/`,
captions: `test${server.internalServerNumber}/captions/`, captions: `test${server.internalServerNumber}/captions/`,
cache: `test${server.internalServerNumber}/cache/` cache: `test${server.internalServerNumber}/cache/`,
plugins: `test${server.internalServerNumber}/plugins/`
}, },
admin: { admin: {
email: `admin${server.internalServerNumber}@example.com` email: `admin${server.internalServerNumber}@example.com`