Files
ontime/apps/server/src/api-data/automation/__tests__/automation.service.test.ts
T
Alex Christoffer Rasmussen 722e045b20 Revert "Session version" (#1649)
* Revert "refactor: add version to session endpoint"

This reverts commit cd40a6e55e.

* Revert "Remove public event feature (#1645)"

This reverts commit 08d9e24871.

* Revert "Fix: rearrange playing event (#1640)"

This reverts commit 0649678dca.

* Revert "refactor: style tweaks to edit css modal"

This reverts commit a00ec2d02a.

* Revert "refactor: remove usages of framer-motion"

This reverts commit 54a74ccc2a.

* Revert "Upgrade expressjs (#1633)"

This reverts commit 6f3ab274bd.

* Revert "Refactor: require trigger in all events objects (#1636)"

This reverts commit 90870ecfb6.

* Revert "Fix: Correct boundary condition in applyDelay"

This reverts commit b640e0e181.

* Revert "let vite be the proxy to the dev server (#1630)"

This reverts commit c41fe824cf.

* Revert "refactor: migrate custom fields to transactions"

This reverts commit 62c8319d70.

* Revert "refactor: simplify validations"

This reverts commit b1d23467a2.

* Revert "refactor: create transaction system and apply to adding entry (#1620)"

This reverts commit 9a62daf047.

* Revert "Refactor: better rounding (#1594)"

This reverts commit b9ab1c6fd7.

* Revert "refactor: improve reorder logic"

This reverts commit c1054711b0.

* Revert "chore: simplify URLs"

This reverts commit a4d4f29a37.

* Revert "refactor: order is single source of truth"

This reverts commit 2793aadea0.

* Revert "refactor: refetch targets is enum"

This reverts commit e7cfb7d9d9.

* Revert "refactor: small ux improvements"

This reverts commit 256a851c9b.

* Revert "refactor: remove trivially inferred numEvents"

This reverts commit 4ab9c81cb8.

* Revert "feat: duplicate groups"

This reverts commit 2f13d6c89e.

* Revert "feat: create group from entry selection"

This reverts commit f1f7bad25e.

* Revert "feat: create block from rundown empty"

This reverts commit a8b52a48f7.

* Revert "fix: collapsed blocks dont render children"

This reverts commit cd0999b2ab.

* Revert "refactor: type cleanup and test improvements"

This reverts commit b4c60f3f04.

* Revert "feat: allow dissolving a block"

This reverts commit 5cefad3666.

* Revert "fix: uncontrolled prop on controlled component"

This reverts commit 7a6ecd8c34.

* Revert "refactor: improve return of reorder"

This reverts commit ba96ecfd91.

* Revert "refactor: mutations on batch elements must have IDs"

This reverts commit 7bed3757f2.

* Revert "chore: upgrade dependencies"

This reverts commit e2e755b1d2.

* Revert "refactor: extract utility to merge two arrays"

This reverts commit a77d23109d.

* Revert "refactor: change network mode defaults"

This reverts commit 0eb3b8d382.

* Revert "fix: delete nested events"

This reverts commit 0021185288.

* Revert "fix: add event at end of block"

This reverts commit 94c72ff4f6.

* Revert "refactor: make finder available in exported rundown"

This reverts commit e4c08dc9b2.

* Revert "assert non null and update test (#1604)"

This reverts commit ec74af0d62.

* Revert "Fix project renumber (#1597)"

This reverts commit b6d72dd082.

* Revert "Refactor: WebSocket from flush queue to one patch (#1595)"

This reverts commit 31c311daf0.

* Revert "Refactor: ms for api calls (#1593)"

This reverts commit e9b3cc6090.

* Revert "fix test (#1601)"

This reverts commit 543b04a097.

* Revert "fix: rebase master"

This reverts commit d39b85b6e6.

* Revert "chore: correct test path"

This reverts commit bbe107bb2b.

* Revert "refactor: extract rundown parsing"

This reverts commit c616240db1.

* Revert "chore: improve convention entry <> event"

This reverts commit 166be66ce3.

* Revert "refactor: maintain flat orders"

This reverts commit 4180d0a337.

* Revert "refactor: implement operations on nested events"

This reverts commit 78108e316c.

* Revert "refactor: process events in rundown"

This reverts commit 3bb8b70915.

* Revert "chore: improve convention entry <> event"

This reverts commit 1c4f13a0ed.

* Revert "chore: rename currentBlock > parent"

This reverts commit 3ca0abad53.

* Revert "refactor: fix delay positioning in gaps"

This reverts commit 030c8f897f.

* Revert "refactor(e2e): skip flaky test"

This reverts commit 68175cfa3b.

* Revert "refactor: improve project loading"

This reverts commit fd8f757851.

* Revert "refactor: gather group metadata"

This reverts commit 876d111c61.

* Revert "refactor: swap maintains schedule"

This reverts commit 730cb95c04.

* Revert "chore: rename files"

This reverts commit 3c388d4fb5.

* Revert "refactor: restructure model to contain an object of rundowns"

This reverts commit 2e23718d73.

* Revert "refactor: clearer relationship on rundown elements"

This reverts commit 89ea8c470b.

* Revert "refactor: use strict typing"

This reverts commit 178640bfc4.

* Revert "refactor: remove stop as a possible end action"

This reverts commit 4ed38340e0.

* Revert "refactor: restructure model to contain an object of rundowns"

This reverts commit 69eb9a5eff.

* Revert "chore: remove IDE files"

This reverts commit 351425127a.

* Revert "refactor: remove unused and legacy code"

This reverts commit 95f2ba37cc.
2025-06-20 12:28:20 +02:00

317 lines
11 KiB
TypeScript

import { PlayableEvent, TimerLifeCycle } from 'ontime-types';
import { makeRuntimeStateData } from '../../../stores/__mocks__/runtimeState.mocks.js';
import { makeOntimeEvent } from '../../../services/rundown-service/__mocks__/rundown.mocks.js';
import { deleteAllTriggers, addTrigger, addAutomation } from '../automation.dao.js';
import { testConditions, triggerAutomations } from '../automation.service.js';
import * as oscClient from '../clients/osc.client.js';
import * as httpClient from '../clients/http.client.js';
import { makeOSCAction, makeHTTPAction } from './testUtils.js';
beforeAll(() => {
vi.mock('../../../classes/data-provider/DataProvider.js', () => {
// Create a small mock store
let automations = {
enabledAutomations: true,
enabledOscIn: true,
oscPortIn: 8888,
triggers: [],
automations: {},
};
return {
getDataProvider: vi.fn().mockImplementation(() => {
return {
getAutomation: vi.fn().mockImplementation(() => automations),
setAutomation: vi.fn().mockImplementation((newData) => (automations = newData)),
};
}),
};
});
});
afterAll(() => {
vi.clearAllMocks();
});
describe('triggerAction()', () => {
let oscSpy = vi.spyOn(oscClient, 'emitOSC');
let httpSpy = vi.spyOn(httpClient, 'emitHTTP');
beforeEach(async () => {
oscSpy = vi.spyOn(oscClient, 'emitOSC').mockImplementation(() => {});
httpSpy = vi.spyOn(httpClient, 'emitHTTP').mockImplementation(() => {});
await deleteAllTriggers();
const oscAutomation = await addAutomation({
title: 'test-osc',
filterRule: 'all',
filters: [],
outputs: [makeOSCAction()],
});
const httpAutomation = await addAutomation({
title: 'test-http',
filterRule: 'any',
filters: [],
outputs: [makeHTTPAction()],
});
await addTrigger({
title: 'test-osc',
trigger: TimerLifeCycle.onLoad,
automationId: oscAutomation.id,
});
await addTrigger({
title: 'test-http',
trigger: TimerLifeCycle.onFinish,
automationId: httpAutomation.id,
});
});
it('should trigger automations for a given action', () => {
const state = makeRuntimeStateData();
triggerAutomations(TimerLifeCycle.onLoad, state);
expect(oscSpy).toHaveBeenCalledTimes(1);
expect(httpSpy).not.toBeCalled();
oscSpy.mockReset();
httpSpy.mockReset();
triggerAutomations(TimerLifeCycle.onStart, state);
expect(oscClient.emitOSC).not.toBeCalled();
expect(httpSpy).not.toBeCalled();
oscSpy.mockReset();
httpSpy.mockReset();
triggerAutomations(TimerLifeCycle.onFinish, state);
expect(oscSpy).not.toBeCalled();
expect(httpSpy).toHaveBeenCalledTimes(1);
oscSpy.mockReset();
httpSpy.mockReset();
triggerAutomations(TimerLifeCycle.onStop, state);
expect(oscSpy).not.toBeCalled();
expect(httpSpy).not.toBeCalled();
});
});
describe('testConditions()', () => {
it('should return true when no filters are provided', () => {
const result = testConditions([], 'all', {});
expect(result).toBe(true);
});
describe('equals operator', () => {
it('should compare two equal values', () => {
const mockStore = makeRuntimeStateData({ clock: 10 });
const result = testConditions([{ field: 'clock', operator: 'equals', value: '10' }], 'all', mockStore);
expect(result).toBe(true);
});
it('string comparisons should be case insensitive', () => {
const mockStore = makeRuntimeStateData({ eventNow: { title: 'Title' } as PlayableEvent });
const result = testConditions(
[{ field: 'eventNow.title', operator: 'equals', value: 'title' }],
'all',
mockStore,
);
expect(result).toBe(true);
});
it('should check if a value does not exist', () => {
const mockStore = makeRuntimeStateData({ eventNow: null });
const result = testConditions([{ field: 'eventNow.title', operator: 'equals', value: '' }], 'all', mockStore);
expect(result).toBe(true);
});
it('should handle trueness boolean comparisons', () => {
const mockStore = makeRuntimeStateData({ eventNow: { countToEnd: true } as PlayableEvent });
expect(
testConditions([{ field: 'eventNow.countToEnd', operator: 'equals', value: 'true' }], 'all', mockStore),
).toBe(true);
expect(
testConditions([{ field: 'eventNow.countToEnd', operator: 'equals', value: 'false' }], 'all', mockStore),
).toBe(false);
});
it('should handle falseness boolean comparisons', () => {
const mockStore = makeRuntimeStateData({ eventNow: { countToEnd: false } as PlayableEvent });
expect(
testConditions([{ field: 'eventNow.countToEnd', operator: 'equals', value: 'false' }], 'all', mockStore),
).toBe(true);
expect(
testConditions([{ field: 'eventNow.countToEnd', operator: 'equals', value: 'true' }], 'all', mockStore),
).toBe(false);
});
});
describe('not_equals operator', () => {
it('should check if two values are different', () => {
const mockStore = makeRuntimeStateData({ clock: 10 });
const result = testConditions([{ field: 'clock', operator: 'not_equals', value: '11' }], 'all', mockStore);
expect(result).toBe(true);
});
});
describe('greater_than operator', () => {
it('should check if the given value is smaller', () => {
const mockStore = makeRuntimeStateData({ clock: 10 });
const result = testConditions([{ field: 'clock', operator: 'greater_than', value: '9' }], 'all', mockStore);
expect(result).toBe(true);
});
it('should handle values which are not numbers', () => {
const mockStore = makeRuntimeStateData({ clock: 10 });
const result = testConditions([{ field: 'clock', operator: 'greater_than', value: 'testing' }], 'all', mockStore);
expect(result).toBe(false);
});
});
describe('less_than operator', () => {
it('should check if the given value is larger', () => {
const mockStore = makeRuntimeStateData({ clock: 10 });
const result = testConditions([{ field: 'clock', operator: 'less_than', value: '11' }], 'all', mockStore);
expect(result).toBe(true);
});
it('should handle values which are not numbers', () => {
const mockStore = makeRuntimeStateData({ clock: 10 });
const result = testConditions([{ field: 'clock', operator: 'less_than', value: 'testing' }], 'all', mockStore);
expect(result).toBe(false);
});
});
describe('contains operator', () => {
it('should check if value contains given string', () => {
const result = testConditions(
[{ field: 'eventNow.title', operator: 'contains', value: 'lighting' }],
'all',
makeRuntimeStateData({ eventNow: makeOntimeEvent({ title: 'test-lighting' }) as PlayableEvent }),
);
expect(result).toBe(true);
const result2 = testConditions(
[{ field: 'eventNow.title', operator: 'contains', value: 'sound' }],
'all',
makeRuntimeStateData({ eventNow: makeOntimeEvent({ title: 'test-lighting' }) as PlayableEvent }),
);
expect(result2).toBe(false);
});
});
describe('not_contains operator', () => {
it('should check if value does not contain given string', () => {
const result = testConditions(
[{ field: 'eventNow.title', operator: 'not_contains', value: 'lighting' }],
'all',
makeRuntimeStateData({ eventNow: makeOntimeEvent({ title: 'test-lighting' }) as PlayableEvent }),
);
expect(result).toBe(false);
const result2 = testConditions(
[{ field: 'eventNow.title', operator: 'not_contains', value: 'sound' }],
'all',
makeRuntimeStateData({ eventNow: makeOntimeEvent({ title: 'test-lighting' }) as PlayableEvent }),
);
expect(result2).toBe(true);
});
});
describe('for all filter rule', () => {
it('should return true when all filters are true', () => {
const mockStore = makeRuntimeStateData({
clock: 10,
eventNow: makeOntimeEvent({
title: 'test',
custom: { av: 'av-value' },
}) as PlayableEvent,
});
const result = testConditions(
[
{ field: 'clock', operator: 'equals', value: '10' },
{ field: 'eventNow.title', operator: 'equals', value: 'test' },
{ field: 'eventNow.custom.av', operator: 'equals', value: 'av-value' },
],
'all',
mockStore,
);
expect(result).toBe(true);
});
it('should return false if any filters are false', () => {
const mockStore = makeRuntimeStateData({
clock: 10,
eventNow: makeOntimeEvent({
title: 'test',
custom: { av: 'not-av-value' },
}) as PlayableEvent,
});
const result = testConditions(
[
{ field: 'clock', operator: 'equals', value: '11' },
{ field: 'eventNow.title', operator: 'equals', value: 'test' },
{ field: 'eventNow.custom.av', operator: 'equals', value: 'av-value' },
],
'all',
mockStore,
);
expect(result).toBe(false);
});
});
describe('for any filter rule', () => {
it('should return true when all filters are true', () => {
const mockStore = makeRuntimeStateData({
clock: 10,
eventNow: makeOntimeEvent({
title: 'test',
custom: { av: 'av-value' },
}) as PlayableEvent,
});
const result = testConditions(
[
{ field: 'clock', operator: 'equals', value: '10' },
{ field: 'eventNow.title', operator: 'equals', value: 'test' },
{ field: 'eventNow.custom.av', operator: 'equals', value: 'av-value' },
],
'any',
mockStore,
);
expect(result).toBe(true);
});
it('should return true if any filters are true', () => {
const mockStore = makeRuntimeStateData({
clock: 10,
eventNow: makeOntimeEvent({
title: 'not-test',
custom: { av: 'av-value' },
}) as PlayableEvent,
});
const result = testConditions(
[
{ field: 'clock', operator: 'equals', value: '11' },
{ field: 'eventNow.title', operator: 'equals', value: 'not-test' },
{ field: 'eventNow.custom.av', operator: 'equals', value: 'av-value' },
],
'any',
mockStore,
);
expect(result).toBe(true);
});
it('should return false if all filters are false', () => {
const mockStore = makeRuntimeStateData({
clock: 10,
eventNow: makeOntimeEvent({ title: 'test' }) as PlayableEvent,
});
const result = testConditions(
[
{ field: 'clock', operator: 'equals', value: '11' },
{ field: 'eventNow.title', operator: 'equals', value: 'not-test' },
],
'any',
mockStore,
);
expect(result).toBe(false);
});
});
});