From 786710ad1d71015b76dc7e01cdc7a286a02c96a4 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 10 Jan 2024 19:37:38 +0100 Subject: [PATCH 1/2] feat: support for receiving Profile 1 and messages. BREAKING CHANGES: For Profile 1: you must now add a callback for mosDevice.onMOSObjects() --- packages/connector/src/MosDevice.ts | 39 +- .../connector/src/__tests__/Profile0.spec.ts | 13 + .../connector/src/__tests__/Profile1.spec.ts | 67 +++- .../connector/src/__tests__/Profile2.spec.ts | 17 + .../__snapshots__/Profile1.spec.ts.snap | 361 ++++++++++++++++++ packages/connector/src/__tests__/api.spec.ts | 5 +- packages/connector/src/api.ts | 20 +- 7 files changed, 503 insertions(+), 19 deletions(-) diff --git a/packages/connector/src/MosDevice.ts b/packages/connector/src/MosDevice.ts index 7da1ab76..ef0c1d70 100644 --- a/packages/connector/src/MosDevice.ts +++ b/packages/connector/src/MosDevice.ts @@ -85,7 +85,8 @@ export class MosDevice implements IMOSDevice { // Callbacks for Profile 1: private _callbackOnRequestMOSOBject?: (objId: string) => Promise - private _callbackOnRequestAllMOSObjects?: () => Promise> + private _callbackOnRequestAllMOSObjects?: () => Promise + private _callbackOnMOSObjects?: (objs: IMOSObject[]) => Promise // Callbacks for Profile 2: private _callbackOnCreateRunningOrder?: (ro: IMOSRunningOrder) => Promise @@ -305,6 +306,15 @@ export class MosDevice implements IMOSDevice { }, this.strict ) + } else if (data.mosObj && typeof this._callbackOnMOSObjects === 'function') { + const obj = MosModel.XMLMosObject.fromXML(data.mosObj, this.strict) + const resp = await this._callbackOnMOSObjects([obj]) + return new MosModel.MOSAck(resp, this.strict) + } else if (data.mosListAll && typeof this._callbackOnMOSObjects === 'function') { + const mosObjs = Array.isArray(data.mosListAll.mosObj) ? data.mosListAll.mosObj : [data.mosListAll.mosObj] + const objs = mosObjs.map((mosObj: any) => MosModel.XMLMosObject.fromXML(mosObj, this.strict)) + const resp = await this._callbackOnMOSObjects(objs) + return new MosModel.MOSAck(resp, this.strict) } // Profile 2: ------------------------------------------------------------------------------------------------- // Translate deprecated messages into the functionally equivalent roElementActions: @@ -857,18 +867,22 @@ export class MosDevice implements IMOSDevice { this.checkProfile('onRequestAllMOSObjects', 'profile1') this._callbackOnRequestAllMOSObjects = cb } + onMOSObjects(cb: (objs: IMOSObject[]) => Promise): void { + this.checkProfile('onMOSObjects', 'profile1') + this._callbackOnMOSObjects = cb + } - async sendRequestAllMOSObjects(): Promise> { - const message = new MosModel.ReqMosObjAll(undefined, this.strict) - const reply = await this.executeCommand(message) - if (reply.mos.roAck) { - throw new Error(MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict).toString()) - } else if (reply.mos.mosListAll) { - const objs: Array = MosModel.XMLMosObjects.fromXML(reply.mos.mosListAll.mosObj, this.strict) - return objs - } else { - throw new Error(`Unknown response: ${safeStringify(reply).slice(0, 200)}`) + async sendRequestAllMOSObjects(pause?: number): Promise { + if (typeof this._callbackOnMOSObjects !== 'function') { + throw new Error('Cannot sent request, because callback onMOSObjects() is required') } + + const message = new MosModel.ReqMosObjAll(pause, this.strict) + const reply = await this.executeCommand(message) + + return MosModel.XMLMosAck.fromXML(reply.mos.mosAck, this.strict) + // Then we'll be sent mosListAll or mosObj messages separately, + // handled in the callback in this.onMOSObjects(cb) } /** @@ -892,7 +906,7 @@ export class MosDevice implements IMOSDevice { return this.sendRequestMOSObject(objId) } /** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ - async getAllMOSObjects(): Promise { + async getAllMOSObjects(): Promise { return this.sendRequestAllMOSObjects() } @@ -1485,6 +1499,7 @@ export class MosDevice implements IMOSDevice { requireProfile(1, 0) requireMOSCallback('1', '_callbackOnRequestMOSOBject', this.onRequestMOSObject) requireMOSCallback('1', '_callbackOnRequestAllMOSObjects', this.onRequestAllMOSObjects) + requireMOSCallback('1', '_callbackOnMOSObjects', this.onMOSObjects) } if (this.supportedProfiles.profile2) { requireProfile(2, 0) diff --git a/packages/connector/src/__tests__/Profile0.spec.ts b/packages/connector/src/__tests__/Profile0.spec.ts index da991d70..5d29376d 100644 --- a/packages/connector/src/__tests__/Profile0.spec.ts +++ b/packages/connector/src/__tests__/Profile0.spec.ts @@ -21,6 +21,7 @@ import { xmlData, xmlApiData } from '../__mocks__/testData' /* eslint-disable @typescript-eslint/no-unused-vars */ // @ts-ignore imports are unused import { Socket } from 'net' +import { IMOSAck, IMOSAckStatus } from '@mos-connection/model' /* eslint-enable @typescript-eslint/no-unused-vars */ beforeAll(() => { @@ -44,6 +45,7 @@ describe('Profile 0', () => { let onRequestMachineInfo: jest.Mock let onRequestMOSObject: jest.Mock let onRequestAllMOSObjects: jest.Mock + let onMOSObjects: jest.Mock beforeAll(async () => { mosConnection = await getMosConnection( @@ -75,6 +77,17 @@ describe('Profile 0', () => { mosDevice.onRequestAllMOSObjects(async (): Promise> => { return onRequestAllMOSObjects() }) + onMOSObjects = jest.fn(async (): Promise => { + return { + ID: mosTypes.mosString128.create(''), + Revision: 1, + Status: IMOSAckStatus.ACK, + Description: mosTypes.mosString128.create(''), + } + }) + mosDevice.onMOSObjects(async (objs: IMOSObject[]): Promise => { + return onMOSObjects(objs) + }) const b = doBeforeAll() socketMockLower = b.socketMockLower socketMockUpper = b.socketMockUpper diff --git a/packages/connector/src/__tests__/Profile1.spec.ts b/packages/connector/src/__tests__/Profile1.spec.ts index f696c4e3..143d44ac 100644 --- a/packages/connector/src/__tests__/Profile1.spec.ts +++ b/packages/connector/src/__tests__/Profile1.spec.ts @@ -24,6 +24,8 @@ import { IMOSObjectStatus, IMOSObjectAirStatus, IMOSObjectPathType, + IMOSAck, + IMOSAckStatus, } from '..' import { SocketMock } from '../__mocks__/socket' import { xmlData, xmlApiData } from '../__mocks__/testData' @@ -56,6 +58,8 @@ describe('Profile 1', () => { let onRequestMachineInfo: jest.Mock let onRequestMOSObject: jest.Mock let onRequestAllMOSObjects: jest.Mock + let onMOSObjects: jest.Mock + let receivedMosObjects: Array = [] beforeAll(async () => { mosConnection = await getMosConnection( @@ -87,6 +91,19 @@ describe('Profile 1', () => { mosDevice.onRequestAllMOSObjects(async (): Promise> => { return onRequestAllMOSObjects() }) + onMOSObjects = jest.fn(async (objs: IMOSObject[]): Promise => { + receivedMosObjects.push(...objs) + + return { + ID: mosTypes.mosString128.create(''), + Revision: 1, + Status: IMOSAckStatus.ACK, + Description: mosTypes.mosString128.create(''), + } + }) + mosDevice.onMOSObjects(async (objs: IMOSObject[]): Promise => { + return onMOSObjects(objs) + }) const b = doBeforeAll() socketMockLower = b.socketMockLower socketMockUpper = b.socketMockUpper @@ -106,6 +123,8 @@ describe('Profile 1', () => { // SocketMock.mockClear() onRequestMOSObject.mockClear() onRequestAllMOSObjects.mockClear() + onMOSObjects.mockClear() + receivedMosObjects.splice(0, 99999) serverSocketMockLower.mockClear() serverSocketMockUpper.mockClear() @@ -256,25 +275,65 @@ describe('Profile 1', () => { Status: 'ACK', }) }) + test('receive mosObj', async () => { + // Fake incoming message on socket: + await fakeIncomingMessage(serverSocketMockLower, xmlData.mosObj) + expect(onMOSObjects).toHaveBeenCalledTimes(1) + expect(onMOSObjects.mock.calls[0][0]).toHaveLength(1) + expect(fixSnapshot(onMOSObjects.mock.calls)).toMatchSnapshot() + + // Check reply to socket server: + await serverSocketMockLower.mockWaitForSentMessages() + expect(serverSocketMockLower.mockSentMessage).toHaveBeenCalledTimes(1) + // @ts-ignore mock + const reply = decode(serverSocketMockLower.mockSentMessage.mock.calls[0][0]) + const parsedReply: any = xml2js(reply, { compact: true, nativeType: true, trim: true }) + expect(parsedReply.mos.mosAck).toBeTruthy() + expect(parsedReply).toMatchSnapshot() + }) + test('receive mosListAll', async () => { + // Fake incoming message on socket: + await fakeIncomingMessage(serverSocketMockLower, xmlData.mosListAll) + expect(onMOSObjects).toHaveBeenCalledTimes(1) + expect(onMOSObjects.mock.calls[0][0]).toHaveLength(2) + expect(fixSnapshot(onMOSObjects.mock.calls)).toMatchSnapshot() + + // Check reply to socket server: + await serverSocketMockLower.mockWaitForSentMessages() + expect(serverSocketMockLower.mockSentMessage).toHaveBeenCalledTimes(1) + // @ts-ignore mock + const reply = decode(serverSocketMockLower.mockSentMessage.mock.calls[0][0]) + const parsedReply: any = xml2js(reply, { compact: true, nativeType: true, trim: true }) + expect(parsedReply.mos.mosAck).toBeTruthy() + expect(parsedReply).toMatchSnapshot() + }) test('getAllMOSObjects', async () => { expect(socketMockLower).toBeTruthy() + + const REPLY_WAIT_TIME = 100 // Prepare mock server response: const mockReply = jest.fn((data) => { const str = decode(data) const messageID = getMessageId(str) - const repl = getXMLReply(messageID, xmlData.mosListAll) + const repl = getXMLReply(messageID, xmlData.mosAck) + + setTimeout(() => { + fakeIncomingMessage(serverSocketMockLower, xmlData.mosListAll).catch(console.error) + }, REPLY_WAIT_TIME) return encode(repl) }) socketMockLower.mockAddReply(mockReply) - const returnedObjs: Array = await mosDevice.sendRequestAllMOSObjects() + await mosDevice.sendRequestAllMOSObjects() + + await delay(REPLY_WAIT_TIME + 100) expect(mockReply).toHaveBeenCalledTimes(1) const msg = decode(mockReply.mock.calls[0][0]) expect(msg).toMatch(//) checkMessageSnapshot(msg) - expect(returnedObjs).toMatchObject(xmlApiData.mosListAll) - expect(returnedObjs).toMatchSnapshot() + expect(receivedMosObjects).toMatchObject(xmlApiData.mosListAll) + expect(receivedMosObjects).toMatchSnapshot() }) test('command timeout', async () => { diff --git a/packages/connector/src/__tests__/Profile2.spec.ts b/packages/connector/src/__tests__/Profile2.spec.ts index df695bc1..3fb9c8d5 100644 --- a/packages/connector/src/__tests__/Profile2.spec.ts +++ b/packages/connector/src/__tests__/Profile2.spec.ts @@ -45,6 +45,7 @@ import { xml2js } from 'xml-js' /* eslint-disable @typescript-eslint/no-unused-vars */ // @ts-ignore imports are unused import { Socket } from 'net' +import { IMOSAck, IMOSAckStatus } from '@mos-connection/model' /* eslint-enable @typescript-eslint/no-unused-vars */ beforeAll(() => { @@ -66,6 +67,8 @@ describe('Profile 2', () => { let onRequestMachineInfo: jest.Mock let onRequestMOSObject: jest.Mock let onRequestAllMOSObjects: jest.Mock + let onMOSObjects: jest.Mock + let onCreateRunningOrder: jest.Mock let onReplaceRunningOrder: jest.Mock let onDeleteRunningOrder: jest.Mock @@ -122,6 +125,20 @@ describe('Profile 2', () => { mosDevice.onRequestAllMOSObjects(async (): Promise> => { return onRequestAllMOSObjects() }) + mosDevice.onRequestAllMOSObjects(async (): Promise> => { + return onRequestAllMOSObjects() + }) + onMOSObjects = jest.fn(async (): Promise => { + return { + ID: mosTypes.mosString128.create(''), + Revision: 1, + Status: IMOSAckStatus.ACK, + Description: mosTypes.mosString128.create(''), + } + }) + mosDevice.onMOSObjects(async (objs: IMOSObject[]): Promise => { + return onMOSObjects(objs) + }) // Profile 2: const roAckReply = async () => { diff --git a/packages/connector/src/__tests__/__snapshots__/Profile1.spec.ts.snap b/packages/connector/src/__tests__/__snapshots__/Profile1.spec.ts.snap index 03d6d1dc..8769daf4 100644 --- a/packages/connector/src/__tests__/__snapshots__/Profile1.spec.ts.snap +++ b/packages/connector/src/__tests__/__snapshots__/Profile1.spec.ts.snap @@ -542,6 +542,367 @@ exports[`Profile 1 onRequestMOSObject 2`] = ` } `; +exports[`Profile 1 receive mosListAll 1`] = ` +[ + [ + [ + { + "AirStatus": undefined, + "Changed": { + "_mosTime": 1257086155000, + "_timezone": "Z", + "_timezoneOffset": 0, + }, + "ChangedBy": { + "_mosString128": "Chris", + }, + "Created": { + "_mosTime": 1257032352000, + "_timezone": "Z", + "_timezoneOffset": 0, + }, + "CreatedBy": { + "_mosString128": "Chris", + }, + "Description": { + "elements": [ + { + "$name": "p", + "$type": "element", + "elements": [ + { + "$type": "text", + "text": "Exterior footage of", + }, + { + "$name": "em", + "$type": "text", + "text": "Baley Park Hotel", + }, + { + "$type": "text", + "text": "on fire with natural sound. Trucks are visible for the first portion of the clip.", + }, + { + "$name": "em", + "$type": "text", + "text": "CG locator at 0:04 and duration 0:05, Baley Park Hotel.", + }, + ], + }, + { + "$name": "p", + "$type": "element", + "elements": [ + { + "$name": "tab", + "$type": "element", + }, + { + "$type": "text", + "text": "Cuts to view of fire personnel exiting hotel lobby and cleaning up after the fire is out.", + }, + ], + }, + { + "$name": "p", + "$type": "element", + "em": "Clip has been doubled for pad on voice over.", + }, + ], + }, + "Duration": undefined, + "Group": undefined, + "ID": { + "_mosString128": "M000123", + }, + "MosAbstract": undefined, + "Paths": [ + { + "Description": "MPEG2 Video", + "Target": "\\server\\media\\clip392028cd2320s0d.mxf", + "Type": "PATH", + }, + { + "Description": "WM9 750Kbps", + "Target": "https://server/proxy/clipe.wmv", + "Type": "PROXY PATH", + }, + { + "Description": "MOS Object", + "Target": "https://server/proxy/clipe.xml", + "Type": "METADATA PATH", + }, + ], + "Revision": undefined, + "Slug": { + "_mosString128": "HOTEL FIRE", + }, + "Status": undefined, + "TimeBase": undefined, + "Type": undefined, + }, + { + "AirStatus": "READY", + "Changed": { + "_mosTime": 1257088875000, + "_timezone": "Z", + "_timezoneOffset": 0, + }, + "ChangedBy": { + "_mosString128": "Chris", + }, + "Created": { + "_mosTime": 1257088741000, + "_timezone": "Z", + "_timezoneOffset": 0, + }, + "CreatedBy": { + "_mosString128": "Phil", + }, + "Description": "VOICE OVER MATERIAL OF COLSTAT MURDER SITES SHOT ON 1-NOV.", + "Duration": 800, + "Group": undefined, + "ID": { + "_mosString128": "M000224", + }, + "MosAbstract": undefined, + "MosExternalMetaData": [ + { + "MosPayload": { + "Approved": 0, + "Creator": "SHOLMES", + "ModBy": "LJOHNSTON", + "ModTime": 20010308142001, + "Owner": "SHOLMES", + "TextTime": 278, + "mediaTime": 0, + "text": 0, + }, + "MosSchema": "https://MOSA4.com/mos/supported_schemas/MOSAXML2.08", + "MosScope": "STORY", + }, + ], + "Paths": [ + { + "Description": "MPEG2 Video", + "Target": "\\server\\media\\clip392028cd2320s0d.mxf", + "Type": "PATH", + }, + { + "Description": "WM9 750Kbps", + "Target": "https://server/proxy/clipe.wmv", + "Type": "PROXY PATH", + }, + { + "Description": "MOS Object", + "Target": "https://server/proxy/clipe.xml", + "Type": "METADATA PATH", + }, + ], + "Revision": 4, + "Slug": { + "_mosString128": "COLSTAT MURDER:VO", + }, + "Status": "UPDATED", + "TimeBase": 59.94, + "Type": "VIDEO", + }, + ], + ], +] +`; + +exports[`Profile 1 receive mosListAll 2`] = ` +{ + "mos": { + "messageID": { + "_text": 1636, + }, + "mosAck": { + "objID": {}, + "objRev": { + "_text": 1, + }, + "status": { + "_text": "ACK", + }, + "statusDescription": {}, + }, + "mosID": { + "_text": "our.mos.id", + }, + "ncsID": { + "_text": "their.mos.id", + }, + }, +} +`; + +exports[`Profile 1 receive mosObj 1`] = ` +[ + [ + [ + { + "AirStatus": "READY", + "Changed": { + "_mosTime": 1257032352000, + "_timezone": "Z", + "_timezoneOffset": 0, + }, + "ChangedBy": { + "_mosString128": "Chris", + }, + "Created": { + "_mosTime": 1257032352000, + "_timezone": "Z", + "_timezoneOffset": 0, + }, + "CreatedBy": { + "_mosString128": "Chris", + }, + "Description": { + "elements": [ + { + "$name": "p", + "$type": "element", + "elements": [ + { + "$type": "text", + "text": "Exterior footage of", + }, + { + "$name": "em", + "$type": "text", + "text": "Baley Park Hotel", + }, + { + "$type": "text", + "text": "on fire with natural sound. Trucks are visible for the first portion of the clip.", + }, + { + "$name": "em", + "$type": "text", + "text": "CG locator at 0:04 and duration 0:05, Baley Park Hotel.", + }, + ], + }, + { + "$name": "p", + "$type": "element", + "elements": [ + { + "$name": "tab", + "$type": "element", + }, + { + "$type": "text", + "text": "Cuts to view of fire personnel exiting hotel lobby and cleaning up after the fire is out.", + }, + ], + }, + { + "$name": "p", + "$type": "element", + "em": "Clip has been doubled for pad on voice over.", + }, + ], + }, + "Duration": 1800, + "Group": "Show 7", + "ID": { + "_mosString128": "M000123", + }, + "MosAbstract": { + "elements": [ + { + "$name": "b", + "$type": "text", + "text": "Hotel Fire", + }, + { + "$name": "em", + "$type": "text", + "text": "vo", + }, + { + "$type": "text", + "text": ":30", + }, + ], + }, + "MosExternalMetaData": [ + { + "MosPayload": { + "Approved": 0, + "Creator": "SHOLMES", + "ModBy": "LJOHNSTON", + "ModTime": 20010308142001, + "Owner": "SHOLMES", + "TextTime": 278, + "mediaTime": 0, + "text": 0, + }, + "MosSchema": "https://MOSA4.com/mos/supported_schemas/MOSAXML2.08", + "MosScope": "STORY", + }, + ], + "Paths": [ + { + "Description": "MPEG2 Video", + "Target": "\\server\\media\\clip392028cd2320s0d.mxf", + "Type": "PATH", + }, + { + "Description": "WM9 750Kbps", + "Target": "https://server/proxy/clipe.wmv", + "Type": "PROXY PATH", + }, + { + "Description": "MOS Object", + "Target": "https://server/proxy/clipe.xml", + "Type": "METADATA PATH", + }, + ], + "Revision": 1, + "Slug": { + "_mosString128": "Hotel Fire", + }, + "Status": "NEW", + "TimeBase": 59.94, + "Type": "VIDEO", + }, + ], + ], +] +`; + +exports[`Profile 1 receive mosObj 2`] = ` +{ + "mos": { + "messageID": { + "_text": 1635, + }, + "mosAck": { + "objID": {}, + "objRev": { + "_text": 1, + }, + "status": { + "_text": "ACK", + }, + "statusDescription": {}, + }, + "mosID": { + "_text": "our.mos.id", + }, + "ncsID": { + "_text": "their.mos.id", + }, + }, +} +`; + exports[`Profile 1 sendMOSObject 1`] = ` " their.mos.id diff --git a/packages/connector/src/__tests__/api.spec.ts b/packages/connector/src/__tests__/api.spec.ts index 0ddff730..5d369ff6 100644 --- a/packages/connector/src/__tests__/api.spec.ts +++ b/packages/connector/src/__tests__/api.spec.ts @@ -117,7 +117,10 @@ test('api & exports', () => { f = function (cb: () => Promise>): void { return mosDevice.onRequestAllMOSObjects(cb) } - f = function (): Promise> { + f = function (cb: (objs: IMOSObject[]) => Promise): void { + return mosDevice.onMOSObjects(cb) + } + f = function (): Promise { return mosDevice.sendRequestAllMOSObjects() } diff --git a/packages/connector/src/api.ts b/packages/connector/src/api.ts index 7bf936eb..8a01a9f4 100644 --- a/packages/connector/src/api.ts +++ b/packages/connector/src/api.ts @@ -110,6 +110,14 @@ export interface IMOSDeviceProfile1 { */ sendRequestMOSObject: (objId: IMOSString128) => Promise + /** + * Assign callback (as a MOS device) for when receiving message from NCS: + * Contains information that describes a unique MOS Object to the NCS. + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosObj + * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosListAll + */ + onMOSObjects: (cb: (objs: IMOSObject[]) => Promise) => void + /** * Assign callback (as a MOS device) for when receiving message from NCS: * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. @@ -118,15 +126,23 @@ export interface IMOSDeviceProfile1 { onRequestAllMOSObjects: (cb: () => Promise>) => void /** * Method for the NCS to request the MOS to send it a mosObj message for every Object in the MOS. + * The replies will be sent to the callback set up in onMOSObjects() * https://mosprotocol.com/wp-content/MOS-Protocol-Documents/MOS-Protocol-2.8.4-Current.htm#mosReqAll */ - sendRequestAllMOSObjects: () => Promise> + sendRequestAllMOSObjects: ( + /** + * Pause, when greater than zero, indicates the number of seconds to pause between individual mosObj messages. + * Pause of zero indicates that all objects will be sent using the mosListAll message. + * @default 0 + */ + pause?: number + ) => Promise // Deprecated methods: /** @deprecated getMOSObject is deprecated, use sendRequestMOSObject instead */ getMOSObject: (objId: IMOSString128) => Promise /** @deprecated getAllMOSObjects is deprecated, use sendRequestAllMOSObjects instead */ - getAllMOSObjects: () => Promise + getAllMOSObjects: () => Promise } /** * Method definitions for Profile 2 From 34fba90bcce7e226f0e8f52183c769eade8dbf6a Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Fri, 2 Feb 2024 07:21:19 +0100 Subject: [PATCH 2/2] chore: fix test --- packages/connector/src/__tests__/Profile0.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/connector/src/__tests__/Profile0.spec.ts b/packages/connector/src/__tests__/Profile0.spec.ts index 989f330c..72d0f003 100644 --- a/packages/connector/src/__tests__/Profile0.spec.ts +++ b/packages/connector/src/__tests__/Profile0.spec.ts @@ -11,6 +11,7 @@ import { getMosConnection, getMosDevice, getXMLReply, + mosTypes, setupMocks, } from './lib' import { MosConnection, MosDevice, IMOSObject, IMOSListMachInfo } from '..'