mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-15 11:03:04 +00:00
feat(automation): improve definition and trigger editing
This commit is contained in:
committed by
Carlos Valente
parent
79c21daf73
commit
b07544ab84
@@ -51,4 +51,34 @@ describe('automation controllers', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('persists definitions using the not_contains filter operator', async () => {
|
||||
const request = {
|
||||
body: {
|
||||
...requestBody,
|
||||
filters: [{ field: 'eventNow.title', operator: 'not_contains', value: 'break' }],
|
||||
},
|
||||
} as Request;
|
||||
|
||||
await postAutomation(request, makeResponse());
|
||||
|
||||
expect(automationDao.addAutomation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ filters: [expect.objectContaining({ operator: 'not_contains' })] }),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects trigger bindings when editing a definition', async () => {
|
||||
const request = {
|
||||
body: { ...requestBody, triggers: [{ trigger: 'onStart', automationId: 'other-definition' }] },
|
||||
params: { id: 'automation-id' },
|
||||
} as unknown as Request;
|
||||
const response = makeResponse();
|
||||
|
||||
await editAutomation(request, response);
|
||||
|
||||
expect(automationDao.editAutomation).not.toHaveBeenCalled();
|
||||
expect(response.status).toHaveBeenCalledWith(400);
|
||||
expect(response.send).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: 'Automation definitions cannot include triggers' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,7 +146,7 @@ describe('editAutomation()', () => {
|
||||
title: 'test-osc',
|
||||
filterRule: 'all',
|
||||
filters: [],
|
||||
outputs: [],
|
||||
outputs: [makeOSCAction(), makeHTTPAction()],
|
||||
});
|
||||
await addAutomation({
|
||||
title: 'test-http',
|
||||
@@ -164,7 +164,7 @@ describe('editAutomation()', () => {
|
||||
title: 'test-osc',
|
||||
filterRule: 'all',
|
||||
filters: expect.any(Array),
|
||||
outputs: expect.any(Array),
|
||||
outputs: [makeOSCAction(), makeHTTPAction()],
|
||||
});
|
||||
|
||||
const editedOSC = await editAutomation(firstAutomation.id, {
|
||||
@@ -179,9 +179,34 @@ describe('editAutomation()', () => {
|
||||
title: 'edited-title',
|
||||
filterRule: 'any',
|
||||
filters: expect.any(Array),
|
||||
outputs: expect.any(Array),
|
||||
outputs: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('replaces all outputs when an automation definition is edited', async () => {
|
||||
const editedWithOneOutput = await editAutomation(firstAutomation.id, {
|
||||
title: 'edited-title',
|
||||
filterRule: 'any',
|
||||
filters: [],
|
||||
outputs: [makeHTTPAction()],
|
||||
});
|
||||
expect(editedWithOneOutput.outputs).toEqual([makeHTTPAction()]);
|
||||
});
|
||||
|
||||
it('preserves global triggers when a definition is edited', async () => {
|
||||
await addTrigger({ title: 'On Start', trigger: TimerLifeCycle.onStart, automationId: firstAutomation.id });
|
||||
|
||||
await editAutomation(firstAutomation.id, {
|
||||
title: 'edited-title',
|
||||
filterRule: 'all',
|
||||
filters: [],
|
||||
outputs: [],
|
||||
});
|
||||
|
||||
expect(getAutomationTriggers()).toEqual([
|
||||
expect.objectContaining({ automationId: firstAutomation.id, trigger: TimerLifeCycle.onStart }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteAutomation()', () => {
|
||||
@@ -225,4 +250,35 @@ describe('deleteAutomation()', () => {
|
||||
const removed = getAutomations();
|
||||
expect(Object.keys(removed).length).toEqual(0);
|
||||
});
|
||||
|
||||
it('refuses an automation attached to a global trigger', async () => {
|
||||
const automationId = Object.keys(getAutomations())[0];
|
||||
await addTrigger({ title: 'On Start', trigger: TimerLifeCycle.onStart, automationId });
|
||||
|
||||
await expect(deleteAutomation({}, automationId)).rejects.toThrow(/used in trigger/);
|
||||
expect(getAutomationTriggers()).toHaveLength(1);
|
||||
expect(getAutomations()[automationId]).toBeDefined();
|
||||
});
|
||||
|
||||
it('refuses an automation attached to an event', async () => {
|
||||
const automationId = Object.keys(getAutomations())[0];
|
||||
const projectRundowns: ProjectRundowns = {
|
||||
'rundown-1': {
|
||||
id: 'rundown-1',
|
||||
title: 'Rundown 1',
|
||||
order: ['1'],
|
||||
flatOrder: ['1'],
|
||||
entries: {
|
||||
'1': makeOntimeEvent({
|
||||
id: '1',
|
||||
triggers: [{ id: 'trigger-1', title: 'Trigger 1', trigger: TimerLifeCycle.onClock, automationId }],
|
||||
}),
|
||||
},
|
||||
revision: 1,
|
||||
},
|
||||
};
|
||||
|
||||
await expect(deleteAutomation(projectRundowns, automationId)).rejects.toThrow(/used in rundown/);
|
||||
expect(getAutomations()[automationId]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { ProjectRundowns, TimerLifeCycle } from 'ontime-types';
|
||||
|
||||
import { makeOntimeEvent } from '../../rundown/__mocks__/rundown.mocks.js';
|
||||
import { isAutomationUsed, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js';
|
||||
import { isAutomationUsed, isHostname, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js';
|
||||
|
||||
describe('isHostname()', () => {
|
||||
it.each(['localhost', 'qlab', 'osc.example.com', 'osc-target.example'])('accepts %s', (hostname) => {
|
||||
expect(isHostname(hostname)).toBe(true);
|
||||
});
|
||||
|
||||
it.each(['not a host', '-osc.example', 'osc-.example', 'osc..example', 'osc.example.'])('rejects %s', (hostname) => {
|
||||
expect(isHostname(hostname)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTemplateNested()', () => {
|
||||
it('parses string with a single-level variable name', () => {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { parseOutput } from '../automation.validation.js';
|
||||
import { parseAutomation, parseOutput } from '../automation.validation.js';
|
||||
|
||||
describe('parseAutomation', () => {
|
||||
it('rejects trigger bindings from definition payloads', () => {
|
||||
expect(() =>
|
||||
parseAutomation({ title: 'Definition', filterRule: 'all', filters: [], outputs: [], triggers: [] }),
|
||||
).toThrow('Automation definitions cannot include triggers');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseOutput', () => {
|
||||
describe('handles OSC outputs', () => {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { runtimeStorePlaceholder } from 'ontime-types';
|
||||
|
||||
const { send } = vi.hoisted(() => ({ send: vi.fn() }));
|
||||
|
||||
vi.mock('node:dgram', () => ({
|
||||
createSocket: vi.fn(() => ({ send })),
|
||||
}));
|
||||
|
||||
import { emitOSC } from '../clients/osc.client.js';
|
||||
|
||||
describe('emitOSC()', () => {
|
||||
beforeEach(() => {
|
||||
send.mockClear();
|
||||
});
|
||||
|
||||
it('resolves templates in the target host before sending', () => {
|
||||
emitOSC(
|
||||
{
|
||||
type: 'osc',
|
||||
targetIP: '{{eventNow.custom.oscTarget}}',
|
||||
targetPort: 53000,
|
||||
address: '/cue/start',
|
||||
args: '',
|
||||
},
|
||||
{
|
||||
...runtimeStorePlaceholder,
|
||||
eventNow: {
|
||||
id: 'current-event',
|
||||
type: 'event',
|
||||
cue: '1',
|
||||
title: 'Opening',
|
||||
note: '',
|
||||
timeStart: 0,
|
||||
timeEnd: 0,
|
||||
duration: 0,
|
||||
timerType: 'count-down',
|
||||
colour: '',
|
||||
delay: 0,
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
endAction: 'none',
|
||||
revision: 0,
|
||||
custom: { oscTarget: '192.0.2.10' },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(send).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
0,
|
||||
expect.any(Number),
|
||||
53000,
|
||||
'192.0.2.10',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user