Skip to content
Open
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
87 changes: 87 additions & 0 deletions meteor/server/api/rest/v1/__tests__/typeConversions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
import { buildStudioFromResolved } from '../typeConversion'
import { wrapDefaultObject } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
import { DBStudio, IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio'
import { OrganizationId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { IBlueprintConfig, StudioBlueprintManifest } from '@sofie-automation/blueprints-integration'
import { APIStudio } from '../../../../lib/rest/v1'

describe('buildStudioFromResolved', () => {
test('preserves existing fields and overrides API ones', async () => {
const blueprintManifest = {} as unknown as StudioBlueprintManifest
const apiStudio = {
name: 'New Name',
settings: { frameRate: 25 } as IStudioSettings,
config: { someValue: 1 },
supportedShowStyleBase: ['A'],
} as APIStudio
const existingStudio = {
_id: protectString<StudioId>('studio0'),
organizationId: protectString<OrganizationId>('orgId'),
name: 'Studio 0',
settingsWithOverrides: wrapDefaultObject({ frameRate: 50, allowHold: true } as IStudioSettings),
blueprintConfigWithOverrides: wrapDefaultObject({ B: 0 } as IBlueprintConfig),
} as DBStudio
const studio = await buildStudioFromResolved({
apiStudio,
existingStudio,
blueprintManifest,
blueprintId: protectString('bp1'),
studioId: protectString('studio0'),
})

expect(studio._id).toBe('studio0')
expect(studio.name).toBe('New Name')
expect(studio.organizationId).toBe('orgId')
expect(studio.blueprintId).toBe('bp1')
expect(studio.settingsWithOverrides.overrides).toContainEqual({
op: 'set',
path: 'frameRate',
value: 25,
})
expect(studio.blueprintConfigWithOverrides.overrides).toContainEqual({
op: 'set',
path: 'someValue',
value: 1,
})
})
test('preserves existing fields and overrides API ones with blueprintConfigFromAPI defined', async () => {
const blueprintManifest = { blueprintConfigFromAPI: async () => ({ fromBlueprints: true }) } as any
const apiStudio = {
name: 'New Name',
settings: { frameRate: 25 } as IStudioSettings,
config: { someValue: 1 },
supportedShowStyleBase: ['A'],
blueprintConfigPresetId: 'preset0',
} as APIStudio
const existingStudio = {
_id: protectString<StudioId>('studio0'),
organizationId: protectString<OrganizationId>('orgId'),
name: 'Studio 0',
settingsWithOverrides: wrapDefaultObject({ frameRate: 50 } as IStudioSettings),
blueprintConfigWithOverrides: wrapDefaultObject({ B: 0 } as IBlueprintConfig),
} as DBStudio
const studio = await buildStudioFromResolved({
apiStudio,
existingStudio,
blueprintManifest,
blueprintId: protectString('bp1'),
studioId: protectString('studio0'),
})

expect(studio._id).toBe('studio0')
expect(studio.name).toBe('New Name')
expect(studio.organizationId).toBe('orgId')
expect(studio.blueprintId).toBe('bp1')
expect(studio.settingsWithOverrides.overrides).toContainEqual({
op: 'set',
path: 'frameRate',
value: 25,
})
expect(studio.blueprintConfigWithOverrides.overrides).toContainEqual({
op: 'set',
path: 'fromBlueprints',
value: true,
})
})
})
59 changes: 44 additions & 15 deletions meteor/server/api/rest/v1/typeConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,19 +304,42 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
}
if (!blueprint) return undefined

let studio: DBStudio | undefined
if (existingId) studio = await Studios.findOneAsync(existingId)
let existingStudio: DBStudio | undefined
if (existingId) existingStudio = await Studios.findOneAsync(existingId)

const blueprintManifest = evalBlueprint(blueprint) as StudioBlueprintManifest

return buildStudioFromResolved({
apiStudio,
existingStudio,
blueprintManifest,
blueprintId: blueprint._id,
studioId: existingId ?? getRandomId(),
})
}

export async function buildStudioFromResolved({
apiStudio,
existingStudio,
blueprintManifest,
blueprintId,
studioId,
}: {
apiStudio: APIStudio
existingStudio?: DBStudio
blueprintManifest: StudioBlueprintManifest
blueprintId: BlueprintId
studioId: StudioId
}): Promise<DBStudio> {
let blueprintConfig: ObjectWithOverrides<IBlueprintConfig>
if (typeof blueprintManifest.blueprintConfigFromAPI !== 'function') {
blueprintConfig = studio
? updateOverrides(studio.blueprintConfigWithOverrides, apiStudio.config as IBlueprintConfig)
blueprintConfig = existingStudio
? updateOverrides(existingStudio.blueprintConfigWithOverrides, apiStudio.config as IBlueprintConfig)
: wrapDefaultObject({})
} else {
blueprintConfig = studio
blueprintConfig = existingStudio
? updateOverrides(
studio.blueprintConfigWithOverrides,
existingStudio.blueprintConfigWithOverrides,
await StudioBlueprintConfigFromAPI(apiStudio, blueprintManifest)
)
: convertObjectIntoOverrides(await StudioBlueprintConfigFromAPI(apiStudio, blueprintManifest))
Expand All @@ -325,15 +348,7 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
const studioSettings = studioSettingsFrom(apiStudio.settings)

return {
_id: existingId ?? getRandomId(),
name: apiStudio.name,
blueprintId: blueprint?._id,
blueprintConfigPresetId: apiStudio.blueprintConfigPresetId,
blueprintConfigWithOverrides: blueprintConfig,
settingsWithOverrides: studio
? updateOverrides(studio.settingsWithOverrides, studioSettings)
: wrapDefaultObject(studioSettings),
supportedShowStyleBase: apiStudio.supportedShowStyleBase?.map((id) => protectString<ShowStyleBaseId>(id)) ?? [],
// fill in the blanks if there is no existing studio
organizationId: null,
mappingsWithOverrides: wrapDefaultObject({}),
routeSetsWithOverrides: wrapDefaultObject({}),
Expand All @@ -350,6 +365,20 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
},
lastBlueprintConfig: undefined,
lastBlueprintFixUpHash: undefined,

// take what existing studio might have
...existingStudio,

// override what apiStudio can
_id: studioId,
name: apiStudio.name,
blueprintId,
blueprintConfigPresetId: apiStudio.blueprintConfigPresetId,
blueprintConfigWithOverrides: blueprintConfig,
settingsWithOverrides: existingStudio
? updateOverrides(existingStudio.settingsWithOverrides, studioSettings)
: wrapDefaultObject(studioSettings),
supportedShowStyleBase: apiStudio.supportedShowStyleBase?.map((id) => protectString<ShowStyleBaseId>(id)) ?? [],
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clone = require('fast-clone')
import { literal } from '../../lib'
import {
applyAndValidateOverrides,
Expand Down Expand Up @@ -186,8 +187,8 @@ describe('applyAndValidateOverrides', () => {
},
},
overrides: [
{ op: 'set', path: 'valA', value: 'def' },
{ op: 'set', path: 'valB.valD', value: 'uvw' },
{ op: 'set', path: 'valA', value: 'def' },
{ op: 'set', path: 'valB.valC', value: 6 },
],
})
Expand Down Expand Up @@ -235,4 +236,189 @@ describe('applyAndValidateOverrides', () => {
})
)
})

test('update overrides - add to existing overrides', () => {
const inputObj: BasicType = {
valA: 'abc',
valB: {
valC: 5,
valD: 'foo',
},
}

const inputObjWithOverrides: ObjectWithOverrides<BasicType> = {
defaults: inputObj,
overrides: [
{ op: 'set', path: 'valA', value: 'def' },
{ op: 'set', path: 'valB.valC', value: 6 },
],
}

const updateObj: BasicType = {
valA: 'ghi',
valB: {
valC: 7,
valD: 'bar',
},
}

const res = updateOverrides(inputObjWithOverrides, updateObj)
expect(res).toBeTruthy()

expect(res).toStrictEqual(
literal<ObjectWithOverrides<BasicType>>({
defaults: {
valA: 'abc',
valB: {
valC: 5,
valD: 'foo',
},
},
overrides: [
{ op: 'set', path: 'valA', value: 'ghi' },
{ op: 'set', path: 'valB.valC', value: 7 },
{ op: 'set', path: 'valB.valD', value: 'bar' },
],
})
)
})

test('update overrides - add to existing overrides #2', () => {
const inputObj = {
valA: 'abc',
valB: {
'0': { propA: 35, propB: 'Mic 1' },
'1': { propA: 36, propB: 'Mic 2' },
'2': { propA: 37, propB: 'Mic 3' },
},
}

const inputObjWithOverrides: ObjectWithOverrides<any> = {
defaults: inputObj,
overrides: [
{
op: 'set',
path: 'valB.0.propC',
value: true,
},
{
op: 'set',
path: 'valB.0.propD',
value: true,
},
{ op: 'set', path: 'valB.1.propC', value: true },
],
}

const updateObj = {
valA: 'abc',
valB: {
'0': { propA: 35, propB: 'Mic 1', propC: true, propD: true },
'1': { propA: 36, propB: 'Mic 2', propC: true },
'2': { propA: 37, propB: 'Mic 3', propC: true },
},
}

const res = updateOverrides(inputObjWithOverrides, updateObj)
expect(res).toBeTruthy()

expect(res).toStrictEqual(
literal<ObjectWithOverrides<any>>({
defaults: clone(inputObj),
overrides: [
{
op: 'set',
path: 'valB.0.propC',
value: true,
},
{
op: 'set',
path: 'valB.0.propD',
value: true,
},
{ op: 'set', path: 'valB.1.propC', value: true },
{ op: 'set', path: 'valB.2.propC', value: true },
],
})
)
})

test('update overrides - delete key', () => {
const inputObj = {
valA: 'abc',
valB: {
'0': { propA: 35, propB: 'Mic 1' },
'1': { propA: 36, propB: 'Mic 2' },
'2': { propA: 37, propB: 'Mic 3' },
},
}

const inputObjWithOverrides: ObjectWithOverrides<any> = {
defaults: inputObj,
overrides: [],
}

const updateObj = {
valA: 'abc',
valB: {
'0': { propA: 35, propB: 'Mic 1' },
'1': { propA: 36, propB: 'Mic 2' },
},
}

const res = updateOverrides(inputObjWithOverrides, updateObj)
expect(res).toBeTruthy()

expect(res).toStrictEqual(
literal<ObjectWithOverrides<any>>({
defaults: clone(inputObj),
overrides: [
{
op: 'delete',
path: 'valB.2',
},
],
})
)
})

test('update overrides - delete value', () => {
const inputObj = {
valA: 'abc',
valB: {
'0': { propA: 35, propB: 'Mic 1' },
'1': { propA: 36, propB: 'Mic 2' },
'2': { propA: 37, propB: 'Mic 3' },
},
}

const inputObjWithOverrides: ObjectWithOverrides<any> = {
defaults: inputObj,
overrides: [],
}

const updateObj = {
valA: 'abc',
valB: {
'0': { propA: 35, propB: 'Mic 1' },
'1': { propA: 36, propB: 'Mic 2' },
'2': { propA: 37 },
},
}

const res = updateOverrides(inputObjWithOverrides, updateObj)
expect(res).toBeTruthy()

expect(res).toStrictEqual(
literal<ObjectWithOverrides<any>>({
defaults: clone(inputObj),
overrides: [
{
op: 'delete',
path: 'valB.2.propB',
},
],
})
)
})
})
Loading
Loading