mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 17:03:53 +00:00
c1fcdf7065
* feat: get server port from app satate or env optional startup port from env will always override function for parsing port from env populate default port in app state add test for migration * bump version
594 lines
16 KiB
TypeScript
594 lines
16 KiB
TypeScript
import {
|
|
AutomationSettings,
|
|
CustomFields,
|
|
DatabaseModel,
|
|
EndAction,
|
|
OntimeView,
|
|
ProjectData,
|
|
Rundown,
|
|
Settings,
|
|
SupportedEntry,
|
|
TimerLifeCycle,
|
|
TimerType,
|
|
TimeStrategy,
|
|
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',
|
|
options: {},
|
|
},
|
|
{
|
|
enabled: true,
|
|
alias: 'minimal',
|
|
target: OntimeView.Timer,
|
|
search:
|
|
'showLeadingZeros=true&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
|
|
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,
|
|
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,
|
|
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,
|
|
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);
|
|
});
|
|
});
|