UNPKG

botium-core

Version:
304 lines (282 loc) 10.9 kB
const fs = require('fs') const util = require('util') const path = require('path') const findRoot = require('find-root') const async = require('async') const request = require('request') const io = require('socket.io-client') const copydir = require('copy-dir') const _ = require('lodash') const debug = require('debug')('botium-DockerMocks') const TcpPortUtils = require('../helpers/TcpPortUtils') const Capabilities = require('../Capabilities') const Events = require('../Events') const ProcessUtils = require('../helpers/ProcessUtils') const SafeFileCopy = require('../helpers/SafeFileCopy') const BotiumMockMessage = require('../mocks/BotiumMockMessage') const BotiumMockCommand = require('../mocks/BotiumMockCommand') const botiumPackageRootDir = findRoot() class BaseMock { constructor () { this.publishPort = null this.publishIp = null this.capNamePublishPort = null this.capNamePublishPortRange = null this.mockDir = null this.packageDir = null this.initCommand = null this.dockerFile = null this.dockerComposeFile = null } SelectPublishPort (publishIp, caps) { this.publishIp = publishIp if (caps[this.capNamePublishPort]) { this.publishPort = caps[this.capNamePublishPort] if (caps[Capabilities.BOTIUMGRIDSLOT]) { this.publishPort += caps[Capabilities.BOTIUMGRIDSLOT] } return Promise.resolve() } else { return new Promise((resolve, reject) => { TcpPortUtils.GetFreePortInRange(this.publishIp, caps[this.capNamePublishPortRange]) .then((port) => { this.publishPort = port resolve() }) .catch(reject) }) } } PrepareDocker (mockDir) { this.mockDir = mockDir return new Promise((resolve, reject) => { async.series([ (packageCopied) => { copydir(path.resolve(botiumPackageRootDir, this.packageDir), this.mockDir, (err) => { if (err) return packageCopied(`Error copying mock to ${this.mockDir}: ${util.inspect(err)}`) if (this.initCommand) { ProcessUtils.childCommandLineRun(this.initCommand, false, { cwd: this.mockDir }) .then(() => packageCopied()) .catch(packageCopied) } else { packageCopied() } }) }, (dockerfileCopied) => { const dockermockOverride = path.resolve(process.cwd(), this.dockerFile) fs.stat(dockermockOverride, (err, stats) => { if (!err && stats.isFile()) { debug(`Docker file ${dockermockOverride} present, using it.`) SafeFileCopy(dockermockOverride, path.resolve(this.mockDir, this.dockerFile)) .then(() => dockerfileCopied()) .catch((err) => dockerfileCopied(`Copying Docker file ${dockermockOverride} failed ${util.inspect(err)}`)) } else { dockerfileCopied() } }) } ], (err) => { if (err) { return reject(new Error(`PrepareDocker failed ${util.inspect(err)}`)) } resolve() }) }) } GetDockerCompose () { return new Promise((resolve) => { const dockermockOverride = path.resolve(process.cwd(), this.dockerComposeFile) fs.stat(dockermockOverride, (err, stats) => { if (!err && stats.isFile()) { debug(`Docker file ${dockermockOverride} present, using it.`) resolve(dockermockOverride) } else { resolve(path.resolve(this.mockDir, this.dockerComposeFile)) } }) }) } Start (container) { return new Promise((resolve, reject) => { async.series([ (mockupOnline) => { TcpPortUtils.WaitForPort(this.publishIp, this.publishPort) .then(() => mockupOnline()) .catch(mockupOnline) }, (endpointOnline) => { this.mockUrl = `http://${this.publishIp}:${this.publishPort}` this._waitMockOnline(this.mockUrl).then(() => endpointOnline()).catch(endpointOnline) }, (socketStartDone) => { if (this.socket) { this.socket.disconnect() this.socket = null } this.socket = io.connect(this.mockUrl) this.socket.on(BotiumMockCommand.MOCKCMD_RECEIVEDFROMBOT, (botMsg) => { this.debug(`Mock - socket received from bot ${util.inspect(botMsg)}`) container._QueueBotSays(new BotiumMockMessage(botMsg)) }) this.socket.on('error', (err) => { this.debug(`Mock - socket connection error! ${util.inspect(err)}`) }) this.socket.on('connect', () => { this.debug(`Mock - socket connected ${this.mockUrl}`) container.eventEmitter.emit(Events.BOT_CONNECTED, this, this.socket) socketStartDone() }) } ], (err) => { if (err) { return reject(new Error(`Start failed ${util.inspect(err)}`)) } resolve() }) }) } Stop () { return new Promise((resolve) => { if (this.socket) { this.socket.disconnect() this.socket = null this.debug('Socket disconnected') } resolve() }) } async _waitMockOnline (mockUrl) { const timeout = ms => new Promise(resolve => setTimeout(resolve, ms)) const options = { uri: mockUrl, method: 'GET' } while (true) { this.debug(`Mock - checking endpoint ${mockUrl} is online before proceed ...`) const { err, response, body } = await new Promise((resolve) => { request(options, (err, response, body) => { resolve({ err, response, body }) }) }) if (!err && response.statusCode === 200) { this.debug(`Mock - endpoint ${mockUrl} is online (${body})`) return } else if (err) { this.debug(`Mock - endpoint ${mockUrl} not online (${err} - ${body}), checking again in 2 seconds ...`) await timeout(2000) } else if (response) { this.debug(`Mock - endpoint ${mockUrl} not online (Status Code: ${response.statusCode} - ${body}), checking again in 2 seconds ...`) await timeout(2000) } else { throw new Error('Mock - checking endpoint didn\'t either return error nor response.') } } } } module.exports = { Facebook: class FacebookMock extends BaseMock { constructor () { super() this.capNamePublishPort = Capabilities.FACEBOOK_PUBLISHPORT this.capNamePublishPortRange = Capabilities.FACEBOOK_PUBLISHPORT_RANGE this.packageDir = 'src/mocks/facebook' this.dockerFile = 'Dockerfile.fbmock' this.dockerComposeFile = 'docker-compose.fbmock.yml' this.debug = require('debug')('botium-FacebookMock') } FillDockerEnv (composeEnv, caps, logging) { composeEnv.services['botium-fbmock'] = { build: { context: this.mockDir }, logging: _.cloneDeep(logging), volumes: [ `${this.mockDir}:/usr/src/app` ], ports: [ `${this.publishPort}:${this.publishPort}` ], environment: { BOTIUM_FACEBOOK_WEBHOOKPORT: caps[Capabilities.FACEBOOK_WEBHOOK_PORT], BOTIUM_FACEBOOK_WEBHOOKPATH: caps[Capabilities.FACEBOOK_WEBHOOK_PATH], BOTIUM_FACEBOOK_PUBLISHPORT: this.publishPort, BOTIUM_FACEBOOK_SENDDELIVERY: `${caps[Capabilities.FACEBOOK_SEND_DELIVERY_CONFIRMATION]}`, BOTIUM_FACEBOOK_HEALTH_CHECK_PATH: `${caps[Capabilities.BOT_HEALTH_CHECK_PATH] || ''}`, BOTIUM_FACEBOOK_HEALTH_CHECK_VERB: `${caps[Capabilities.BOT_HEALTH_CHECK_VERB] || ''}`, BOTIUM_FACEBOOK_HEALTH_CHECK_STATUS: `${caps[Capabilities.BOT_HEALTH_STATUS]}` } } } }, Slack: class SlackMock extends BaseMock { constructor () { super() this.capNamePublishPort = Capabilities.SLACK_PUBLISHPORT this.capNamePublishPortRange = Capabilities.SLACK_PUBLISHPORT_RANGE this.packageDir = 'src/mocks/slack' this.dockerFile = 'Dockerfile.slackmock' this.dockerComposeFile = 'docker-compose.slackmock.yml' this.debug = require('debug')('botium-SlackMock') } FillDockerEnv (composeEnv, caps, logging) { composeEnv.services['botium-slackmock'] = { build: { context: this.mockDir }, logging: _.cloneDeep(logging), volumes: [ `${this.mockDir}:/usr/src/app` ], ports: [ `${this.publishPort}:${this.publishPort}` ], environment: { BOTIUM_SLACK_EVENTPORT: caps[Capabilities.SLACK_EVENT_PORT], BOTIUM_SLACK_EVENTPATH: caps[Capabilities.SLACK_EVENT_PATH], BOTIUM_SLACK_OAUTHPORT: caps[Capabilities.SLACK_OAUTH_PORT], BOTIUM_SLACK_OAUTHPATH: caps[Capabilities.SLACK_OAUTH_PATH], BOTIUM_SLACK_PUBLISHPORT: this.publishPort, BOTIUM_SLACK_HEALTH_CHECK_PATH: `${caps[Capabilities.BOT_HEALTH_CHECK_PATH] || ''}`, BOTIUM_SLACK_HEALTH_CHECK_VERB: `${caps[Capabilities.BOT_HEALTH_CHECK_VERB] || ''}`, BOTIUM_SLACK_HEALTH_CHECK_STATUS: `${caps[Capabilities.BOT_HEALTH_STATUS]}` } } } }, BotFramework: class BotFrameworkMock extends BaseMock { constructor () { super() this.capNamePublishPort = Capabilities.BOTFRAMEWORK_PUBLISHPORT this.capNamePublishPortRange = Capabilities.BOTFRAMEWORK_PUBLISHPORT_RANGE this.packageDir = 'src/mocks/botframework' this.dockerFile = 'Dockerfile.botframeworkmock' this.dockerComposeFile = 'docker-compose.botframeworkmock.yml' this.debug = require('debug')('botium-BotFrameworkMock') } FillDockerEnv (composeEnv, caps, logging) { composeEnv.services['botium-botframeworkmock'] = { build: { context: this.mockDir }, logging: _.cloneDeep(logging), volumes: [ `${this.mockDir}:/usr/src/app` ], ports: [ `${this.publishPort}:${this.publishPort}` ], environment: { BOTIUM_BOTFRAMEWORK_WEBHOOKPORT: caps[Capabilities.BOTFRAMEWORK_WEBHOOK_PORT], BOTIUM_BOTFRAMEWORK_WEBHOOKPATH: caps[Capabilities.BOTFRAMEWORK_WEBHOOK_PATH], BOTIUM_BOTFRAMEWORK_APP_ID: caps[Capabilities.BOTFRAMEWORK_APP_ID], BOTIUM_BOTFRAMEWORK_CHANNEL_ID: caps[Capabilities.BOTFRAMEWORK_CHANNEL_ID], BOTIUM_BOTFRAMEWORK_PUBLISHPORT: this.publishPort, BOTIUM_BOTFRAMEWORK_HEALTH_CHECK_PATH: `${caps[Capabilities.BOT_HEALTH_CHECK_PATH] || ''}`, BOTIUM_BOTFRAMEWORK_HEALTH_CHECK_VERB: `${caps[Capabilities.BOT_HEALTH_CHECK_VERB] || ''}`, BOTIUM_BOTFRAMEWORK_HEALTH_CHECK_STATUS: `${caps[Capabilities.BOT_HEALTH_STATUS]}` } } } } }