openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
407 lines (333 loc) • 14.9 kB
JavaScript
/* eslint-env mocha */
import { TransactionModel, TaskModel, ChannelModel } from '../../src/model'
import * as tasks from '../../src/tasks'
import * as testUtils from '../utils'
import { promisify } from 'util'
import * as constants from '../constants'
import { config } from '../../src/config'
import sinon from 'sinon'
import {ObjectId} from 'mongodb'
// const {ObjectId} = require('mongoose').Types
if (config.rerun == null) {
config.rerun = config.get('rerun')
}
describe('Rerun Task Tests', () => {
let server
const originalRerun = testUtils.clone(config.rerun || config.get('rerun'))
before(async () => {
Object.assign(config.rerun, {
httpPort: constants.SERVER_PORTS.rerunPort
})
})
after(async () => {
Object.assign(config.rerun, originalRerun)
})
afterEach(async () => {
if (server != null) {
await server.close()
}
server = null
})
describe('rerunGetTransaction', () => {
const DEFAULT_TRANSACTION = Object.freeze({
status: 'Failed',
request: {
timestamp: new Date().toISOString()
},
updatedBy: {
id: new ObjectId(),
name: 'Test'
}
})
afterEach(async () => {
await TransactionModel.deleteMany({})
})
it(`will fail if the transaction can't be found`, async () => {
const transactionID = `transactioniddoesntexist`
await promisify(tasks.rerunGetTransaction)(transactionID).should.rejectedWith(`Transaction ${transactionID} could not be found`)
})
it(`will fail if the transaction can't be rerun`, async () => {
const transaction = new TransactionModel(Object.assign({}, DEFAULT_TRANSACTION, { canRerun: false }))
await transaction.save()
const transactionID = transaction._id
await promisify(tasks.rerunGetTransaction)(transactionID).should.rejectedWith(`Transaction ${transactionID} cannot be rerun as there isn't enough information about the request`)
})
it('will find a transaction', async () => {
const transaction = new TransactionModel(Object.assign({}, DEFAULT_TRANSACTION, { canRerun: true }))
await transaction.save()
const transactionID = transaction._id
const foundTransaction = await promisify(tasks.rerunGetTransaction)(transactionID)
foundTransaction._id.toString().should.equal(transaction._id.toString())
})
})
describe('rerunSetHTTPRequestOptions', async () => {
const DEFAULT_TRANSACTION = Object.freeze({
_id: 'somefakeid',
request: {
path: '/test',
method: 'GET',
headers: {
Accept: 'text/plain'
}
}
})
it('will throw if the transaction is not set', async () => {
const rejectedMessage = `An empty Transaction object was supplied. Aborting HTTP options configuration`
await promisify(tasks.rerunSetHTTPRequestOptions)(null, null).should.rejectedWith(rejectedMessage)
await promisify(tasks.rerunSetHTTPRequestOptions)(undefined, undefined).should.rejectedWith(rejectedMessage)
})
it('will set the options', async () => {
const transaction = testUtils.clone(DEFAULT_TRANSACTION)
const taskId = 'testTaskId'
const options = await promisify(tasks.rerunSetHTTPRequestOptions)(transaction, taskId)
options.hostname.should.equal(config.rerun.host)
options.port.should.equal(config.rerun.httpPort)
options.path.should.equal(transaction.request.path)
options.method.should.equal(transaction.request.method)
options.headers.Accept.should.equal('text/plain')
options.headers.parentID.should.equal(transaction._id)
options.headers.taskID.should.equal(taskId)
})
it('will set client id in the header if defined', async () => {
const transaction = testUtils.clone(DEFAULT_TRANSACTION)
transaction.clientID = 'testClientID'
const options = await promisify(tasks.rerunSetHTTPRequestOptions)(transaction, null)
options.headers.clientID.should.equal(transaction.clientID)
})
it('will add the request.querystring to the path if defined', async () => {
const transaction = testUtils.clone(DEFAULT_TRANSACTION)
const testQueryString = transaction.request.querystring = 'testQueryStringValue'
const options = await promisify(tasks.rerunSetHTTPRequestOptions)(transaction, null)
options.path.should.equal(`${transaction.request.path}?${testQueryString}`)
})
})
describe('rerunHttpRequestSend', async () => {
const DEFAULT_HTTP_OPTIONS = Object.freeze({
host: 'localhost',
port: constants.HTTP_PORT,
path: '/test',
headers: {
some: 'value'
}
})
it('will throw an error if no options are sent in', async () => {
const expectedError = 'An empty \'Options\' object was supplied. Aborting HTTP Send Request'
await promisify(tasks.rerunHttpRequestSend)(null, null).should.rejectedWith(expectedError)
})
it('will throw an error if no transaction is sent in', async () => {
const expectedError = 'An empty \'Transaction\' object was supplied. Aborting HTTP Send Request'
await promisify(tasks.rerunHttpRequestSend)({}, null).should.rejectedWith(expectedError)
})
it('will rerun a transaction', async () => {
const options = Object.assign({}, DEFAULT_HTTP_OPTIONS)
const responsestr = 'Response string'
const spy = sinon.spy(req => responsestr)
const transaction = { request: {} }
server = await testUtils.createMockHttpServer(spy, undefined, 200)
const response = await promisify(tasks.rerunHttpRequestSend)(options, transaction)
response.body.should.equal(responsestr)
response.transaction.status.should.eql('Completed')
response.timestamp.should.Date()
response.headers.should.properties(testUtils.lowerCaseMembers(constants.DEFAULT_HEADERS))
response.status.should.eql(200)
response.message.should.eql('OK')
spy.callCount.should.eql(1)
const req = spy.args[0][0]
req.headers.should.properties(DEFAULT_HTTP_OPTIONS.headers)
})
it('will report if it failed', async () => {
const options = Object.assign({}, DEFAULT_HTTP_OPTIONS, { port: constants.PORT_START - 1 })
const transaction = { request: {} }
const response = await promisify(tasks.rerunHttpRequestSend)(options, transaction)
response.transaction.status.should.eql('Failed')
response.status.should.eql(500)
response.message.should.eql('Internal Server Error')
response.timestamp.should.Date()
})
it('will send the request body on post', async () => {
const spy = sinon.spy(req => testUtils.readBody(req))
server = await testUtils.createMockHttpServer(spy)
const options = Object.assign({}, DEFAULT_HTTP_OPTIONS, { method: 'POST' })
const transaction = { request: { method: 'POST', body: 'Hello Post' } }
const response = await promisify(tasks.rerunHttpRequestSend)(options, transaction)
response.body.should.eql(transaction.request.body) // The spy just sends back the data
spy.callCount.should.eql(1)
const req = spy.args[0][0]
req.method.should.eql('POST')
})
it('can handle a post with no body', async () => {
const spy = sinon.spy(req => testUtils.readBody(req))
server = await testUtils.createMockHttpServer(spy)
const options = Object.assign({}, DEFAULT_HTTP_OPTIONS, { method: 'POST' })
const transaction = { request: { method: 'POST', body: null } }
await promisify(tasks.rerunHttpRequestSend)(options, transaction)
spy.callCount.should.eql(1)
const req = spy.args[0][0]
req.method.should.eql('POST')
})
})
describe('rerunTcpRequestSend', async () => {
const DEFAULT_CHANNEL = Object.freeze({
tcpHost: 'localhost',
tcpPort: constants.TCP_PORT
})
const DEFAULT_TRANSACTION = Object.freeze({
request: {
body: 'Hello Tcp'
}
})
it('will do a tcp request', async () => {
const spy = sinon.spy((data) => data.toString().toLowerCase())
server = await testUtils.createMockTCPServer(spy)
const channel = Object.assign({}, DEFAULT_CHANNEL)
const transaction = Object.assign({}, DEFAULT_TRANSACTION)
const response = await promisify(tasks.rerunTcpRequestSend)(channel, transaction)
spy.callCount.should.eql(1)
response.status.should.eql(200)
response.transaction.status.should.eql('Completed')
response.message.should.eql('')
response.headers.should.eql({})
response.timestamp.should.Date()
response.body.should.eql(transaction.request.body.toLowerCase())
})
it('will correctly record an error', async () => {
const channel = Object.assign({}, DEFAULT_CHANNEL, { tcpPort: constants.PORT_START - 1 })
const transaction = Object.assign({}, DEFAULT_TRANSACTION)
const response = await promisify(tasks.rerunTcpRequestSend)(channel, transaction)
response.transaction.status.should.eql('Failed')
response.status.should.eql(500)
response.message.should.eql('Internal Server Error')
response.timestamp.should.Date()
})
})
describe('findAndProcessAQueuedTask', async () => {
const DEFAULT_CHANNEL = Object.freeze({
name: 'testChannel',
urlPattern: '.+',
type: 'http',
routes: [{
name: 'asdf',
host: 'localhost',
path: '/test1',
port: '12345'
}],
updatedBy: {
id: new ObjectId(),
name: 'Test'
}
})
const DEFAULT_TASK = Object.freeze({
status: 'Queued',
user: 'user'
})
const DEFAULT_TRANSACTION = Object.freeze({
status: 'Processing',
request: {
timestamp: new Date()
}
})
function createTask (transactions = [], taskOverrides = {}) {
const taskDoc = Object.assign({}, DEFAULT_TASK, {
remainingTransactions: transactions.length,
totalTransactions: transactions.length,
transactions: transactions.map(t => ({
tid: t._id,
tstatus: 'Queued'
}))
}, taskOverrides)
return new TaskModel(taskDoc).save()
}
const clearTasksFn = () => Promise.all([
TaskModel.deleteMany({}),
TransactionModel.deleteMany({}),
ChannelModel.deleteMany({})
])
before(async () => {
await clearTasksFn()
})
afterEach(async () => {
await clearTasksFn()
})
it(`will not throw if it doesn't find any tasks`, async () => {
await tasks.findAndProcessAQueuedTask().should.not.rejected()
})
it('will process empty tasks', async () => {
const originalTask = await createTask()
await tasks.findAndProcessAQueuedTask()
const updatedTask = await TaskModel.findById(originalTask._id)
updatedTask.status.should.eql('Completed')
})
it(`will process a single transactions`, async () => {
const channel = await new ChannelModel(DEFAULT_CHANNEL).save()
const originalTrans = await new TransactionModel(Object.assign({ channelID: channel._id }, DEFAULT_TRANSACTION)).save()
const originalTask = await createTask([originalTrans])
const spy = sinon.spy()
server = await testUtils.createMockHttpServer(spy, constants.SERVER_PORTS.rerunPort)
await tasks.findAndProcessAQueuedTask()
spy.callCount.should.eql(1)
const req = spy.args[0][0]
req.headers.should.properties({
parentid: originalTrans._id.toString(),
taskid: originalTask._id.toString()
})
await testUtils.setImmediatePromise()
const updatedTask = await TaskModel.findById(originalTask._id)
updatedTask.status.should.eql('Completed')
updatedTask.transactions[0].tstatus.should.eql('Completed')
})
it(`will process the batch size`, async () => {
const channel = await new ChannelModel(DEFAULT_CHANNEL).save()
const transactions = await Promise.all(
Array(3).fill(new TransactionModel(Object.assign({ channelID: channel._id }, DEFAULT_TRANSACTION)).save())
)
const originalTask = await createTask(transactions, { batchSize: 2 })
const spy = sinon.spy()
server = await testUtils.createMockHttpServer(spy, constants.SERVER_PORTS.rerunPort)
await tasks.findAndProcessAQueuedTask()
spy.callCount.should.eql(2)
const updatedTask = await TaskModel.findById(originalTask._id)
updatedTask.status.should.eql('Queued')
updatedTask.remainingTransactions.should.be.equal(1)
updatedTask.transactions[0].tstatus.should.be.equal('Completed')
updatedTask.transactions[1].tstatus.should.be.equal('Completed')
updatedTask.transactions[2].tstatus.should.be.equal('Queued')
})
it(`will process the transactions till they are completed`, async () => {
const channel = await new ChannelModel(DEFAULT_CHANNEL).save()
const transactions = await Promise.all(
Array(3).fill(new TransactionModel(Object.assign({ channelID: channel._id }, DEFAULT_TRANSACTION)).save())
)
const originalTask = await createTask(transactions, { batchSize: 2 })
const spy = sinon.spy()
server = await testUtils.createMockHttpServer(spy, constants.SERVER_PORTS.rerunPort)
await tasks.findAndProcessAQueuedTask()
spy.callCount.should.eql(2)
let updatedTask = await TaskModel.findById(originalTask._id)
updatedTask.status.should.eql('Queued')
updatedTask.remainingTransactions.should.be.equal(1)
updatedTask.transactions[0].tstatus.should.be.equal('Completed')
updatedTask.transactions[1].tstatus.should.be.equal('Completed')
updatedTask.transactions[2].tstatus.should.be.equal('Queued')
await tasks.findAndProcessAQueuedTask()
spy.callCount.should.eql(3)
updatedTask = await TaskModel.findById(originalTask._id)
updatedTask.status.should.eql('Completed')
updatedTask.remainingTransactions.should.be.equal(0)
updatedTask.transactions[2].tstatus.should.be.equal('Completed')
})
it(`not process a paused transaction`, async () => {
const channel = await new ChannelModel(DEFAULT_CHANNEL).save()
const originalTrans = await new TransactionModel(Object.assign({ channelID: channel._id }, DEFAULT_TRANSACTION)).save()
const originalTask = await createTask([originalTrans], { status: 'Paused' })
const spy = sinon.spy()
server = await testUtils.createMockHttpServer(spy, constants.SERVER_PORTS.rerunPort)
await tasks.findAndProcessAQueuedTask()
spy.callCount.should.eql(0)
await testUtils.setImmediatePromise()
const updatedTask = await TaskModel.findById(originalTask._id)
updatedTask.status.should.eql('Paused')
updatedTask.transactions[0].tstatus.should.eql('Queued')
})
// TODO : Have to add the failed transaction test
})
})