Skip to content

Commit

Permalink
chore: add test to check receiving fragmented message
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Sep 28, 2023
1 parent 4b1bc55 commit b72f965
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 19 deletions.
112 changes: 112 additions & 0 deletions src/__mocks__/net.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { EventEmitter } from 'events'
const sockets: Array<Socket> = []
const onNextSocket: Array<(socket: Socket) => void> = []

const orgSetImmediate = setImmediate

export class Socket extends EventEmitter {
public onWrite?: (buff: Buffer, encoding: string) => void
public onConnect?: (port: number, host: string) => void
public onClose?: () => void

// private _port: number
// private _host: string
private _connected = false

public destroyed = false

constructor() {
super()

const cb = onNextSocket.shift()
if (cb) {
cb(this)
}

sockets.push(this)
}

public static mockSockets(): Socket[] {
return sockets
}
public static openSockets(): Socket[] {
return sockets.filter((s) => !s.destroyed)
}
public static mockOnNextSocket(cb: (s: Socket) => void): void {
onNextSocket.push(cb)
}
public static clearMockOnNextSocket(): void {
onNextSocket.splice(0, 99999)
}
// this.emit('connect')
// this.emit('close')
// this.emit('end')

public connect(port: number, host = 'localhost', cb?: () => void): void {
// this._port = port
// this._host = host

if (this.onConnect) this.onConnect(port, host)
orgSetImmediate(() => {
if (cb) {
cb()
}
this.setConnected()
})
}
public write(buf: Buffer, cb?: () => void): void
public write(buf: Buffer, encoding?: BufferEncoding, cb?: () => void): void
public write(buf: Buffer, encodingOrCb?: BufferEncoding | (() => void), cb?: () => void): void {
const DEFAULT_ENCODING = 'utf-8'
cb = typeof encodingOrCb === 'function' ? encodingOrCb : cb
const encoding = typeof encodingOrCb === 'function' ? DEFAULT_ENCODING : encodingOrCb
if (this.onWrite) {
this.onWrite(buf, encoding ?? DEFAULT_ENCODING)
}
if (cb) cb()
}
public end(): void {
this.setEnd()
this.setClosed()
}

public mockClose(): void {
this.setClosed()
}
public mockData(data: Buffer): void {
this.emit('data', data)
}

public setNoDelay(_noDelay?: boolean): void {
// noop
}

public setEncoding(_encoding?: BufferEncoding): void {
// noop
}

public destroy(): void {
this.destroyed = true
}

private setConnected() {
if (this._connected !== true) {
this._connected = true
}
this.emit('connect')
}
private setClosed() {
if (this._connected !== false) {
this._connected = false
}
this.destroyed = true
this.emit('close')
if (this.onClose) this.onClose()
}
private setEnd() {
if (this._connected !== false) {
this._connected = false
}
this.emit('end')
}
}
201 changes: 182 additions & 19 deletions src/__tests__/connection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,195 @@ import { Version } from '../enums'
import { Connection } from '../connection'
import { serializersV21, serializers } from '../serializers'
import { deserializers } from '../deserializers'
import { Socket as OrgSocket } from 'net'
import { Socket as MockSocket } from '../__mocks__/net'
import { Commands } from '../commands'

jest.mock('net')

const SocketMock = OrgSocket as any as typeof MockSocket

describe('connection', () => {
function setupConnectionClass(v = Version.v23x) {
const conn = new Connection('127.0.0.1', 5250, false)
conn.version = v
describe('version handing', () => {
function setupConnectionClass(v = Version.v23x) {
const conn = new Connection('127.0.0.1', 5250, false)
conn.version = v

return conn
}
it('should use 2.1 serializers for 2.1 connection', () => {
const conn = setupConnectionClass(Version.v21x)
return conn
}
it('should use 2.1 serializers for 2.1 connection', () => {
const conn = setupConnectionClass(Version.v21x)

expect(conn['_getVersionedSerializers']()).toBe(serializersV21)
})
it('should use 2.3 serializers for 2.3 connection', () => {
const conn = setupConnectionClass()
expect(conn['_getVersionedSerializers']()).toBe(serializersV21)
})
it('should use 2.3 serializers for 2.3 connection', () => {
const conn = setupConnectionClass()

expect(conn['_getVersionedSerializers']()).toBe(serializers)
})
it('should use 2.1 deserializers for 2.1 connection', () => {
const conn = setupConnectionClass(Version.v21x)
expect(conn['_getVersionedSerializers']()).toBe(serializers)
})
it('should use 2.1 deserializers for 2.1 connection', () => {
const conn = setupConnectionClass(Version.v21x)

expect(conn['_getVersionedDeserializers']()).toBe(deserializers)
expect(conn['_getVersionedDeserializers']()).toBe(deserializers)
})
it('should use 2.3 deserializers for 2.3 connection', () => {
const conn = setupConnectionClass()

expect(conn['_getVersionedDeserializers']()).toBe(deserializers)
})
})
it('should use 2.3 deserializers for 2.3 connection', () => {
const conn = setupConnectionClass()

expect(conn['_getVersionedDeserializers']()).toBe(deserializers)
describe('receiving', () => {
const onSocketCreate = jest.fn()
const onConnection = jest.fn()
const onSocketClose = jest.fn()
const onSocketWrite = jest.fn()
const onConnectionChanged = jest.fn()

function setupSocketMock() {
SocketMock.mockOnNextSocket((socket: any) => {
onSocketCreate()

socket.onConnect = onConnection
socket.onWrite = onSocketWrite
socket.onClose = onSocketClose
})
}
beforeEach(() => {
setupSocketMock()
})
afterEach(() => {
const sockets = SocketMock.openSockets()
// Destroy any lingering sockets, to prevent a failing test from affecting other tests:
sockets.forEach((s) => s.destroy())

SocketMock.clearMockOnNextSocket()
onSocketCreate.mockClear()
onConnection.mockClear()
onSocketClose.mockClear()
onSocketWrite.mockClear()
onConnectionChanged.mockClear()

// Just a check to ensure that the unit tests cleaned up the socket after themselves:
// eslint-disable-next-line jest/no-standalone-expect
expect(sockets).toHaveLength(0)
})

it('receive whole response', async () => {
const conn = new Connection('127.0.0.1', 5250, true)
try {
expect(conn).toBeTruthy()

const onConnError = jest.fn()
const onConnData = jest.fn()
conn.on('error', onConnError)
conn.on('data', onConnData)

const sockets = SocketMock.openSockets()
expect(sockets).toHaveLength(1)

// Dispatch a command
const sendError = await conn.sendCommand({
command: Commands.Info,
params: {},
})
expect(sendError).toBeFalsy()
expect(onConnError).toHaveBeenCalledTimes(0)
expect(onConnData).toHaveBeenCalledTimes(0)

// Info was sent
expect(onSocketWrite).toHaveBeenCalledTimes(1)
expect(onSocketWrite).toHaveBeenLastCalledWith('INFO\r\n', 'utf-8')

// Reply with a single blob
sockets[0].mockData(
Buffer.from(
`201 INFO OK\r\n<?xml version="1.0" encoding="utf-8"?>\n<channel><test/></channel>\r\n\r\n`
)
)

// Wait for deserializer to run
await new Promise(process.nextTick.bind(process))

expect(onConnError).toHaveBeenCalledTimes(0)
expect(onConnData).toHaveBeenCalledTimes(1)

// Check result looks good
expect(onConnData).toHaveBeenLastCalledWith({
command: 'INFO',
data: [
{
channel: {
test: [''],
},
},
],
message: 'The command has been executed and data is being returned.',
reqId: undefined,
responseCode: 201,
type: 'OK',
})
} finally {
// Ensure cleaned up
conn.disconnect()
}
})

it('receive fragmented response', async () => {
const conn = new Connection('127.0.0.1', 5250, true)
try {
expect(conn).toBeTruthy()

const onConnError = jest.fn()
const onConnData = jest.fn()
conn.on('error', onConnError)
conn.on('data', onConnData)

const sockets = SocketMock.openSockets()
expect(sockets).toHaveLength(1)

// Dispatch a command
const sendError = await conn.sendCommand({
command: Commands.Info,
params: {},
})
expect(sendError).toBeFalsy()
expect(onConnError).toHaveBeenCalledTimes(0)
expect(onConnData).toHaveBeenCalledTimes(0)

// Info was sent
expect(onSocketWrite).toHaveBeenCalledTimes(1)
expect(onSocketWrite).toHaveBeenLastCalledWith('INFO\r\n', 'utf-8')

// Reply with a fragmented message
sockets[0].mockData(Buffer.from(`201 INFO OK\r\n<?xml version="1.0" encoding="utf-8"?>\n<channel>`))
sockets[0].mockData(Buffer.from(`<test/></channel>\r\n\r\n`))

// Wait for deserializer to run
await new Promise(process.nextTick.bind(process))

expect(onConnError).toHaveBeenCalledTimes(0)
expect(onConnData).toHaveBeenCalledTimes(1)

// Check result looks good
expect(onConnData).toHaveBeenLastCalledWith({
command: 'INFO',
data: [
{
channel: {
test: [''],
},
},
],
message: 'The command has been executed and data is being returned.',
reqId: undefined,
responseCode: 201,
type: 'OK',
})
} finally {
// Ensure cleaned up
conn.disconnect()
}
})
})
})

0 comments on commit b72f965

Please sign in to comment.