Skip to content

Commit

Permalink
Merge pull request #97 from SuperFlyTV/fix/nrcs-returns-empty-time-ob…
Browse files Browse the repository at this point in the history
…ject

fix: NRCS returns empty objects
  • Loading branch information
jstarpl authored Mar 14, 2024
2 parents f996003 + 9bb6825 commit 8d6e3ee
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 2 deletions.
28 changes: 28 additions & 0 deletions packages/connector/src/__mocks__/testData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ const xmlData = {
<mosProfile number="7">YES</mosProfile>
</supportedProfiles>
</listMachInfo>`,
machineInfoOpenMedia: `<listMachInfo>
<manufacturer>Annova</manufacturer>
<model>1</model>
<hwRev>1</hwRev>
<swRev>1</swRev>
<DOM>1</DOM>
<SN>1</SN>
<ID>NCS.ID.MOS</ID>
<time></time>
<opTime></opTime>
<mosRev>2.84</mosRev>
</listMachInfo>`,
reqObj: `<mosReqObj> <objID>M000123</objID> </mosReqObj>`,
mosReqAll: `<mosReqAll> <pause>0</pause> </mosReqAll>`,
mosObj: `<mosObj> <objID>M000123</objID> <objSlug>Hotel Fire</objSlug> <mosAbstract> <b>Hotel Fire</b> <em>vo</em> :30 </mosAbstract> <objGroup>Show 7</objGroup> <objType>VIDEO</objType> <objTB>59.94</objTB> <objRev>1</objRev> <objDur>1800</objDur> <status>NEW</status> <objAir>READY</objAir><objPaths><objPath techDescription="MPEG2 Video">\\server\\media\\clip392028cd2320s0d.mxf</objPath><objProxyPath techDescription="WM9 750Kbps">https://server/proxy/clipe.wmv</objProxyPath><objMetadataPath techDescription="MOS Object">https://server/proxy/clipe.xml</objMetadataPath></objPaths> <createdBy>Chris</createdBy> <created>2009-10-31T23:39:12</created> <changedBy>Chris</changedBy> <changed>2009-10-31T23:39:12</changed> <description> <p> Exterior footage of <em>Baley Park Hotel</em> on fire with natural sound. Trucks are visible for the first portion of the clip. <em>CG locator at 0:04 and duration 0:05, Baley Park Hotel.</em> </p> <p> <tab/> Cuts to view of fire personnel exiting hotel lobby and cleaning up after the fire is out. </p> <p> <em>Clip has been doubled for pad on voice over.</em> </p> </description> <mosExternalMetadata> <mosScope>STORY</mosScope> <mosSchema>https://MOSA4.com/mos/supported_schemas/MOSAXML2.08</mosSchema> <mosPayload> <Owner>SHOLMES</Owner> <ModTime>20010308142001</ModTime> <mediaTime>0</mediaTime> <TextTime>278</TextTime> <ModBy>LJOHNSTON</ModBy> <Approved>0</Approved> <Creator>SHOLMES</Creator> </mosPayload> </mosExternalMetadata></mosObj>`,
Expand Down Expand Up @@ -516,6 +528,22 @@ const xmlApiData = {
profile7: true,
},
}),
machineInfoOpenMediaReply: literal<IMOSListMachInfo>({
manufacturer: mosTypes.mosString128.create('Annova'),
model: mosTypes.mosString128.create('1'),
hwRev: mosTypes.mosString128.create('1'),
swRev: mosTypes.mosString128.create('1'),
DOM: mosTypes.mosString128.create('1'),
SN: mosTypes.mosString128.create('1'),
ID: mosTypes.mosString128.create('NCS.ID.MOS'),
time: mosTypes.mosTime.create('0'),
opTime: mosTypes.mosTime.create('0'),
mosRev: mosTypes.mosString128.create('2.84'),

supportedProfiles: {
deviceType: 'N/A',
},
}),
mosObj: literal<IMOSObject>({
ID: mosTypes.mosString128.create('M000123'),
Slug: mosTypes.mosString128.create('My new object'),
Expand Down
200 changes: 200 additions & 0 deletions packages/connector/src/__tests__/Profile0-open-media.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import {
checkMessageSnapshot,
clearMocks,
decode,
doBeforeAll,
encode,
getMessageId,
getMosConnection,
getMosDevice,
getXMLReply,
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<any, any>
let onRequestMOSObject: jest.Mock<any, any>
let onRequestAllMOSObjects: jest.Mock<any, any>

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<IMOSListMachInfo> => {
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<IMOSObject | null> => {
return onRequestMOSObject(objId)
})
mosDevice.onRequestAllMOSObjects(async (): Promise<Array<IMOSObject>> => {
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 - missing <time>', async () => {
// Prepare mock server response:
const mockReply = jest.fn((data) => {
const str = decode(data)
const messageID = getMessageId(str)
const replyMessage = xmlData.machineInfoOpenMedia.replace(/<time>.*<\/time>/, '')
const repl = getXMLReply(messageID, replyMessage)
return encode(repl)
})
socketMockLower.mockAddReply(mockReply)
if (!xmlApiData.mosObj.ID) throw new Error('xmlApiData.mosObj.ID not set')

const returnedMachineInfo: IMOSListMachInfo = await mosDevice.requestMachineInfo()
expect(mockReply).toHaveBeenCalledTimes(1)
const msg = decode(mockReply.mock.calls[0][0])
expect(msg).toMatch(/<reqMachInfo\/>/)
checkMessageSnapshot(msg)

const replyMessage = { ...xmlApiData.machineInfoOpenMediaReply }
replyMessage.time = returnedMachineInfo.time
replyMessage.opTime = returnedMachineInfo.opTime

expect(returnedMachineInfo).toMatchObject(replyMessage)
// expect(returnedMachineInfo.opTime).toBeUndefined()
})
test('requestMachineInfo - empty <time>', async () => {
// Prepare mock server response:
const mockReply = jest.fn((data) => {
const str = decode(data)
const messageID = getMessageId(str)
const replyMessage = xmlData.machineInfoOpenMedia.replace(/<time>.*<\/time>/, '<time></time>')
const repl = getXMLReply(messageID, replyMessage)
return encode(repl)
})
socketMockLower.mockAddReply(mockReply)
if (!xmlApiData.mosObj.ID) throw new Error('xmlApiData.mosObj.ID not set')

const returnedMachineInfo: IMOSListMachInfo = await mosDevice.requestMachineInfo()
expect(mockReply).toHaveBeenCalledTimes(1)
const msg = decode(mockReply.mock.calls[0][0])
expect(msg).toMatch(/<reqMachInfo\/>/)
checkMessageSnapshot(msg)

const replyMessage = { ...xmlApiData.machineInfoOpenMediaReply }
replyMessage.time = returnedMachineInfo.time
replyMessage.opTime = returnedMachineInfo.opTime

expect(returnedMachineInfo).toMatchObject(replyMessage)
// expect(returnedMachineInfo.opTime).toBeUndefined()
})
test('requestMachineInfo - bad formatted <time>', async () => {
// Prepare mock server response:
const mockReply = jest.fn((data) => {
const str = decode(data)
const messageID = getMessageId(str)
const replyMessage = xmlData.machineInfoOpenMedia.replace(/<time>.*<\/time>/, '<time>BAD DATA</time>')
const repl = getXMLReply(messageID, replyMessage)
return encode(repl)
})
socketMockLower.mockAddReply(mockReply)
if (!xmlApiData.mosObj.ID) throw new Error('xmlApiData.mosObj.ID not set')

let caughtError: Error | undefined = undefined
await mosDevice.requestMachineInfo().catch((err) => {
caughtError = err
})
expect(mockReply).toHaveBeenCalledTimes(1)

expect(String(caughtError)).toMatch(/Unable to parse MOS reply.*Invalid timestamp/i)
})
test('requestMachineInfo - missing <opTime>', async () => {
// Prepare mock server response:
const mockReply = jest.fn((data) => {
const str = decode(data)
const replyMessage = xmlData.machineInfoOpenMedia.replace(/<opTime>.*<\/opTime>/, '')
const repl = getXMLReply(getMessageId(str), replyMessage)
return encode(repl)
})
socketMockLower.mockAddReply(mockReply)
if (!xmlApiData.mosObj.ID) throw new Error('xmlApiData.mosObj.ID not set')

const returnedMachineInfo: IMOSListMachInfo = await mosDevice.requestMachineInfo()
expect(mockReply).toHaveBeenCalledTimes(1)
const msg = decode(mockReply.mock.calls[0][0])
expect(msg).toMatch(/<reqMachInfo\/>/)
checkMessageSnapshot(msg)

const replyMessage = { ...xmlApiData.machineInfoOpenMediaReply }
replyMessage.opTime = returnedMachineInfo.opTime
replyMessage.time = returnedMachineInfo.time

expect(returnedMachineInfo).toMatchObject(replyMessage)
expect(returnedMachineInfo.opTime).toBeUndefined()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Profile 0 - non strict requestMachineInfo - empty <time> 1`] = `
"<mos>
<ncsID>their.mos.id</ncsID>
<mosID>our.mos.id</mosID>
<messageID>xx</messageID>
<reqMachInfo/>
</mos>"
`;
exports[`Profile 0 - non strict requestMachineInfo - missing <opTime> 1`] = `
"<mos>
<ncsID>their.mos.id</ncsID>
<mosID>our.mos.id</mosID>
<messageID>xx</messageID>
<reqMachInfo/>
</mos>"
`;
exports[`Profile 0 - non strict requestMachineInfo - missing <time> 1`] = `
"<mos>
<ncsID>their.mos.id</ncsID>
<mosID>our.mos.id</mosID>
<messageID>xx</messageID>
<reqMachInfo/>
</mos>"
`;
2 changes: 1 addition & 1 deletion packages/helper/src/mosModel/profile0/xmlConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function parseSupportedProfiles(xmlSupportedProfiles: any, strict: boolean): IMO
}
}

if (Array.isArray(xmlSupportedProfiles.mosProfile)) {
if (Array.isArray(xmlSupportedProfiles?.mosProfile)) {
for (const mosProfile of xmlSupportedProfiles.mosProfile) {
// @ts-expect-error hack
parsed[`profile${mosProfile.attributes.number}`] = mosProfile.text === 'YES'
Expand Down
2 changes: 1 addition & 1 deletion packages/model/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export interface IMOSListMachInfo {
mosRev: IMOSString128

supportedProfiles: {
deviceType: 'NCS' | 'MOS'
deviceType: 'NCS' | 'MOS' | 'N/A'
profile0?: boolean
profile1?: boolean
profile2?: boolean
Expand Down
2 changes: 2 additions & 0 deletions packages/model/src/mosTypes/mosTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export function create(timestamp: AnyValue, strict: boolean): IMOSTime {
time = timestamp
} else if (timestamp?._mosTime !== undefined) {
time = new Date(timestamp._mosTime)
} else if (!strict) {
time = new Date()
} else {
throw new Error(`MosTime: Invalid input: "${timestamp}"`)
}
Expand Down

0 comments on commit 8d6e3ee

Please sign in to comment.