From 278eef6b7e02683f0c387cc40d3cf27b431ff954 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 27 Dec 2023 15:24:09 +0100 Subject: [PATCH 01/10] chore: lint fixes --- .../src/__tests__/MessageChunking.spec.ts | 14 +++++++------- .../helper/src/mosModel/__tests__/profile2.spec.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/connector/src/__tests__/MessageChunking.spec.ts b/packages/connector/src/__tests__/MessageChunking.spec.ts index 9a623ff0..fec4af58 100644 --- a/packages/connector/src/__tests__/MessageChunking.spec.ts +++ b/packages/connector/src/__tests__/MessageChunking.spec.ts @@ -247,11 +247,11 @@ describe('message chunking', () => { const chunks = [message.message.slice(0, 100), message.message.slice(100)] // Send first part of the message: - await sendFakeIncomingMessage(serverSocketMockLower, chunks[0]) + sendFakeIncomingMessage(serverSocketMockLower, chunks[0]) expect(onRequestMOSObject).toHaveBeenCalledTimes(0) // Send rest of the message: - await sendFakeIncomingMessage(serverSocketMockLower, chunks[1]) + sendFakeIncomingMessage(serverSocketMockLower, chunks[1]) expect(onRequestMOSObject).toHaveBeenCalledTimes(1) }) @@ -281,13 +281,13 @@ describe('message chunking', () => { ] // Send the parts of the message: - await sendFakeIncomingMessage(serverSocketMockLower, chunks[0]) + sendFakeIncomingMessage(serverSocketMockLower, chunks[0]) expect(onRequestMOSObject).toHaveBeenCalledTimes(0) - await sendFakeIncomingMessage(serverSocketMockLower, chunks[1]) + sendFakeIncomingMessage(serverSocketMockLower, chunks[1]) expect(onRequestMOSObject).toHaveBeenCalledTimes(0) - await sendFakeIncomingMessage(serverSocketMockLower, chunks[2]) + sendFakeIncomingMessage(serverSocketMockLower, chunks[2]) expect(onRequestMOSObject).toHaveBeenCalledTimes(0) - await sendFakeIncomingMessage(serverSocketMockLower, chunks[2]) + sendFakeIncomingMessage(serverSocketMockLower, chunks[2]) expect(onRequestMOSObject).toHaveBeenCalledTimes(1) }) test('multiple messages', async () => { @@ -308,7 +308,7 @@ describe('message chunking', () => { ) // Send both messages right away: - await sendFakeIncomingMessage(serverSocketMockLower, message0.message + message1.message) + sendFakeIncomingMessage(serverSocketMockLower, message0.message + message1.message) expect(onRequestMOSObject).toHaveBeenCalledTimes(2) }) }) diff --git a/packages/helper/src/mosModel/__tests__/profile2.spec.ts b/packages/helper/src/mosModel/__tests__/profile2.spec.ts index 586bb028..965668c5 100644 --- a/packages/helper/src/mosModel/__tests__/profile2.spec.ts +++ b/packages/helper/src/mosModel/__tests__/profile2.spec.ts @@ -37,7 +37,7 @@ describe('Profile 2', () => { { Type: IMOSObjectPathType.PATH, Description: 'MPEG2 Video', - Target: '\\servermediaclip392028cd2320s0d.mxf', + Target: '\\server\\media\\clip392028cd2320s0d.mxf', }, { Type: IMOSObjectPathType.PROXY_PATH, @@ -59,7 +59,7 @@ describe('Profile 2', () => { 715 415 -\\server\media\clip392028cd2320s0d.mxf +\\server\\media\\clip392028cd2320s0d.mxf https://server/proxy/clipe.wmv https://server/proxy/clipe.xml From 605c8328482a906d288b36e28088730b0acdee20 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 19 Dec 2023 17:24:52 +0100 Subject: [PATCH 02/10] chore: nicer formatted parsing errors --- packages/connector/src/MosConnection.ts | 8 +- packages/connector/src/MosDevice.ts | 116 +++++++++++++----------- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/packages/connector/src/MosConnection.ts b/packages/connector/src/MosConnection.ts index dc7c2b38..3271d064 100644 --- a/packages/connector/src/MosConnection.ts +++ b/packages/connector/src/MosConnection.ts @@ -93,8 +93,8 @@ export class MosConnection extends EventEmitter implements IMosConnection { primary.on('warning', (str: string) => { this.emit('warning', 'primary: ' + str) }) - primary.on('error', (str: string) => { - this.emit('error', 'primary: ' + str) + primary.on('error', (error: Error) => { + this.emit('error', 'primary: ' + error + ' ' + (typeof error === 'object' ? error.stack : '')) }) primary.on('info', (str: string) => { this.emit('info', 'primary: ' + str) @@ -137,8 +137,8 @@ export class MosConnection extends EventEmitter implements IMosConnection { secondary.on('warning', (str: string) => { this.emit('warning', 'secondary: ' + str) }) - secondary.on('error', (str: string) => { - this.emit('error', 'secondary: ' + str) + secondary.on('error', (error: Error) => { + this.emit('error', 'secondary: ' + error + ' ' + (typeof error === 'object' ? error.stack : '')) }) secondary.on('info', (str: string) => { this.emit('info', 'secondary: ' + str) diff --git a/packages/connector/src/MosDevice.ts b/packages/connector/src/MosDevice.ts index 7da1ab76..f0012d60 100644 --- a/packages/connector/src/MosDevice.ts +++ b/packages/connector/src/MosDevice.ts @@ -150,11 +150,11 @@ export class MosDevice implements IMOSDevice { this.model = this.mosTypes.mosString128.create('TCS6000') this.hwRev = this.mosTypes.mosString128.create('0.1') // empty string returnes this.swRev = this.mosTypes.mosString128.create('0.1') - this.DOM = this.mosTypes.mosTime.create(undefined) + this.DOM = this.mosTypes.mosTime.create(Date.now()) this.SN = this.mosTypes.mosString128.create('927748927') this.ID = this.mosTypes.mosString128.create(connectionConfig ? connectionConfig.mosID : '') - this.time = this.mosTypes.mosTime.create(undefined) - this.opTime = this.mosTypes.mosTime.create(undefined) + this.time = this.mosTypes.mosTime.create(Date.now()) + this.opTime = this.mosTypes.mosTime.create(Date.now()) this.mosRev = this.mosTypes.mosString128.create('2.8.5') if (connectionConfig) { @@ -789,7 +789,7 @@ export class MosDevice implements IMOSDevice { const data = await this.executeCommand(message) - return MosModel.XMLMosListMachInfo.fromXML(data.mos.listMachInfo, this.strict) + return this.handleParseReply(() => MosModel.XMLMosListMachInfo.fromXML(data.mos.listMachInfo, this.strict)) } onRequestMachineInfo(cb: () => Promise): void { @@ -830,8 +830,7 @@ export class MosDevice implements IMOSDevice { const reply = await this.executeCommand(message) - const ack: IMOSAck = MosModel.XMLMosAck.fromXML(reply.mos.mosAck, this.strict) - return ack + return this.handleParseReply(() => MosModel.XMLMosAck.fromXML(reply.mos.mosAck, this.strict)) } onRequestMOSObject(cb: (objId: string) => Promise): void { @@ -846,8 +845,7 @@ export class MosDevice implements IMOSDevice { if (reply.mos.roAck) { throw new Error(MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict).toString()) } else if (reply.mos.mosObj) { - const obj: IMOSObject = MosModel.XMLMosObject.fromXML(reply.mos.mosObj, this.strict) - return obj + return this.handleParseReply(() => MosModel.XMLMosObject.fromXML(reply.mos.mosObj, this.strict)) } else { throw new Error(`Unknown response: ${safeStringify(reply).slice(0, 200)}`) } @@ -864,8 +862,7 @@ export class MosDevice implements IMOSDevice { 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 + return this.handleParseReply(() => MosModel.XMLMosObjects.fromXML(reply.mos.mosListAll.mosObj, this.strict)) } else { throw new Error(`Unknown response: ${safeStringify(reply).slice(0, 200)}`) } @@ -906,7 +903,7 @@ export class MosDevice implements IMOSDevice { async sendCreateRunningOrder(ro: IMOSRunningOrder): Promise { const message = new MosModel.ROCreate(ro, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onReplaceRunningOrder(cb: (ro: IMOSRunningOrder) => Promise): void { @@ -916,7 +913,7 @@ export class MosDevice implements IMOSDevice { async sendReplaceRunningOrder(ro: IMOSRunningOrder): Promise { const message = new MosModel.ROReplace(ro, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onDeleteRunningOrder(cb: (runningOrderId: IMOSString128) => Promise): void { @@ -926,7 +923,7 @@ export class MosDevice implements IMOSDevice { async sendDeleteRunningOrder(runningOrderId: IMOSString128): Promise { const message = new MosModel.RODelete(runningOrderId, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onRequestRunningOrder(cb: (runningOrderId: IMOSString128) => Promise): void { @@ -938,8 +935,7 @@ export class MosDevice implements IMOSDevice { const message = new MosModel.ROReq(runningOrderId, this.strict) const reply = await this.executeCommand(message) if (reply.mos.roList) { - const ro: IMOSRunningOrder = MosModel.XMLRunningOrder.fromXML(reply.mos.roList, this.strict) - return ro + return this.handleParseReply(() => MosModel.XMLRunningOrder.fromXML(reply.mos.roList, this.strict)) } else if (reply.mos.roAck) { if (reply.mos.roAck.roStatus) { throw new Error(`Reply from NRCS: ${reply.mos.roAck.roStatus}`) @@ -961,7 +957,7 @@ export class MosDevice implements IMOSDevice { async sendMetadataReplace(metadata: IMOSRunningOrderBase): Promise { const message = new MosModel.ROMetadataReplace(metadata, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onRunningOrderStatus(cb: (status: IMOSRunningOrderStatus) => Promise): void { @@ -1001,7 +997,7 @@ export class MosDevice implements IMOSDevice { this.strict ) const reply = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict)) } async sendStoryStatus(status: IMOSStoryStatus): Promise { @@ -1015,7 +1011,7 @@ export class MosDevice implements IMOSDevice { this.strict ) const reply = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict)) } async sendItemStatus(status: IMOSItemStatus): Promise { const message = new MosModel.ROElementStat( @@ -1031,7 +1027,7 @@ export class MosDevice implements IMOSDevice { this.strict ) const reply = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict)) } onReadyToAir(cb: (Action: IMOSROReadyToAir) => Promise): void { this.checkProfile('onReadyToAir', 'profile2') @@ -1047,7 +1043,7 @@ export class MosDevice implements IMOSDevice { ) const reply = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(reply.mos.roAck, this.strict)) } onROInsertStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void { this.checkProfile('onROInsertStories', 'profile2') @@ -1056,7 +1052,7 @@ export class MosDevice implements IMOSDevice { async sendROInsertStories(Action: IMOSStoryAction, Stories: Array): Promise { const message = new MosModel.ROInsertStories(Action, Stories, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROInsertItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void { this.checkProfile('onROInsertItems', 'profile2') @@ -1065,7 +1061,7 @@ export class MosDevice implements IMOSDevice { async sendROInsertItems(Action: IMOSItemAction, Items: Array): Promise { const message = new MosModel.ROInsertItems(Action, Items, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROReplaceStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void { this.checkProfile('onROReplaceStories', 'profile2') @@ -1074,7 +1070,7 @@ export class MosDevice implements IMOSDevice { async sendROReplaceStories(Action: IMOSStoryAction, Stories: Array): Promise { const message = new MosModel.ROReplaceStories(Action, Stories, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROReplaceItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void { this.checkProfile('onROReplaceItems', 'profile2') @@ -1083,7 +1079,7 @@ export class MosDevice implements IMOSDevice { async sendROReplaceItems(Action: IMOSItemAction, Items: Array): Promise { const message = new MosModel.ROReplaceItems(Action, Items, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROMoveStories(cb: (Action: IMOSStoryAction, Stories: Array) => Promise): void { this.checkProfile('onROMoveStories', 'profile2') @@ -1092,7 +1088,7 @@ export class MosDevice implements IMOSDevice { async sendROMoveStories(Action: IMOSStoryAction, Stories: Array): Promise { const message = new MosModel.ROMoveStories(Action, Stories, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROMoveItems(cb: (Action: IMOSItemAction, Items: Array) => Promise): void { this.checkProfile('onROMoveItems', 'profile2') @@ -1101,7 +1097,7 @@ export class MosDevice implements IMOSDevice { async sendROMoveItems(Action: IMOSItemAction, Items: Array): Promise { const message = new MosModel.ROMoveItems(Action, Items, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onRODeleteStories(cb: (Action: IMOSROAction, Stories: Array) => Promise): void { this.checkProfile('onRODeleteStories', 'profile2') @@ -1110,7 +1106,7 @@ export class MosDevice implements IMOSDevice { async sendRODeleteStories(Action: IMOSROAction, Stories: Array): Promise { const message = new MosModel.RODeleteStories(Action, Stories, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onRODeleteItems(cb: (Action: IMOSStoryAction, Items: Array) => Promise): void { this.checkProfile('onRODeleteItems', 'profile2') @@ -1119,7 +1115,7 @@ export class MosDevice implements IMOSDevice { async sendRODeleteItems(Action: IMOSStoryAction, Items: Array): Promise { const message = new MosModel.RODeleteItems(Action, Items, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROSwapStories( cb: (Action: IMOSROAction, StoryID0: IMOSString128, StoryID1: IMOSString128) => Promise @@ -1134,7 +1130,7 @@ export class MosDevice implements IMOSDevice { ): Promise { const message = new MosModel.ROSwapStories(Action, StoryID0, StoryID1, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onROSwapItems( cb: (Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128) => Promise @@ -1145,7 +1141,7 @@ export class MosDevice implements IMOSDevice { async sendROSwapItems(Action: IMOSStoryAction, ItemID0: IMOSString128, ItemID1: IMOSString128): Promise { const message = new MosModel.ROSwapItems(Action, ItemID0, ItemID1, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } // ============================================================================================================ @@ -1159,7 +1155,7 @@ export class MosDevice implements IMOSDevice { async sendObjectCreate(object: IMOSObject): Promise { const message = new MosModel.MosObjCreate(object, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosAck.fromXML(data.mos.mosAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosAck.fromXML(data.mos.mosAck, this.strict)) } onItemReplace(cb: (roID: IMOSString128, storyID: IMOSString128, item: IMOSItem) => Promise): void { @@ -1170,7 +1166,7 @@ export class MosDevice implements IMOSDevice { async sendItemReplace(options: MosItemReplaceOptions): Promise { const message = new MosModel.MosItemReplace(options, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } onRequestSearchableSchema(cb: (username: string) => Promise): void { @@ -1181,7 +1177,7 @@ export class MosDevice implements IMOSDevice { async sendRequestSearchableSchema(username: string): Promise { const message = new MosModel.MosReqSearchableSchema({ username }, this.strict) const response = await this.executeCommand(message) - return response.mos.mosListSearchableSchema + return this.handleParseReply(() => response.mos.mosListSearchableSchema) } onRequestObjectList(cb: (objList: IMOSRequestObjectList) => Promise): void { @@ -1192,9 +1188,12 @@ export class MosDevice implements IMOSDevice { async sendRequestObjectList(reqObjList: IMOSRequestObjectList): Promise { const message = new MosModel.MosReqObjList(reqObjList, this.strict) const response = await this.executeCommand(message) - const objList = response.mos.mosObjList - if (objList.list) objList.list = MosModel.XMLMosObjects.fromXML(objList.list.mosObj, this.strict) - return objList + + return this.handleParseReply(() => { + const objList = response.mos.mosObjList + if (objList.list) objList.list = MosModel.XMLMosObjects.fromXML(objList.list.mosObj, this.strict) + return objList + }) } onRequestObjectActionNew(cb: (obj: IMOSObject) => Promise): void { @@ -1204,7 +1203,7 @@ export class MosDevice implements IMOSDevice { async sendRequestObjectActionNew(obj: IMOSObject): Promise { const message = new MosModel.MosReqObjActionNew({ object: obj }, this.strict) const response = await this.executeCommand(message) - return MosModel.XMLMosAck.fromXML(response.mos.mosAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosAck.fromXML(response.mos.mosAck, this.strict)) } onRequestObjectActionUpdate(cb: (objId: IMOSString128, obj: IMOSObject) => Promise): void { @@ -1214,7 +1213,7 @@ export class MosDevice implements IMOSDevice { async sendRequestObjectActionUpdate(objId: IMOSString128, obj: IMOSObject): Promise { const message = new MosModel.MosReqObjActionUpdate({ object: obj, objectId: objId }, this.strict) const response = await this.executeCommand(message) - return MosModel.XMLMosAck.fromXML(response.mos.mosAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosAck.fromXML(response.mos.mosAck, this.strict)) } onRequestObjectActionDelete(cb: (objId: IMOSString128) => Promise): void { this.checkProfile('onRequestObjectActionDelete', 'profile3') @@ -1223,7 +1222,7 @@ export class MosDevice implements IMOSDevice { async sendRequestObjectActionDelete(objId: IMOSString128): Promise { const message = new MosModel.MosReqObjActionDelete({ objectId: objId }, this.strict) const response = await this.executeCommand(message) - return MosModel.XMLMosAck.fromXML(response.mos.mosAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosAck.fromXML(response.mos.mosAck, this.strict)) } // Deprecated methods: @@ -1277,17 +1276,19 @@ export class MosDevice implements IMOSDevice { if (this._currentConnection) { const reply = await this.executeCommand(message) - if (has(reply.mos, 'roListAll')) { - let xmlRos: Array = reply.mos.roListAll?.ro - if (!Array.isArray(xmlRos)) xmlRos = [xmlRos] - const ros: Array = [] - xmlRos.forEach((xmlRo) => { - if (xmlRo) { - ros.push(MosModel.XMLRunningOrderBase.fromXML(xmlRo, this.strict)) - } - }) - return ros - } else throw new Error(`Unknown response: ${safeStringify(reply).slice(0, 200)}`) + return this.handleParseReply(() => { + if (has(reply.mos, 'roListAll')) { + let xmlRos: Array = reply.mos.roListAll?.ro + if (!Array.isArray(xmlRos)) xmlRos = [xmlRos] + const ros: Array = [] + xmlRos.forEach((xmlRo) => { + if (xmlRo) { + ros.push(MosModel.XMLRunningOrderBase.fromXML(xmlRo, this.strict)) + } + }) + return ros + } else throw new Error(`Unknown response: ${safeStringify(reply).slice(0, 200)}`) + }) } else throw new Error(`Unable to send message due to no current connection`) } onRunningOrderStory(cb: (story: IMOSROFullStory) => Promise): void { @@ -1297,7 +1298,7 @@ export class MosDevice implements IMOSDevice { async sendRunningOrderStory(story: IMOSROFullStory): Promise { const message = new MosModel.ROStory(story, this.strict) const data = await this.executeCommand(message) - return MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict) + return this.handleParseReply(() => MosModel.XMLMosROAck.fromXML(data.mos.roAck, this.strict)) } // Deprecated methods: @@ -1406,6 +1407,19 @@ export class MosDevice implements IMOSDevice { private _emitConnectionChange(): void { if (this._callbackOnConnectionChange) this._callbackOnConnectionChange(this.getConnectionStatus()) } + private handleParseReply(fcn: () => T): T { + try { + return fcn() + } catch (e) { + if (e instanceof Error) { + const newError = new Error(`Error when parsing reply MOS data: ${e.message}`) + if (e.stack) newError.stack = e.stack + throw newError + } else { + throw new Error(`Error when parsing reply MOS data: ${e}`) + } + } + } /** throws if there is an error */ private _ensureReply(reply: any): MosReply { if (!reply.mos) throw new Error(`Unknown data: missing from message`) From fdcf58974f2d326f23ad545bf5a63626ae383e18 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 27 Dec 2023 15:34:24 +0100 Subject: [PATCH 03/10] chore: improve unit test --- .../src/__tests__/MosConnection.spec.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/connector/src/__tests__/MosConnection.spec.ts b/packages/connector/src/__tests__/MosConnection.spec.ts index d1394e41..d79c9878 100644 --- a/packages/connector/src/__tests__/MosConnection.spec.ts +++ b/packages/connector/src/__tests__/MosConnection.spec.ts @@ -72,6 +72,11 @@ describe('MosDevice: General', () => { }) ) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mos.on('error', onError) + mos.on('warning', onWarning) + expect(mos.profiles).toMatchObject({ '0': true, '1': true, @@ -83,6 +88,9 @@ describe('MosDevice: General', () => { '7': false, }) + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + await mos.dispose() }) test('Incoming connections', async () => { @@ -94,11 +102,18 @@ describe('MosDevice: General', () => { '1': true, }, }) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mos.on('error', onError) + mos.on('warning', onWarning) expect(mos.acceptsConnections).toBe(true) await initMosConnection(mos) expect(mos.isListening).toBe(true) expect(SocketMock.instances).toHaveLength(0) + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + // close sockets after test await mos.dispose() }) @@ -111,6 +126,11 @@ describe('MosDevice: General', () => { '1': true, }, }) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mos.on('error', onError) + mos.on('warning', onWarning) + expect(mos.acceptsConnections).toBe(true) await initMosConnection(mos) expect(mos.isListening).toBe(true) @@ -146,6 +166,9 @@ describe('MosDevice: General', () => { expect(SocketMock.instances[2].destroy).toHaveBeenCalledTimes(1) expect(SocketMock.instances[3].destroy).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + expect(mosDevice.hasConnection).toEqual(false) }) test('MosDevice secondary', async () => { @@ -157,6 +180,11 @@ describe('MosDevice: General', () => { '1': true, }, }) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mos.on('error', onError) + mos.on('warning', onWarning) + expect(mos.acceptsConnections).toBe(true) await initMosConnection(mos) expect(mos.isListening).toBe(true) @@ -236,6 +264,9 @@ describe('MosDevice: General', () => { returnedObj = await mosDevice.sendRequestMOSObject(xmlApiData.mosObj.ID) expect(returnedObj).toBeTruthy() + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + await mos.dispose() }) test('init and connectionStatusChanged', async () => { @@ -252,6 +283,11 @@ describe('MosDevice: General', () => { }, }) ) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mosConnection.on('error', onError) + mosConnection.on('warning', onWarning) + await initMosConnection(mosConnection) const mosDevice = await mosConnection.connect({ @@ -299,6 +335,10 @@ describe('MosDevice: General', () => { // mock cause timeout mosConnection.checkProfileValidness() + + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + await mosConnection.dispose() }) test('buddy failover', async () => { @@ -310,6 +350,14 @@ describe('MosDevice: General', () => { '1': true, }, }) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mos.on('error', (e) => { + // filter out heartbeat errors: + if (!(e + '').match(/heartbeat.*timed out/i)) onError(e) + }) + mos.on('warning', onWarning) + await initMosConnection(mos) const mosDevice: MosDevice = await mos.connect({ primary: { @@ -382,6 +430,9 @@ describe('MosDevice: General', () => { expect(connMocks[1].destroy).toHaveBeenCalledTimes(1) expect(connMocks[2].destroy).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + await mos.dispose() }) test('buddy failover - primary starts offline', async () => { @@ -393,6 +444,14 @@ describe('MosDevice: General', () => { '1': true, }, }) + const onError = jest.fn((e) => console.log(e)) + const onWarning = jest.fn((e) => console.log(e)) + mos.on('error', (e) => { + // filter out heartbeat errors: + if (!(e + '').match(/heartbeat.*timed out/i)) onError(e) + }) + mos.on('warning', onWarning) + await initMosConnection(mos) const mosDevice: MosDevice = await mos.connect({ primary: { @@ -471,6 +530,9 @@ describe('MosDevice: General', () => { expect(connMocks[1].destroy).toHaveBeenCalledTimes(1) expect(connMocks[2].destroy).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + expect(onWarning).toHaveBeenCalledTimes(0) + await mos.dispose() }) }) From cfc036f5c2604ae193bc2d683e02ad2a9d6bb477 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 19 Dec 2023 17:29:58 +0100 Subject: [PATCH 04/10] fix: revert mosTime support of empty string. Now, mosTime will throw on all invalid input formats --- packages/connector/src/__mocks__/socket.ts | 2 +- .../helper/src/mosModel/profile0/heartBeat.ts | 2 +- .../src/mosModel/profile2/roElementStat.ts | 2 +- .../src/mosTypes/__tests__/mosTime.spec.ts | 6 +- packages/model/src/mosTypes/mosTime.ts | 66 +++++++++---------- 5 files changed, 34 insertions(+), 44 deletions(-) diff --git a/packages/connector/src/__mocks__/socket.ts b/packages/connector/src/__mocks__/socket.ts index b2754eab..82d94172 100644 --- a/packages/connector/src/__mocks__/socket.ts +++ b/packages/connector/src/__mocks__/socket.ts @@ -212,7 +212,7 @@ export class SocketMock extends EventEmitter implements Socket { ${ncsID} ${messageId}\ - + \r\n` this.mockReceiveMessage(this.encode(repl)) diff --git a/packages/helper/src/mosModel/profile0/heartBeat.ts b/packages/helper/src/mosModel/profile0/heartBeat.ts index 886622d1..f9ccc4b7 100644 --- a/packages/helper/src/mosModel/profile0/heartBeat.ts +++ b/packages/helper/src/mosModel/profile0/heartBeat.ts @@ -10,7 +10,7 @@ export class HeartBeat extends MosMessage { /** */ constructor(port: PortType, time: IMOSTime | undefined, strict: boolean) { super(port, strict) - if (!time) time = getMosTypes(true).mosTime.create(undefined) + if (!time) time = getMosTypes(true).mosTime.create(Date.now()) this.time = time } diff --git a/packages/helper/src/mosModel/profile2/roElementStat.ts b/packages/helper/src/mosModel/profile2/roElementStat.ts index 09b81996..6014e9f5 100644 --- a/packages/helper/src/mosModel/profile2/roElementStat.ts +++ b/packages/helper/src/mosModel/profile2/roElementStat.ts @@ -24,7 +24,7 @@ export class ROElementStat extends MosMessage { constructor(options: ROElementStatOptions, strict: boolean) { super('upper', strict) this.options = options - this.time = getMosTypes(strict).mosTime.create(undefined) + this.time = getMosTypes(strict).mosTime.create(Date.now()) } /** */ diff --git a/packages/model/src/mosTypes/__tests__/mosTime.spec.ts b/packages/model/src/mosTypes/__tests__/mosTime.spec.ts index b697060e..99202678 100644 --- a/packages/model/src/mosTypes/__tests__/mosTime.spec.ts +++ b/packages/model/src/mosTypes/__tests__/mosTime.spec.ts @@ -68,7 +68,7 @@ describe('MosTime', () => { expect(toTime(date.toString())).toBe(date.getTime()) // locale time expect(toTime(date.toUTCString())).toBe(date.getTime()) // utc expect(toTime(123456789)).toBe(123456789) - expect(Math.abs(toTime(undefined) - new Date().getTime())).toBeLessThan(10) + expect(Math.abs(toTime(Date.now()) - new Date().getTime())).toBeLessThan(10) expect(mosTypes.mosTime.valueOf(mosTypes.mosTime.create(mosTypes.mosTime.create(date)))).toBe(date.getTime()) @@ -84,11 +84,7 @@ describe('MosTime', () => { expect(toTime('2009-04-11T14:22:07+5:00')).toBe(new Date('2009-04-11T14:22:07+05:00').getTime()) expect(toTime('2009-04-11T14:22:07+5:30')).toBe(new Date('2009-04-11T14:22:07+05:30').getTime()) expect(toTime('2009-04-11T14:22:07+5:5')).toBe(new Date('2009-04-11T14:22:07+05:05').getTime()) - expect(toTime('Sun Feb 25 2018 08:59:08 GMT+0100 (CET)')).toBe(new Date('2018-02-25T08:59:08+01:00').getTime()) - - // Empty string - expect(typeof toTime('')).toBe('number') }) test('format time strings correctly', () => { const mosTypes = getMosTypes(true) diff --git a/packages/model/src/mosTypes/mosTime.ts b/packages/model/src/mosTypes/mosTime.ts index ea28d3e3..9ba12174 100644 --- a/packages/model/src/mosTypes/mosTime.ts +++ b/packages/model/src/mosTypes/mosTime.ts @@ -20,41 +20,36 @@ export function create(timestamp: AnyValue, strict: boolean): IMOSTime { if (typeof timestamp === 'number') { time = new Date(timestamp) } else if (typeof timestamp === 'string') { - if (timestamp.trim()) { - // formats: - // YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] - // Sun Feb 25 2018 08:59:08 GMT+0100 (CET) - // 2018-02-25T08:00:45.528Z - - let _timezoneZuluIndicator = '' - let _timezoneDeclaration = '' - - // parse out custom Z indicator (mos-centric) - const customFormatParseResult = parseMosCustomFormat(timestamp) - // parse out custom timezones (mos local-local centric format) - const timezoneParseResult = parseTimeOffset(timestamp) - - if (customFormatParseResult !== false) { - _timezone = customFormatParseResult.timezoneIndicator - _timezoneOffset = customFormatParseResult.timezoneOffset - _timezoneZuluIndicator = customFormatParseResult.timezoneIndicator - - const r = customFormatParseResult - const dateStr = `${r.yy}-${r.mm}-${r.dd}T${r.hh}:${r.ii}:${r.ss}${ - r.ms ? '.' + r.ms : '' - }${_timezoneZuluIndicator}${_timezoneDeclaration}` - time = new Date(dateStr) - } else if (timezoneParseResult !== false) { - _timezoneDeclaration = timezoneParseResult.timezoneDeclaration - - time = new Date(timestamp) - } else { - // try to parse the time directly with Date, for Date-supported formats - time = new Date(timestamp) - } + // formats: + // YYYY-MM-DD'T'hh:mm:ss[,ddd]['Z'] + // Sun Feb 25 2018 08:59:08 GMT+0100 (CET) + // 2018-02-25T08:00:45.528Z + + let _timezoneZuluIndicator = '' + let _timezoneDeclaration = '' + + // parse out custom Z indicator (mos-centric) + const customFormatParseResult = parseMosCustomFormat(timestamp) + // parse out custom timezones (mos local-local centric format) + const timezoneParseResult = parseTimeOffset(timestamp) + + if (customFormatParseResult !== false) { + _timezone = customFormatParseResult.timezoneIndicator + _timezoneOffset = customFormatParseResult.timezoneOffset + _timezoneZuluIndicator = customFormatParseResult.timezoneIndicator + + const r = customFormatParseResult + const dateStr = `${r.yy}-${r.mm}-${r.dd}T${r.hh}:${r.ii}:${r.ss}${ + r.ms ? '.' + r.ms : '' + }${_timezoneZuluIndicator}${_timezoneDeclaration}` + time = new Date(dateStr) + } else if (timezoneParseResult !== false) { + _timezoneDeclaration = timezoneParseResult.timezoneDeclaration + + time = new Date(timestamp) } else { - // empty string, create Date now: - time = new Date() + // try to parse the time directly with Date, for Date-supported formats + time = new Date(timestamp) } } else if (typeof timestamp === 'object') { if (timestamp instanceof Date) { @@ -68,8 +63,7 @@ export function create(timestamp: AnyValue, strict: boolean): IMOSTime { throw new Error(`MosTime: Invalid input: "${timestamp}"`) } } else { - // no timestamp, create Date now: - time = new Date() + throw new Error(`MosTime: Invalid input: "${timestamp}"`) } if (isNaN(time.getTime())) { From bf4a0845a7f836015aa452db45c023debef94480 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 19 Dec 2023 17:35:44 +0100 Subject: [PATCH 05/10] fix: change how data fields is parsed, better handling of missing data. Introducing createRequired and createOptional, used to better handle missing data (filling in fallback data in non-strict mode) --- packages/connector/src/__mocks__/testData.ts | 28 +++- .../src/__tests__/Profile0-non-strict.spec.ts | 151 ++++++++++++++++++ .../connector/src/__tests__/Profile0.spec.ts | 87 +++++++--- .../connector/src/__tests__/Profile1.spec.ts | 49 +++--- .../Profile0-non-strict.spec.ts.snap | 10 ++ .../__snapshots__/Profile0.spec.ts.snap | 9 ++ .../__snapshots__/Profile2.spec.ts.snap | 66 ++++++-- .../__snapshots__/Profile3.spec.ts.snap | 55 ++----- .../__snapshots__/Profile4.spec.ts.snap | 110 +++---------- packages/connector/src/__tests__/lib.ts | 2 +- packages/helper/src/mosModel/parseMosTypes.ts | 80 ++++++++++ .../src/mosModel/profile0/xmlConversion.ts | 37 +++-- .../src/mosModel/profile1/xmlConversion.ts | 25 +-- .../src/mosModel/profile2/xmlConversion.ts | 60 +++---- packages/model/src/mosTypes.ts | 13 +- .../mosTypes/__tests__/mosDuration.spec.ts | 1 - packages/model/src/mosTypes/mosDuration.ts | 7 +- packages/model/src/mosTypes/mosString128.ts | 7 + packages/model/src/mosTypes/mosTime.ts | 9 ++ 19 files changed, 560 insertions(+), 246 deletions(-) create mode 100644 packages/connector/src/__tests__/Profile0-non-strict.spec.ts create mode 100644 packages/connector/src/__tests__/__snapshots__/Profile0-non-strict.spec.ts.snap create mode 100644 packages/helper/src/mosModel/parseMosTypes.ts diff --git a/packages/connector/src/__mocks__/testData.ts b/packages/connector/src/__mocks__/testData.ts index 0a31cbe4..4374d382 100644 --- a/packages/connector/src/__mocks__/testData.ts +++ b/packages/connector/src/__mocks__/testData.ts @@ -489,6 +489,30 @@ const xmlApiData = { profile1: true, }, }), + machineInfoReply: literal({ + manufacturer: mosTypes.mosString128.create('RadioVision, Ltd.'), + model: mosTypes.mosString128.create('TCS6000'), + hwRev: mosTypes.mosString128.create(''), + swRev: mosTypes.mosString128.create('2.1.0.37'), + DOM: mosTypes.mosString128.create(''), + SN: mosTypes.mosString128.create('927748927'), + ID: mosTypes.mosString128.create('airchache.newscenter.com'), + time: mosTypes.mosTime.create('2009-04-11T17:20:42'), + opTime: mosTypes.mosTime.create('2009-03-01T23:55:10'), + mosRev: mosTypes.mosString128.create('2.8.2'), + + supportedProfiles: { + deviceType: 'NCS', + profile0: true, + profile1: true, + profile2: true, + profile3: true, + profile4: true, + profile5: true, + profile6: true, + profile7: true, + }, + }), mosObj: literal({ ID: mosTypes.mosString128.create('M000123'), Slug: mosTypes.mosString128.create('My new object'), @@ -1085,7 +1109,7 @@ const xmlApiData = { literal({ ID: mosTypes.mosString128.create('17'), Items: [], - Slug: mosTypes.mosString128.create(undefined), + Slug: undefined, }), ], roElementAction_insert_item_Action: literal({ @@ -1174,7 +1198,7 @@ const xmlApiData = { literal({ ID: mosTypes.mosString128.create('17'), Items: [], - Slug: mosTypes.mosString128.create(undefined), + Slug: undefined, // MosExternalMetaData?: Array, }), ], diff --git a/packages/connector/src/__tests__/Profile0-non-strict.spec.ts b/packages/connector/src/__tests__/Profile0-non-strict.spec.ts new file mode 100644 index 00000000..7a46e416 --- /dev/null +++ b/packages/connector/src/__tests__/Profile0-non-strict.spec.ts @@ -0,0 +1,151 @@ +import { + checkMessageSnapshot, + clearMocks, + decode, + doBeforeAll, + encode, + getMessageId, + getMosConnection, + getMosDevice, + getXMLReply, + mosTypes, + setupMocks, +} from './lib' +import { MosConnection, MosDevice, IMOSObject, IMOSListMachInfo } from '..' +import { SocketMock } from '../__mocks__/socket' +import { xmlData, xmlApiData } from '../__mocks__/testData' + +/* eslint-disable @typescript-eslint/no-unused-vars */ +// @ts-ignore imports are unused +import { Socket } from 'net' +/* eslint-enable @typescript-eslint/no-unused-vars */ + +beforeAll(() => { + setupMocks() +}) +beforeEach(() => { + clearMocks() +}) +describe('Profile 0 - non strict', () => { + let mosDevice: MosDevice + let mosConnection: MosConnection + + let socketMockLower: SocketMock + let socketMockUpper: SocketMock + let socketMockQuery: SocketMock + + let serverSocketMockLower: SocketMock + let serverSocketMockUpper: SocketMock + let serverSocketMockQuery: SocketMock + + let onRequestMachineInfo: jest.Mock + let onRequestMOSObject: jest.Mock + let onRequestAllMOSObjects: jest.Mock + + beforeAll(async () => { + mosConnection = await getMosConnection( + { + '0': true, + '1': true, // Must support at least one other profile + }, + false + ) + mosDevice = await getMosDevice(mosConnection) + + // Profile 0: + onRequestMachineInfo = jest.fn(async () => { + return xmlApiData.machineInfo + }) + mosDevice.onRequestMachineInfo(async (): Promise => { + return onRequestMachineInfo() + }) + // Profile 1: + onRequestMOSObject = jest.fn(async () => { + return xmlApiData.mosObj + }) + onRequestAllMOSObjects = jest.fn(async () => { + return [xmlApiData.mosObj, xmlApiData.mosObj2] + }) + mosDevice.onRequestMOSObject(async (objId: string): Promise => { + return onRequestMOSObject(objId) + }) + mosDevice.onRequestAllMOSObjects(async (): Promise> => { + return onRequestAllMOSObjects() + }) + const b = doBeforeAll() + socketMockLower = b.socketMockLower + socketMockUpper = b.socketMockUpper + socketMockQuery = b.socketMockQuery + serverSocketMockLower = b.serverSocketMockLower + serverSocketMockUpper = b.serverSocketMockUpper + serverSocketMockQuery = b.serverSocketMockQuery + + mosDevice.checkProfileValidness() + mosConnection.checkProfileValidness() + }) + afterAll(async () => { + await mosDevice.dispose() + await mosConnection.dispose() + }) + beforeEach(() => { + onRequestMOSObject.mockClear() + onRequestAllMOSObjects.mockClear() + + serverSocketMockLower.mockClear() + serverSocketMockUpper.mockClear() + if (serverSocketMockQuery) serverSocketMockQuery.mockClear() + socketMockLower.mockClear() + socketMockUpper.mockClear() + if (socketMockQuery) socketMockQuery.mockClear() + }) + test('init', async () => { + expect(mosDevice).toBeTruthy() + expect(socketMockLower).toBeTruthy() + expect(socketMockUpper).toBeTruthy() + expect(serverSocketMockLower).toBeTruthy() + }) + test('requestMachineInfo - empty