Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Setting 2 full path properties fails SOFIE-2628 #34

Merged
merged 4 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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