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: NRCS returns empty objects #97

Merged
merged 6 commits into from
Mar 14, 2024
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
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