Files
ontime/apps/server/src/api-data/db/migration/migration.test.ts
T

598 lines
16 KiB
TypeScript

import {
AutomationSettings,
CustomFields,
type Day,
DatabaseModel,
EndAction,
OntimeView,
ProjectData,
Rundown,
Settings,
SupportedEntry,
TimeStrategy,
TimerLifeCycle,
TimerType,
URLPreset,
ViewSettings,
} from 'ontime-types';
import * as v3 from './db.migration.v3.js';
import * as v4 from './db.migration.v4.js';
describe('v3 to v4', () => {
const oldDb = {
rundown: [
{
id: 'event1',
type: 'event',
cue: '123',
title: 'ABC',
note: 'DEF',
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: false,
linkStart: false,
timeStrategy: TimeStrategy.LockDuration,
timeStart: 0,
timeEnd: 10,
duration: 10,
isPublic: false,
skip: false,
colour: 'blue',
timeWarning: 5,
timeDanger: 2,
custom: {
song: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
doseNotExist: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
revision: 0,
delay: 0,
dayOffset: 0,
gap: 0,
},
{ id: 'group0', type: 'block', title: 'GROUP 0' },
{
id: 'event2',
type: SupportedEntry.Event,
cue: '124',
title: 'ABC',
note: 'DEF',
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: false,
linkStart: false,
timeStrategy: TimeStrategy.LockDuration,
timeStart: 0,
timeEnd: 10,
duration: 10,
isPublic: false,
skip: false,
colour: 'blue',
timeWarning: 5,
timeDanger: 2,
custom: {
wow: 'http://www.agoodimage.com',
artist: 'Ib Andersen',
},
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
revision: 0,
delay: 0,
dayOffset: 0,
gap: 0,
},
{
id: 'event3',
type: SupportedEntry.Event,
cue: '125',
// title: 'ABC', this has a missing field
note: 'DEF',
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: false,
linkStart: false,
timeStrategy: TimeStrategy.LockDuration,
timeStart: 0,
timeEnd: 10,
duration: 10,
isPublic: false,
skip: false,
colour: 'blue',
timeWarning: 5,
timeDanger: 2,
custom: {
wow: 'http://www.agoodimage.com',
artist: 'Ib Andersen',
},
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
revision: 0,
delay: 0,
dayOffset: 0,
gap: 0,
},
{ id: 'group1', type: 'block', title: 'GROUP 1' },
{ id: 'delay', type: 'delay', duration: 1000 },
],
project: {
title: 'Eurovision Song Contest',
description: 'Turin 2022',
publicUrl: '123',
publicInfo: '456',
backstageUrl: 'www.github.com/cpvalente/ontime',
backstageInfo: 'Rehearsal Schedule - Turin 2022\nAll performers to wear full costumes for 1st rehearsal',
},
settings: {
version: '3.3.3',
serverPort: 4001,
editorKey: null,
operatorKey: null,
timeFormat: '24',
language: 'en',
},
viewSettings: {
overrideStyles: false,
normalColor: '#ffffffcc',
warningColor: '#ffa528',
dangerColor: '#ff7300',
freezeEnd: false,
endMessage: '',
},
urlPresets: [
{
enabled: true,
alias: 'clock',
pathAndParams:
'timer?showLeadingZeros=true&timerType=clock&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
},
{
enabled: true,
alias: 'minimal',
pathAndParams:
'timer?showLeadingZeros=true&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
},
],
customFields: {
song: {
label: 'Song and Dance',
type: 'string',
colour: '#339E4E',
},
artist: {
label: 'Artist and Host',
type: 'string',
colour: '#3E75E8',
},
wow: {
label: 'WOW 123',
type: 'image',
colour: '#E80000',
},
},
automation: {
enabledAutomations: true,
enabledOscIn: true,
oscPortIn: 8888,
triggers: [],
automations: {},
},
};
test('migrate settings', () => {
const expectSettings: Settings & { serverPort: number } = {
version: '4.0.0',
serverPort: 4001,
editorKey: null,
operatorKey: null,
timeFormat: '24',
language: 'en',
};
const newSettings = v3.migrateSettings(oldDb);
expect(newSettings).toEqual(expectSettings);
});
test('migrate view settings', () => {
const expectViewSettings: ViewSettings = {
dangerColor: '#ff7300',
normalColor: '#ffffffcc',
overrideStyles: false,
warningColor: '#ffa528',
};
const newViewSettings = v3.migrateViewSettings(oldDb);
expect(newViewSettings).toEqual(expectViewSettings);
});
test('migrate url preset', () => {
const expectUrlPresets: URLPreset[] = [
{
enabled: true,
alias: 'clock',
target: OntimeView.Timer,
search:
'showLeadingZeros=true&timerType=clock&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
displayInNav: false,
options: {},
},
{
enabled: true,
alias: 'minimal',
target: OntimeView.Timer,
search:
'showLeadingZeros=true&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
displayInNav: false,
options: {},
},
];
const newUrlPreset = v3.migrateURLPresets(oldDb);
expect(newUrlPreset).toEqual(expectUrlPresets);
});
test('migrate project data', () => {
const expectProjectData: ProjectData = {
title: 'Eurovision Song Contest',
description: 'Turin 2022',
url: 'www.github.com/cpvalente/ontime',
info: 'Rehearsal Schedule - Turin 2022\nAll performers to wear full costumes for 1st rehearsal',
logo: null,
custom: [],
};
const newProjectData = v3.migrateProjectData(oldDb);
expect(newProjectData).toEqual(expectProjectData);
});
test('migrate custom fields', () => {
const expectCustomFields: CustomFields = {
Song_and_Dance: {
label: 'Song and Dance',
type: 'text',
colour: '#339E4E',
},
Artist_and_Host: {
label: 'Artist and Host',
type: 'text',
colour: '#3E75E8',
},
WOW_123: {
label: 'WOW 123',
type: 'image',
colour: '#E80000',
},
};
const parsedData = v3.migrateCustomFields(oldDb);
expect(parsedData).not.toBeUndefined();
expect(parsedData?.customFields).toEqual(expectCustomFields);
expect(parsedData?.translationTable).toEqual(
new Map([
['song', 'Song_and_Dance'],
['artist', 'Artist_and_Host'],
['wow', 'WOW_123'],
]),
);
});
test('migrate rundown', () => {
const expectedRundown: Rundown = {
id: 'default',
title: 'Default',
order: ['event1', 'group0', 'group1'],
flatOrder: ['event1', 'group0', 'event2', 'event3', 'group1', 'delay'],
entries: {
event1: {
id: 'event1',
type: SupportedEntry.Event,
cue: '123',
title: 'ABC',
note: 'DEF',
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: false,
linkStart: false,
timeStrategy: TimeStrategy.LockDuration,
timeStart: 0,
timeEnd: 10,
duration: 10,
skip: false,
colour: 'blue',
timeWarning: 5,
timeDanger: 2,
custom: { Song_and_Dance: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' },
triggers: [],
revision: -1,
flag: false,
parent: null,
delay: 0,
dayOffset: 0 as Day,
gap: 0,
},
group0: {
id: 'group0',
type: SupportedEntry.Group,
title: 'GROUP 0',
colour: '',
custom: {},
duration: 0,
entries: ['event2', 'event3'],
isFirstLinked: false,
note: '',
revision: -1,
targetDuration: null,
timeEnd: null,
timeStart: null,
},
event2: {
id: 'event2',
type: SupportedEntry.Event,
cue: '124',
title: 'ABC',
note: 'DEF',
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: false,
linkStart: false,
timeStrategy: TimeStrategy.LockDuration,
timeStart: 0,
timeEnd: 10,
duration: 10,
skip: false,
colour: 'blue',
timeWarning: 5,
timeDanger: 2,
custom: {
WOW_123: 'http://www.agoodimage.com',
Artist_and_Host: 'Ib Andersen',
},
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
flag: false,
parent: 'group0',
revision: -1,
delay: 0,
dayOffset: 0 as Day,
gap: 0,
},
event3: {
id: 'event3',
type: SupportedEntry.Event,
cue: '125',
//@ts-expect-error - if a field is missing we should pass it through a let our normal import fix it
title: undefined,
note: 'DEF',
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: false,
linkStart: false,
timeStrategy: TimeStrategy.LockDuration,
timeStart: 0,
timeEnd: 10,
duration: 10,
skip: false,
colour: 'blue',
timeWarning: 5,
timeDanger: 2,
custom: {
WOW_123: 'http://www.agoodimage.com',
Artist_and_Host: 'Ib Andersen',
},
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
flag: false,
parent: 'group0',
revision: -1,
delay: 0,
dayOffset: 0 as Day,
gap: 0,
},
group1: {
id: 'group1',
type: SupportedEntry.Group,
title: 'GROUP 1',
colour: '',
custom: {},
duration: 0,
entries: ['delay'],
isFirstLinked: false,
note: '',
revision: -1,
targetDuration: null,
timeEnd: null,
timeStart: null,
},
delay: {
type: SupportedEntry.Delay,
id: 'delay',
duration: 1000,
parent: 'group1',
},
},
revision: 0,
};
const translationTable = new Map([
['song', 'Song_and_Dance'],
['artist', 'Artist_and_Host'],
['wow', 'WOW_123'],
]);
//@ts-expect-error - we know the default rundown should appear
expect(v3.migrateRundown(oldDb, translationTable)['default']).toStrictEqual(expectedRundown);
});
describe('migrate old automation', () => {
test('osc', () => {
const oldData = {
portIn: 8881,
portOut: 55890,
targetIP: '127.0.0.1',
enabledIn: true,
enabledOut: true,
subscriptions: [
{
id: '23f4d8',
cycle: 'onClock',
address: '/test',
payload: 'bip',
enabled: true,
},
],
};
const expectedAutomation: AutomationSettings = {
enabledAutomations: true,
enabledOscIn: true,
oscPortIn: 8881,
triggers: [
{
id: '23f4d8-T',
title: 'Migrated Trigger 23f4d8',
trigger: TimerLifeCycle.onClock,
automationId: '23f4d8-A',
},
],
automations: {
'23f4d8-A': {
id: '23f4d8-A',
title: 'Migrated Automation 23f4d8',
filterRule: 'any',
filters: [],
outputs: [{ type: 'osc', targetIP: '127.0.0.1', targetPort: 55890, address: '/test', args: 'bip' }],
},
},
};
expect(v3.migrateAutomations({ osc: oldData })).toStrictEqual(expectedAutomation);
});
test('http', () => {
const oldData = {
enabledOut: true,
subscriptions: [
{
id: '1ge4r8',
cycle: 'onClock',
message: 'http://www.test.com',
enabled: true,
},
],
};
const expectedAutomation: AutomationSettings = {
enabledAutomations: true,
enabledOscIn: false,
oscPortIn: 8888,
triggers: [
{
id: '1ge4r8-T',
title: 'Migrated Trigger 1ge4r8',
trigger: TimerLifeCycle.onClock,
automationId: '1ge4r8-A',
},
],
automations: {
'1ge4r8-A': {
id: '1ge4r8-A',
title: 'Migrated Automation 1ge4r8',
filterRule: 'any',
filters: [],
outputs: [{ type: 'http', url: 'http://www.test.com' }],
},
},
};
expect(v3.migrateAutomations({ http: oldData })).toStrictEqual(expectedAutomation);
});
});
});
describe('v4 remove server port', () => {
const demoDb = {
rundowns: {},
project: {
title: 'Eurovision Song Contest',
description: 'Turin 2022',
url: 'www.github.com/cpvalente/ontime',
info: 'Rehearsal Schedule - Turin 2022\nAll performers to wear full costumes for 1st rehearsal',
logo: null,
custom: [],
},
settings: {
app: 'ontime',
version: '4.0.0',
serverPort: 4001,
editorKey: null,
operatorKey: null,
timeFormat: '24',
language: 'en',
},
viewSettings: {
dangerColor: '#ff7300',
normalColor: '#ffffffcc',
overrideStyles: false,
warningColor: '#ffa528',
},
customFields: {
song: {
label: 'Song',
type: 'text',
colour: '#339E4E',
},
artist: {
label: 'Artist',
type: 'text',
colour: '#3E75E8',
},
},
urlPresets: [
{
enabled: true,
alias: 'clock',
target: 'timer',
search:
'timer?showLeadingZeros=true&timerType=clock&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
},
{
enabled: true,
alias: 'minimal',
target: 'timer',
search:
'timer?showLeadingZeros=true&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
},
],
automation: {
enabledAutomations: false,
enabledOscIn: true,
oscPortIn: 8888,
triggers: [],
automations: {},
},
};
it('should migrate if server port exists', () => {
expect(v4.shouldMigrateServerPort(demoDb)).toBeTruthy();
});
test('should not migrate if the version is newer', () => {
expect(
v4.shouldMigrateServerPort({ settings: { version: '5.0.0', serverPort: 4001 } } as unknown as DatabaseModel),
).toBeFalsy();
});
test('should not migrate if there is no server port', () => {
expect(v4.shouldMigrateServerPort({ settings: { version: '4.0.0' } } as DatabaseModel)).toBeFalsy();
});
it('remove server port from project', () => {
const { db: result, serverPort } = v4.migrateServerPort(demoDb as DatabaseModel);
expect(result).not.toHaveProperty('settings.serverPort');
expect(serverPort).toBe(4001);
expect(result.automation).toMatchObject(demoDb.automation);
expect(result.customFields).toMatchObject(demoDb.customFields);
expect(result.project).toMatchObject(demoDb.project);
expect(result.rundowns).toMatchObject(demoDb.rundowns);
expect(result.settings).toMatchObject({
app: 'ontime',
version: '4.5.0',
editorKey: null,
operatorKey: null,
timeFormat: '24',
language: 'en',
});
expect(result.urlPresets).toMatchObject(demoDb.urlPresets);
expect(result.viewSettings).toMatchObject(demoDb.viewSettings);
});
});