refactor: escalate errors from parser

This commit is contained in:
Carlos Valente
2024-06-23 22:47:24 +02:00
committed by Carlos Valente
parent de9af5aaa2
commit 20d9df2501
7 changed files with 440 additions and 184 deletions
@@ -2,27 +2,205 @@ import {
CustomFields,
DatabaseModel,
EndAction,
HttpSettings,
HttpSubscription,
OSCSettings,
OntimeEvent,
OntimeRundown,
OscSubscription,
Settings,
SupportedEvent,
TimeStrategy,
TimerType,
URLPreset,
} from 'ontime-types';
import {
parseCustomFields,
parseHttp,
parseOsc,
parseProject,
parseRundown,
parseSettings,
parseUrlPresets,
parseViewSettings,
sanitiseCustomFields,
sanitiseHttpSubscriptions,
sanitiseOscSubscriptions,
} from '../parserFunctions.js';
describe('sanitiseOscSubscriptions()', () => {
it('returns an empty array if not an array', () => {
expect(sanitiseOscSubscriptions(undefined)).toEqual([]);
describe('parseRundown()', () => {
it('returns an empty array if no rundown is given', () => {
const errorEmitter = vi.fn();
const result = parseRundown({}, errorEmitter);
expect(result.rundown).toEqual([]);
expect(result.customFields).toEqual({});
expect(errorEmitter).toHaveBeenCalledTimes(2);
});
it('parses data, skipping invalid results', () => {
const errorEmitter = vi.fn();
const rundown = [
{ id: '1', type: SupportedEvent.Event, title: 'test', skip: false }, // OK
{ id: '1', type: SupportedEvent.Block, title: 'test 2', skip: false }, // duplicate ID
{}, // no data
{ id: '2', title: 'test 2', skip: false }, // no type
] as OntimeRundown;
const { rundown: parsedRundown } = parseRundown({ rundown, customFields: {} }, errorEmitter);
expect(parsedRundown.length).toEqual(1);
expect(parsedRundown.at(0)).toMatchObject({ id: '1', type: SupportedEvent.Event, title: 'test', skip: false });
expect(errorEmitter).toHaveBeenCalled();
});
});
describe('parseProject()', () => {
it('returns an a base model if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseProject({}, errorEmitter);
expect(result).toBeTypeOf('object');
expect(errorEmitter).toHaveBeenCalledOnce();
});
});
describe('parseSettings()', () => {
it('throws if settings object does not exist', () => {
expect(() => parseSettings({})).toThrow();
});
it('returns an a base model as long as we have the app and version', () => {
const minimalSettings = { app: 'ontime', version: '1' } as Settings;
const result = parseSettings({ settings: minimalSettings });
expect(result).toBeTypeOf('object');
});
});
describe('parseViewSettings()', () => {
it('returns an a base model if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseViewSettings({}, errorEmitter);
expect(result).toBeTypeOf('object');
expect(errorEmitter).toHaveBeenCalledOnce();
});
});
describe('parseOsc()', () => {
it('returns an a base model if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseOsc({}, errorEmitter);
expect(result).toBeTypeOf('object');
expect(errorEmitter).toHaveBeenCalledOnce();
});
it('parses data, skipping invalid results', () => {
const errorEmitter = vi.fn();
const osc = {
subscriptions: [
{ id: '1', cycle: 'onLoad', address: '/test', payload: 'test', enabled: true }, // OK
{}, // no data
{ id: '2', cycle: 'onStart', payload: 'test', enabled: true }, // no address
],
} as OSCSettings;
const result = parseOsc({ osc }, errorEmitter);
expect(result.subscriptions.length).toEqual(1);
expect(result.subscriptions.at(0)).toMatchObject({
id: '1',
cycle: 'onLoad',
address: '/test',
payload: 'test',
enabled: true,
});
expect(errorEmitter).toHaveBeenCalled();
});
});
describe('parseHttp()', () => {
it('returns an a base model if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseHttp({}, errorEmitter);
expect(result).toBeTypeOf('object');
expect(errorEmitter).toHaveBeenCalledOnce();
});
it('parses data, skipping invalid results', () => {
const errorEmitter = vi.fn();
const http = {
subscriptions: [
{ id: '1', cycle: 'onLoad', message: 'http://', enabled: true }, // OK
{}, // no data
{ id: '2', cycle: 'onStart', enabled: true }, // no message
{ id: '3', cycle: 'onLoad', message: '/test', enabled: true }, // doesnt start with http
],
} as HttpSettings;
const result = parseHttp({ http }, errorEmitter);
expect(result.subscriptions.length).toEqual(1);
expect(result.subscriptions.at(0)).toMatchObject({
id: '1',
cycle: 'onLoad',
message: 'http://',
enabled: true,
});
expect(errorEmitter).toHaveBeenCalled();
});
});
describe('parseUrlPresets()', () => {
it('returns an a base model if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseUrlPresets({}, errorEmitter);
expect(result).toBeTypeOf('object');
expect(errorEmitter).toHaveBeenCalledOnce();
});
it('parses data, skipping invalid results', () => {
const errorEmitter = vi.fn();
const urlPresets = [{ enabled: true, alias: 'alias', pathAndParams: 'ss' }] as URLPreset[];
const result = parseUrlPresets({ urlPresets }, errorEmitter);
expect(result.length).toEqual(1);
expect(result.at(0)).toMatchObject({
enabled: true,
alias: 'alias',
pathAndParams: 'ss',
});
expect(errorEmitter).not.toHaveBeenCalled();
});
});
describe('parseCustomFields()', () => {
it('returns an a base model if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseCustomFields({}, errorEmitter);
expect(result).toBeTypeOf('object');
expect(errorEmitter).toHaveBeenCalledOnce();
});
it('parses data, skipping invalid results', () => {
const errorEmitter = vi.fn();
// @ts-expect-error -- data is external, we check bad types
expect(sanitiseOscSubscriptions({})).toEqual([]);
expect(sanitiseOscSubscriptions(null)).toEqual([]);
const customFields = {
1: { label: 'test', type: 'string', colour: 'red' }, // ok
2: { label: 'test', type: 'string' }, // duplicate label
3: { label: '', type: 'string' }, // missing colour
4: { type: 'string', colour: '' }, // missing label
} as CustomFields;
const result = parseCustomFields({ customFields }, errorEmitter);
expect(result).toMatchObject({
test: {
label: 'test',
type: 'string',
colour: 'red',
},
});
expect(errorEmitter).toHaveBeenCalled();
});
});
describe('sanitiseOscSubscriptions()', () => {
it('throws if not an array an empty array if not an array', () => {
expect(() => sanitiseOscSubscriptions(undefined)).toThrow();
// @ts-expect-error -- data is external, we check bad types
expect(() => sanitiseOscSubscriptions({})).toThrow();
expect(() => sanitiseOscSubscriptions(null)).toThrow();
});
it('returns an array of valid entries', () => {
@@ -49,18 +227,18 @@ describe('sanitiseOscSubscriptions()', () => {
{ id: '4', cycle: 'onStop', enabled: false },
{ id: '5', cycle: 'onUpdate', payload: 'test' },
{ id: '6', cycle: 'onFinish', payload: 'test', enabled: 'true' },
];
const sanitationResult = sanitiseOscSubscriptions(oscSubscriptions as OscSubscription[]);
] as OscSubscription[];
const sanitationResult = sanitiseOscSubscriptions(oscSubscriptions);
expect(sanitationResult.length).toBe(0);
});
});
describe('sanitiseHttpSubscriptions()', () => {
it('returns an empty array if not an array', () => {
expect(sanitiseHttpSubscriptions(undefined)).toEqual([]);
it('throws if the data is unexpected', () => {
expect(() => sanitiseHttpSubscriptions(undefined)).toThrow();
// @ts-expect-error -- data is external, we check bad types
expect(sanitiseHttpSubscriptions({})).toEqual([]);
expect(sanitiseHttpSubscriptions(null)).toEqual([]);
expect(() => sanitiseHttpSubscriptions({})).toThrow();
expect(() => sanitiseHttpSubscriptions(null)).toThrow();
});
it('returns an array of valid entries', () => {
@@ -148,7 +326,7 @@ describe('sanitiseCustomFields()', () => {
expect(sanitationResult).toStrictEqual(expectedCustomFields);
});
it('enforece name cohesion', () => {
it('enforce name cohesion', () => {
const customFields: CustomFields = {
test: { label: 'New Name', type: 'string', colour: 'red' },
};
@@ -214,6 +392,7 @@ describe('parseRundown() linking', () => {
skip: false,
} as OntimeEvent,
],
customFields: {},
};
const expected: OntimeRundown = [
@@ -221,10 +400,10 @@ describe('parseRundown() linking', () => {
{ ...blankEvent, id: '2', cue: '1', linkStart: '1' },
];
const result = parseRundown(data);
expect(result).toEqual(expected);
expect(result.rundown).toEqual(expected);
});
it('returns unlinkd if no previous', () => {
it('returns unlinked if no previous', () => {
const data: Partial<DatabaseModel> = {
rundown: [
{
@@ -234,11 +413,12 @@ describe('parseRundown() linking', () => {
skip: false,
} as OntimeEvent,
],
customFields: {},
};
const expected: OntimeRundown = [{ ...blankEvent, id: '2', cue: '0' }];
const result = parseRundown(data);
expect(result).toEqual(expected);
expect(result.rundown).toEqual(expected);
});
it('returns linked events past blocks and delays', () => {
@@ -272,6 +452,7 @@ describe('parseRundown() linking', () => {
skip: false,
} as OntimeEvent,
],
customFields: {},
};
const expected: OntimeRundown = [
@@ -282,6 +463,6 @@ describe('parseRundown() linking', () => {
{ ...blankEvent, id: '3', cue: '2', linkStart: '2' },
];
const result = parseRundown(data);
expect(result).toEqual(expected);
expect(result.rundown).toEqual(expected);
});
});