Skip to content

Commit

Permalink
fix: Setting 2 full path properties fails SOFIE-2628 (#34)
Browse files Browse the repository at this point in the history
Co-authored-by: Mint de Wit <araminta.hekate@gmail.com>
  • Loading branch information
Julusian and mint-dewit authored Oct 16, 2023
1 parent 7682c4e commit 3d756ae
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 53 deletions.
249 changes: 249 additions & 0 deletions src/Ember/Client/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import {
NumberedTreeNode,
EmberElement,
NumberedTreeNodeImpl,
EmberNodeImpl,
ParameterImpl,
ParameterType,
QualifiedElementImpl,
} from '../../../model'
import { Collection, Root, RootElement } from '../../../types/types'
import { EmberClient } from '../'
import S101ClientMock from '../../../__mocks__/S101Client'
import { DecodeResult } from '../../../encodings/ber/decoder/DecodeResult'
// import { EmberTreeNode, RootElement } from '../../../types/types'
// import { ElementType, EmberElement } from '../../../model/EmberElement'
// import { Parameter, ParameterType } from '../../../model/Parameter'

jest.mock('../../Socket/S101Client', () => require('../../../__mocks__/S101Client'))

describe('client', () => {
const onSocketCreate = jest.fn()
const onConnection = jest.fn()
const onSocketClose = jest.fn()
const onSocketWrite = jest.fn()
const onConnectionChanged = jest.fn()

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

socket.onConnect = onConnection
socket.onWrite = onSocketWrite
socket.onClose = onSocketClose
})
}

beforeEach(() => {
setupSocketMock()
})
afterEach(() => {
const sockets = S101ClientMock.openSockets()
// Destroy any lingering sockets, to prevent a failing test from affecting other tests:
sockets.forEach((s) => s.destroy())

S101ClientMock.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)
})

async function runWithConnection(fn: (connection: EmberClient, socket: S101ClientMock) => Promise<void>) {
const client = new EmberClient('test')
try {
expect(client).toBeTruthy()

await client.connect()

// Wait for connection
await new Promise(setImmediate)

// Should be connected
expect(client.connected).toBeTruthy()

const sockets = S101ClientMock.openSockets()
expect(sockets).toHaveLength(1)
expect(onSocketWrite).toHaveBeenCalledTimes(0)

await fn(client, sockets[0])
} finally {
// Ensure cleaned up
await client.disconnect()
client.discard()

await new Promise(setImmediate)
}
}

function createQualifiedNodeResponse(
path: string,
content: EmberElement,
children: Collection<NumberedTreeNode<EmberElement>>
): DecodeResult<Root> {
const parent = new QualifiedElementImpl<EmberElement>(path, content, children)

const fixLevel = (node: NumberedTreeNode<EmberElement>, parent: NumberedTreeNode<EmberElement>) => {
node.parent = parent

for (const child of Object.values<NumberedTreeNode<EmberElement>>(node.children ?? {})) {
fixLevel(child, node)
}
}
for (const child of Object.values<NumberedTreeNode<EmberElement>>(children)) {
fixLevel(child, parent as any as NumberedTreeNode<EmberElement>)
}
return {
value: {
0: parent as Exclude<RootElement, NumberedTreeNode<EmberElement>>,
},
}
}

it('getDirectory resolves', async () => {
await runWithConnection(async (client, socket) => {
// Do initial load
const getRootDirReq = await client.getDirectory(client.tree)
getRootDirReq.response?.catch(() => null) // Ensure uncaught response is ok
expect(onSocketWrite).toHaveBeenCalledTimes(1)
// TODO: should the value of the call be checked?

// Mock a valid response
socket.mockData({
value: {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Ruby', undefined, undefined, true)),
},
})

// Should have a response
const res = (await getRootDirReq.response) as NumberedTreeNodeImpl<EmberElement>
expect(res).toMatchObject(new NumberedTreeNodeImpl(1, new EmberNodeImpl('Ruby', undefined, undefined, true)))
})
})

it('getElementByPath', async () => {
await runWithConnection(async (client, socket) => {
// Do initial load
const getRootDirReq = await client.getDirectory(client.tree)
getRootDirReq.response?.catch(() => null) // Ensure uncaught response is ok
expect(onSocketWrite).toHaveBeenCalledTimes(1)
onSocketWrite.mockClear()

// Mock a valid response
socket.mockData({
value: {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Ruby', undefined, undefined, true)),
},
})
await getRootDirReq.response

// Run the tree
const getByPathPromise = client.getElementByPath('Ruby.Sums.On')

// First lookup
expect(onSocketWrite).toHaveBeenCalledTimes(1)
socket.mockData({
value: {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Ruby', undefined, undefined, true), {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Sums', undefined, undefined, true)),
}),
},
})

await new Promise(setImmediate)

// Second lookup
expect(onSocketWrite).toHaveBeenCalledTimes(2)
socket.mockData({
value: {
1: new QualifiedElementImpl<EmberElement>('1.1', new EmberNodeImpl('Sums', undefined, undefined, false), {
1: new NumberedTreeNodeImpl(1, new ParameterImpl(ParameterType.Boolean, 'On', undefined, false)),
}) as Exclude<RootElement, NumberedTreeNode<EmberElement>>,
},
})

await new Promise(setImmediate)

const res = await getByPathPromise
expect(res).toBeTruthy()
expect(res).toMatchObject(
new NumberedTreeNodeImpl(1, new ParameterImpl(ParameterType.Boolean, 'On', undefined, false))
)
})
})

it('getElementByPath concurrent', async () => {
await runWithConnection(async (client, socket) => {
// Do initial load
const getRootDirReq = await client.getDirectory(client.tree)
getRootDirReq.response?.catch(() => null) // Ensure uncaught response is ok
expect(onSocketWrite).toHaveBeenCalledTimes(1)
onSocketWrite.mockClear()

// Mock a valid response
socket.mockData({
value: {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Ruby', undefined, undefined, true)),
},
})
await getRootDirReq.response

// Run the tree
const getByPathPromise = client.getElementByPath('Ruby.Sums.MAIN.On')
const getByPathPromise2 = client.getElementByPath('Ruby.Sums.MAIN.Second')

// First lookup from both
expect(onSocketWrite).toHaveBeenCalledTimes(2)
socket.mockData(
createQualifiedNodeResponse('1', new EmberNodeImpl('Ruby', undefined, undefined, true), {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Sums', undefined, undefined, false)),
})
)

socket.mockData(
createQualifiedNodeResponse('1', new EmberNodeImpl('Ruby', undefined, undefined, true), {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('Sums', undefined, undefined, false)),
})
)

await new Promise(setImmediate)

// Second lookup
expect(onSocketWrite).toHaveBeenCalledTimes(4)
socket.mockData(
createQualifiedNodeResponse('1.1', new EmberNodeImpl('Sums', undefined, undefined, false), {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('MAIN', undefined, undefined, false)),
})
)
await new Promise(setImmediate)
socket.mockData(
createQualifiedNodeResponse('1.1', new EmberNodeImpl('Sums', undefined, undefined, false), {
1: new NumberedTreeNodeImpl(1, new EmberNodeImpl('MAIN', undefined, undefined, false)),
})
)

await new Promise(setImmediate)

// Final lookup
expect(onSocketWrite).toHaveBeenCalledTimes(6)
socket.mockData(
createQualifiedNodeResponse('1.1.1', new EmberNodeImpl('MAIN', undefined, undefined, false), {
1: new NumberedTreeNodeImpl(1, new ParameterImpl(ParameterType.Boolean, 'On', undefined, false)),
2: new NumberedTreeNodeImpl(1, new ParameterImpl(ParameterType.Boolean, 'Second', undefined, false)),
})
)

// Both completed successfully
const res = await getByPathPromise
expect(res).toBeTruthy()

const res2 = await getByPathPromise2
expect(res2).toBeTruthy()
})
})
})
Loading

0 comments on commit 3d756ae

Please sign in to comment.