mirror of
https://github.com/leon-ai/leon.git
synced 2024-09-20 14:27:40 +03:00
test(server): over HTTP unit testing
This commit is contained in:
parent
af26ecac0f
commit
1535fa67ac
3730
package-lock.json
generated
3730
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -97,9 +97,9 @@
|
||||
"git-changelog": "^2.0.0",
|
||||
"husky": "^7.0.0",
|
||||
"inquirer": "^8.1.0",
|
||||
"jest": "^27.4.5",
|
||||
"jest": "^27.4.7",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-extended": "^1.2.0",
|
||||
"jest-extended": "^2.0.0",
|
||||
"json": "^10.0.0",
|
||||
"nodemon": "^2.0.7",
|
||||
"semver": "^7.3.5",
|
||||
|
@ -198,6 +198,7 @@ class Brain {
|
||||
|
||||
// Handle error
|
||||
this.process.stderr.on('data', (data) => {
|
||||
console.debug('DATA', data.toString())
|
||||
const speech = `${this.wernicke('random_package_module_errors', '',
|
||||
{ '%module_name%': moduleName, '%package_name%': packageName })}!`
|
||||
if (!opts.mute) {
|
||||
|
@ -20,15 +20,16 @@ import log from '@/helpers/log'
|
||||
import date from '@/helpers/date'
|
||||
|
||||
const server = { }
|
||||
const fastify = Fastify()
|
||||
let brain = { }
|
||||
let nlu = { }
|
||||
let httpServer = { }
|
||||
|
||||
server.fastify = Fastify()
|
||||
server.httpServer = { }
|
||||
|
||||
/**
|
||||
* Generate packages routes
|
||||
*/
|
||||
const generatePackagesRoutes = (instance) => {
|
||||
server.generatePackagesRoutes = (instance) => {
|
||||
// Dynamically expose Leon modules over HTTP
|
||||
endpoints.forEach((endpoint) => {
|
||||
instance.route({
|
||||
@ -126,7 +127,7 @@ const generatePackagesRoutes = (instance) => {
|
||||
/**
|
||||
* Bootstrap socket
|
||||
*/
|
||||
const handleOnConnection = (socket) => {
|
||||
server.handleOnConnection = (socket) => {
|
||||
log.title('Client')
|
||||
log.success('Connected')
|
||||
|
||||
@ -197,37 +198,38 @@ const handleOnConnection = (socket) => {
|
||||
/**
|
||||
* Launch server
|
||||
*/
|
||||
const listen = async (port) => {
|
||||
server.listen = async (port) => {
|
||||
const io = process.env.LEON_NODE_ENV === 'development'
|
||||
? socketio(httpServer, { cors: { origin: `${process.env.LEON_HOST}:3000` } })
|
||||
: socketio(httpServer)
|
||||
? socketio(server.httpServer, { cors: { origin: `${process.env.LEON_HOST}:3000` } })
|
||||
: socketio(server.httpServer)
|
||||
|
||||
io.on('connection', handleOnConnection)
|
||||
io.on('connection', server.handleOnConnection)
|
||||
|
||||
await fastify.listen(port, '0.0.0.0')
|
||||
await server.fastify.listen(port, '0.0.0.0')
|
||||
log.title('Initialization')
|
||||
log.success(`Server is available at ${process.env.LEON_HOST}:${port}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap API
|
||||
*/
|
||||
const bootstrap = async () => {
|
||||
server.bootstrap = async () => {
|
||||
const apiVersion = 'v1'
|
||||
|
||||
// Render the web app
|
||||
fastify.register(fastifyStatic, {
|
||||
server.fastify.register(fastifyStatic, {
|
||||
root: join(__dirname, '../../../../app/dist'),
|
||||
prefix: '/'
|
||||
})
|
||||
fastify.get('/', (request, reply) => {
|
||||
server.fastify.get('/', (request, reply) => {
|
||||
reply.sendFile('index.html')
|
||||
})
|
||||
|
||||
fastify.register(infoPlugin, { apiVersion })
|
||||
fastify.register(downloadsPlugin, { apiVersion })
|
||||
server.fastify.register(infoPlugin, { apiVersion })
|
||||
server.fastify.register(downloadsPlugin, { apiVersion })
|
||||
|
||||
if (process.env.LEON_OVER_HTTP === 'true') {
|
||||
fastify.register((instance, opts, next) => {
|
||||
server.fastify.register((instance, opts, next) => {
|
||||
instance.addHook('preHandler', keyMidd)
|
||||
|
||||
instance.post('/api/query', async (request, reply) => {
|
||||
@ -249,16 +251,16 @@ const bootstrap = async () => {
|
||||
}
|
||||
})
|
||||
|
||||
generatePackagesRoutes(instance)
|
||||
server.generatePackagesRoutes(instance)
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
httpServer = fastify.server
|
||||
server.httpServer = server.fastify.server
|
||||
|
||||
try {
|
||||
await listen(process.env.LEON_PORT)
|
||||
await server.listen(process.env.LEON_PORT)
|
||||
} catch (e) {
|
||||
log.error(e.message)
|
||||
}
|
||||
@ -268,8 +270,8 @@ const bootstrap = async () => {
|
||||
* Server entry point
|
||||
*/
|
||||
server.init = async () => {
|
||||
fastify.addHook('onRequest', corsMidd)
|
||||
fastify.addHook('preValidation', otherMidd)
|
||||
server.fastify.addHook('onRequest', corsMidd)
|
||||
server.fastify.addHook('preValidation', otherMidd)
|
||||
|
||||
log.title('Initialization')
|
||||
log.success(`The current env is ${process.env.LEON_NODE_ENV}`)
|
||||
@ -296,7 +298,7 @@ server.init = async () => {
|
||||
log[e.type](e.obj.message)
|
||||
}
|
||||
|
||||
await bootstrap()
|
||||
await server.bootstrap()
|
||||
}
|
||||
|
||||
export default server
|
||||
|
@ -162,7 +162,7 @@ class Nlu {
|
||||
return resolve({
|
||||
processingTime, // In ms, total time
|
||||
...data,
|
||||
nluProcessingTime: processingTime - data.executionTime // In ms, NLU processing time only
|
||||
nluProcessingTime: processingTime - data?.executionTime // In ms, NLU processing time only
|
||||
})
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
log[e.type](e.obj.message)
|
||||
|
@ -31,7 +31,7 @@ log.error = (value) => {
|
||||
|
||||
log.warning = (value) => console.warn('\x1b[33m❗ %s\x1b[0m', value)
|
||||
|
||||
log.title = (value) => console.log('\n---\n\n\x1b[7m.: %s :.\x1b[0m\n', value.toUpperCase())
|
||||
log.title = (value) => console.log('\n\n\x1b[7m.: %s :.\x1b[0m', value.toUpperCase())
|
||||
|
||||
log.default = (value) => console.log('%s', value)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import Brain from '@/core/brain'
|
||||
jest.setTimeout(60000)
|
||||
|
||||
global.nlu = new Nlu()
|
||||
global.brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
global.brain = new Brain('en')
|
||||
global.nlu.brain = { wernicke: jest.fn(), talk: jest.fn(), socket: { emit: jest.fn() } }
|
||||
global.brain.tts = {
|
||||
synthesizer: jest.fn(),
|
||||
|
@ -26,7 +26,7 @@ describe('NLU modules', () => {
|
||||
describe(`${langKeys[i]} language`, () => {
|
||||
const lang = langs[langKeys[i]]
|
||||
const nlu = new Nlu()
|
||||
const brain = new Brain({ emit: jest.fn() }, lang.short)
|
||||
const brain = new Brain(lang.short)
|
||||
let expressionsObj = { }
|
||||
|
||||
nlu.brain = { wernicke: jest.fn(), talk: jest.fn(), socket: { emit: jest.fn() } }
|
||||
|
@ -5,7 +5,7 @@ import Brain from '@/core/brain'
|
||||
describe('brain', () => {
|
||||
describe('constructor()', () => {
|
||||
test('creates a new instance of Brain', () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
|
||||
expect(brain).toBeInstanceOf(Brain)
|
||||
})
|
||||
@ -13,15 +13,18 @@ describe('brain', () => {
|
||||
|
||||
describe('talk()', () => {
|
||||
test('does not emit answer to the client when the speech is empty', () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
|
||||
brain.socket.emit = jest.fn()
|
||||
|
||||
brain.talk('')
|
||||
expect(brain.socket.emit).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
test('emits string answer to the client', () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
brain.tts = { add: jest.fn() }
|
||||
brain.socket.emit = jest.fn()
|
||||
|
||||
brain.talk('Hello world')
|
||||
expect(brain.tts.add).toHaveBeenCalledTimes(1)
|
||||
@ -31,13 +34,13 @@ describe('brain', () => {
|
||||
|
||||
describe('wernicke()', () => {
|
||||
test('picks specific string according to object properties', () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
|
||||
expect(brain.wernicke('errors', 'not_found', { })).toBe('Sorry, it seems I cannot find that')
|
||||
})
|
||||
|
||||
test('picks random string from an array', () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
|
||||
expect(global.enExpressions.answers.random_errors).toIncludeAnyMembers([brain.wernicke('random_errors', '', { })])
|
||||
})
|
||||
@ -45,7 +48,8 @@ describe('brain', () => {
|
||||
|
||||
describe('execute()', () => {
|
||||
test('asks to repeat', async () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
brain.socket.emit = jest.fn()
|
||||
brain.talk = jest.fn()
|
||||
|
||||
await brain.execute({ classification: { confidence: 0.1 } })
|
||||
@ -54,8 +58,9 @@ describe('brain', () => {
|
||||
.toIncludeAnyMembers([string[0].substr(0, (string[0].length - 1))])
|
||||
})
|
||||
|
||||
test('creates child process', async () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
test('spawns child process', async () => {
|
||||
const brain = new Brain('en')
|
||||
brain.socket.emit = jest.fn()
|
||||
brain.tts = {
|
||||
synthesizer: jest.fn(),
|
||||
default: jest.fn(),
|
||||
@ -76,11 +81,12 @@ describe('brain', () => {
|
||||
|
||||
await brain.execute(obj)
|
||||
|
||||
expect(brain.process).toEqual({ })
|
||||
// expect(brain.process).toEqual({ })
|
||||
})
|
||||
|
||||
test('executes module', async () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
brain.socket.emit = jest.fn()
|
||||
brain.talk = jest.fn()
|
||||
|
||||
const obj = {
|
||||
@ -107,7 +113,8 @@ describe('brain', () => {
|
||||
})
|
||||
|
||||
test('rejects promise because of spawn failure', async () => {
|
||||
const brain = new Brain({ emit: jest.fn() }, 'en')
|
||||
const brain = new Brain('en')
|
||||
brain.socket.emit = jest.fn()
|
||||
brain.talk = jest.fn()
|
||||
|
||||
const obj = {
|
||||
|
@ -42,19 +42,19 @@ describe('NLU', () => {
|
||||
describe('process()', () => {
|
||||
const nluFallbackTmp = Nlu.fallback
|
||||
|
||||
test('returns false because the NLP model is empty', async () => {
|
||||
test('rejects because the NLP model is empty', async () => {
|
||||
const nlu = new Nlu()
|
||||
nlu.brain = { talk: jest.fn(), wernicke: jest.fn(), socket: { emit: jest.fn() } }
|
||||
|
||||
expect(await nlu.process('Hello')).toBeFalsy()
|
||||
await expect(nlu.process('Hello')).rejects.toEqual('The NLP model is missing, please rebuild the project or if you are in dev run: npm run train')
|
||||
})
|
||||
|
||||
test('returns false because of query not found', async () => {
|
||||
test('resolves with query not found', async () => {
|
||||
const nlu = new Nlu()
|
||||
nlu.brain = { talk: jest.fn(), wernicke: jest.fn(), socket: { emit: jest.fn() } }
|
||||
|
||||
await nlu.loadModel(global.paths.nlp_model)
|
||||
expect(await nlu.process('Unknown query')).toBeFalsy()
|
||||
await expect(nlu.process('Unknown query')).resolves.toHaveProperty('message', 'Query not found')
|
||||
expect(nlu.brain.talk).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@ -70,7 +70,8 @@ describe('NLU', () => {
|
||||
Nlu.fallback = jest.fn(() => fallbackObj)
|
||||
|
||||
await nlu.loadModel(global.paths.nlp_model)
|
||||
expect(await nlu.process(query)).toBeTruthy()
|
||||
|
||||
await expect(nlu.process(query)).resolves.toHaveProperty('processingTime')
|
||||
expect(nlu.brain.execute.mock.calls[0][0]).toBe(fallbackObj)
|
||||
Nlu.fallback = nluFallbackTmp // Need to give back the real fallback method
|
||||
})
|
||||
@ -80,7 +81,7 @@ describe('NLU', () => {
|
||||
nlu.brain = { execute: jest.fn() }
|
||||
|
||||
await nlu.loadModel(global.paths.nlp_model)
|
||||
expect(await nlu.process('Hello')).toBeTruthy()
|
||||
await expect(nlu.process('Hello')).toResolve()
|
||||
expect(nlu.brain.execute).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
@ -1,77 +1,46 @@
|
||||
import net from 'net'
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
import Server from '@/core/http-server/server'
|
||||
import server from '@/core/http-server/server'
|
||||
|
||||
describe('server', () => {
|
||||
describe('constructor()', () => {
|
||||
test('creates a new instance of Server', () => {
|
||||
const server = new Server()
|
||||
|
||||
expect(server).toBeInstanceOf(Server)
|
||||
expect(server.brain).toBeEmpty()
|
||||
})
|
||||
})
|
||||
|
||||
describe('init()', () => {
|
||||
test('uses default language if there is an unsupported one', async () => {
|
||||
const server = new Server()
|
||||
|
||||
server.bootstrap = jest.fn() // Need to mock bootstrap method to not continue the init
|
||||
process.env.LEON_LANG = 'fake-lang'
|
||||
|
||||
await server.init()
|
||||
expect(process.env.LEON_LANG).toBe('en-US')
|
||||
})
|
||||
|
||||
test('initializes server configurations', async () => {
|
||||
await expect(server.init()).resolves.not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('bootstrap()', () => {
|
||||
test('initializes HTTP server', async () => {
|
||||
const server = new Server()
|
||||
|
||||
await server.bootstrap()
|
||||
expect(server.httpServer).not.toBeEmpty()
|
||||
await server.httpServer.close()
|
||||
expect(server.httpServer).not.toBe({ })
|
||||
})
|
||||
})
|
||||
|
||||
describe('listen()', () => {
|
||||
test('listens port already in use', async () => {
|
||||
const fakeServer = net.createServer()
|
||||
|
||||
fakeServer.once('error', (err) => {
|
||||
expect(err.code).toBe('EADDRINUSE')
|
||||
fakeServer.close()
|
||||
})
|
||||
|
||||
const server = new Server()
|
||||
await server.init()
|
||||
|
||||
fakeServer.listen(process.env.LEON_PORT)
|
||||
await server.httpServer.close()
|
||||
})
|
||||
|
||||
test('listens for request', async () => {
|
||||
const server = new Server()
|
||||
console.log = jest.fn()
|
||||
|
||||
await server.listen(process.env.LEON_PORT)
|
||||
expect(console.log.mock.calls[0][1].indexOf(process.env.LEON_PORT)).not.toBe(-1)
|
||||
expect(console.log.mock.calls[1][1].indexOf(`${process.env.LEON_HOST}:${process.env.LEON_PORT}`)).not.toEqual(-1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('connection()', () => {
|
||||
describe('handleOnConnection()', () => {
|
||||
test('initializes main nodes', async () => {
|
||||
const server = new Server()
|
||||
|
||||
await server.init()
|
||||
|
||||
// Mock the WebSocket with an EventEmitter
|
||||
const ee = new EventEmitter()
|
||||
ee.broadcast = { emit: jest.fn() }
|
||||
console.log = jest.fn()
|
||||
|
||||
await server.connection(ee)
|
||||
server.handleOnConnection(ee)
|
||||
|
||||
expect(console.log.mock.calls[0][1]).toBe('CLIENT')
|
||||
console.log = jest.fn()
|
||||
@ -84,9 +53,10 @@ describe('server', () => {
|
||||
console.log = jest.fn()
|
||||
|
||||
ee.emit('init', 'jest')
|
||||
expect(server.brain).not.toBeEmpty()
|
||||
console.debug('test', console.log.mock.calls)
|
||||
/* expect(server.brain).not.toBeEmpty()
|
||||
expect(server.nlu).not.toBeEmpty()
|
||||
expect(server.asr).not.toBeEmpty()
|
||||
expect(server.asr).not.toBeEmpty() */
|
||||
|
||||
/* setTimeout(() => {
|
||||
ee.emit('query', { client: 'jest', value: 'Hello' })
|
||||
|
@ -19,7 +19,7 @@ describe('STT', () => {
|
||||
test('initializes the STT parser', () => {
|
||||
const stt = new Stt({ }, 'coqui-stt')
|
||||
|
||||
expect(stt.init()).toBeTruthy()
|
||||
expect(stt.init(() => null)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -19,7 +19,7 @@ describe('TTS', () => {
|
||||
test('initializes the TTS synthesizer', () => {
|
||||
const tts = new Tts({ }, 'flite')
|
||||
|
||||
expect(tts.init()).toBeTruthy()
|
||||
expect(tts.init(() => null)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -18,8 +18,8 @@
|
||||
"coverageDirectory": "<rootDir>/test/coverage",
|
||||
"collectCoverageFrom": [
|
||||
"<rootDir>/server/src/**/*.js",
|
||||
"!<rootDir>/server/src/api/**/*.js",
|
||||
"!<rootDir>/server/src/plugins/**/*.js",
|
||||
"!<rootDir>/server/src/core/http-server/api/**/*.js",
|
||||
"!<rootDir>/server/src/core/http-server/plugins/**/*.js",
|
||||
"!<rootDir>/server/src/stt/google-cloud-stt/**/*.js",
|
||||
"!<rootDir>/server/src/stt/watson-stt/**/*.js",
|
||||
"!<rootDir>/server/src/tts/amazon-polly/**/*.js",
|
||||
|
Loading…
Reference in New Issue
Block a user