actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
278 lines (240 loc) • 9.14 kB
JavaScript
const uuid = require('uuid')
const NR = require('node-resque')
/**
* A speical "mock" server which enables you to test actions and tasks in a simple way. Only availalbe in the TEST environment.
*
* @namespace api.specHelper
* @property {Boolean} enabled - Is the specHelper server enabled
* @property {Object} Server - The instnace of the SpecHelper server
*/
module.exports = {
startPriority: 901,
loadPriority: 900,
initialize: function (api, next) {
if (api.env === 'test' || process.env.SPECHELPER === 'true' || process.env.SPECHELPER === true) {
api.specHelper = {
returnMetadata: true
}
// create a test 'server' to run actions
api.specHelper.initialize = function (api, options, next) {
const type = 'testServer'
const attributes = {
canChat: true,
logConnections: false,
logExits: false,
sendWelcomeMessage: true,
verbs: api.connections.allowedVerbs
}
const server = new api.GenericServer(type, options, attributes)
server.start = function (next) {
api.log('loading the testServer', 'warning')
next()
}
server.stop = function (next) {
next()
}
server.sendMessage = function (connection, message, messageCount) {
process.nextTick(() => {
connection.messages.push(message)
if (typeof connection.actionCallbacks[messageCount] === 'function') {
connection.actionCallbacks[messageCount](message, connection)
delete connection.actionCallbacks[messageCount]
}
})
}
server.sendFile = function (connection, error, fileStream, mime, length) {
let content = ''
let response = {
error: error,
content: null,
mime: mime,
length: length
}
try {
if (!error) {
fileStream.on('data', (d) => { content += d })
fileStream.on('end', () => {
response.content = content
server.sendMessage(connection, response, connection.messageCount)
})
} else {
server.sendMessage(connection, response, connection.messageCount)
}
} catch (e) {
api.log(e, 'warning')
server.sendMessage(connection, response, connection.messageCount)
}
}
server.goodbye = function () {
//
}
server.on('connection', function (connection) {
connection.messages = []
connection.actionCallbacks = {}
})
server.on('actionComplete', function (data) {
if (typeof data.response === 'string' || Array.isArray(data.response)) {
if (data.response.error) {
data.response = api.config.errors.serializers.servers.specHelper(data.response.error)
}
} else {
if (data.response.error) {
data.response.error = api.config.errors.serializers.servers.specHelper(data.response.error)
}
if (api.specHelper.returnMetadata) {
data.response.messageCount = data.messageCount
data.response.serverInformation = {
serverName: api.config.general.serverName,
apiVersion: api.config.general.apiVersion
}
data.response.requesterInformation = {
id: data.connection.id,
remoteIP: data.connection.remoteIP,
receivedParams: {}
}
for (let k in data.params) {
data.response.requesterInformation.receivedParams[k] = data.params[k]
}
}
}
if (data.toRender === true) {
server.sendMessage(data.connection, data.response, data.messageCount)
}
})
next(server)
}
/**
* A special connection usable in tests. Create via `new api.specHelper.Connection()`
*
* @memberof api.specHelper
*/
api.specHelper.connection = function () {
let id = uuid.v4()
api.servers.servers.testServer.buildConnection({
id: id,
rawConnection: {},
remoteAddress: 'testServer',
remotePort: 0
})
return api.connections.connections[id]
}
api.specHelper.Connection = api.specHelper.connection
/**
* Run an action via the specHelper server.
*
* @async
* @param {string} actionName The name of the action to run.
* @param {Object} input You can provide either a pre-build connection `new api.specHelper.Connection()`, or just a Object with params for your action.
* @param {nextCallback} callback The callback that handles the response.
*/
api.specHelper.runAction = function (actionName, input, next) {
let connection
if (typeof input === 'function' && !next) {
next = input
input = {}
}
if (input.id && input.type === 'testServer') {
connection = input
} else {
connection = new api.specHelper.Connection()
connection.params = input
}
connection.params.action = actionName
connection.messageCount++
if (typeof next === 'function') {
connection.actionCallbacks[(connection.messageCount)] = next
}
process.nextTick(() => {
api.servers.servers.testServer.processAction(connection)
})
}
/**
* This callback is invoked with the response from the action.
* @callback nextCallback
* @param {object} response The response from the action.
*/
/**
* Mock a specHelper connection requesting a file from the server.
*
* @async
* @param {string} file The name & path of the file to request.
* @param {fileCallback} callback The callback that handles the response.
* @return {Promise<Object>} The body contents and metadata of the file requested. Conatins: mime, length, body, and more.
*/
api.specHelper.getStaticFile = function (file, next) {
let connection = new api.specHelper.Connection()
connection.params.file = file
connection.messageCount++
if (typeof next === 'function') {
connection.actionCallbacks[(connection.messageCount)] = next
}
api.servers.servers.testServer.processFile(connection)
}
/**
* This callback is invoked with the body contents and metadata of the
* requested file. Conatins: mime, length, body, and more.
* @callback fileCallback
* @param {object} file The requested file.
*/
/**
* Use the specHelper to run a task.
* Note: this only runs the task's `run()` method, and no middleware. This is faster than api.specHelper.runFullTask.
*
* @param {string} taskName The name of the task.
* @param {Object} params Params to pass to the task
* @param {taskCallback} next The callback that handles the response.
* @see api.specHelper.runFullTask
*/
api.specHelper.runTask = function (taskName, params, next) {
api.tasks.tasks[taskName].run(api, params, next)
}
/**
* This callback is invoked with an error or the return value from a task.
* @callback taskCallback
* @param {Error} error An error or null.
* @param {object} returnValue A return value from the task.
*/
/**
* Use the specHelper to run a task.
* Note: this will run a full Task worker, and will also include any middleware. This is slower than api.specHelper.runTask.
*
* @param {string} taskName The name of the task.
* @param {Object} params Params to pass to the task
* @param {taskCallback} next The callback that handles the response.
* @see api.specHelper.runTask
*/
api.specHelper.runFullTask = function (taskName, params, next) {
let options = {
connection: api.redis.clients.tasks,
queues: api.config.tasks.queues || ['default']
}
let worker = new NR.worker(options, api.tasks.jobs) // eslint-disable-line
worker.connect((error) => {
if (error) {
return next(error)
}
worker.performInline(taskName, params, (error, result) => {
worker.end()
next(error, result)
})
})
}
next()
} else {
next()
}
},
start: function (api, next) {
if (api.env === 'test' || process.env.SPECHELPER === 'true' || process.env.SPECHELPER === true) {
api.specHelper.initialize(api, {}, (serverObject) => {
api.servers.servers.testServer = serverObject
api.servers.servers.testServer.start(() => {
next()
})
})
} else {
next()
}
}
}