mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-15 11:03:04 +00:00
fix(automation): validate template-aware output targets
This commit is contained in:
committed by
Carlos Valente
parent
1175b3c641
commit
79c21daf73
@@ -0,0 +1,54 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { Automation, ErrorResponse } from 'ontime-types';
|
||||
|
||||
import { editAutomation, postAutomation } from '../automation.controller.js';
|
||||
import * as automationDao from '../automation.dao.js';
|
||||
|
||||
vi.mock('../automation.dao.js', () => ({
|
||||
addAutomation: vi.fn(),
|
||||
editAutomation: vi.fn(),
|
||||
}));
|
||||
|
||||
function makeResponse() {
|
||||
return {
|
||||
send: vi.fn(),
|
||||
status: vi.fn().mockReturnThis(),
|
||||
} as unknown as Response<Automation | ErrorResponse>;
|
||||
}
|
||||
|
||||
const requestBody = {
|
||||
title: 'OSC definition',
|
||||
filterRule: 'all',
|
||||
filters: [],
|
||||
outputs: [{ type: 'osc', targetIP: ' 127.0.0.1 ', targetPort: 53000, address: '/test', args: '' }],
|
||||
};
|
||||
|
||||
describe('automation controllers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(automationDao.addAutomation).mockImplementation(async (automation) => ({ id: 'new-id', ...automation }));
|
||||
vi.mocked(automationDao.editAutomation).mockImplementation(async (id, automation) => ({ id, ...automation }));
|
||||
});
|
||||
|
||||
it('persists normalized outputs when creating an automation', async () => {
|
||||
const request = { body: requestBody } as Request;
|
||||
|
||||
await postAutomation(request, makeResponse());
|
||||
|
||||
expect(automationDao.addAutomation).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ outputs: [expect.objectContaining({ targetIP: '127.0.0.1' })] }),
|
||||
);
|
||||
});
|
||||
|
||||
it('persists normalized outputs when editing an automation', async () => {
|
||||
const request = { body: requestBody, params: { id: 'automation-id' } } as unknown as Request;
|
||||
|
||||
await editAutomation(request, makeResponse());
|
||||
|
||||
expect(automationDao.editAutomation).toHaveBeenCalledWith(
|
||||
'automation-id',
|
||||
expect.objectContaining({ outputs: [expect.objectContaining({ targetIP: '127.0.0.1' })] }),
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -5,13 +5,13 @@ describe('parseOutput', () => {
|
||||
it('parses a valid payload', () => {
|
||||
const payload = {
|
||||
type: 'osc',
|
||||
targetIP: 'localhost',
|
||||
targetIP: ' qlab ',
|
||||
targetPort: 1234,
|
||||
address: '/test',
|
||||
args: 'test',
|
||||
};
|
||||
const result = parseOutput(payload);
|
||||
expect(result).toStrictEqual(payload);
|
||||
expect(result).toStrictEqual({ ...payload, targetIP: 'qlab' });
|
||||
});
|
||||
|
||||
it('throws on a invalid payload', () => {
|
||||
@@ -24,6 +24,33 @@ describe('parseOutput', () => {
|
||||
};
|
||||
expect(() => parseOutput(payload)).toThrow('Unexpected payload type:');
|
||||
});
|
||||
|
||||
it('rejects invalid targets and ports', () => {
|
||||
expect(() =>
|
||||
parseOutput({ type: 'osc', targetIP: 'not a host', targetPort: 53000, address: '/test', args: '' }),
|
||||
).toThrow('Invalid OSC target');
|
||||
expect(() =>
|
||||
parseOutput({ type: 'osc', targetIP: '127.0.0.1', targetPort: 70000, address: '/test', args: '' }),
|
||||
).toThrow('Invalid OSC port');
|
||||
});
|
||||
|
||||
it('allows runtime templates in a target hostname', () => {
|
||||
expect(
|
||||
parseOutput({
|
||||
type: 'osc',
|
||||
targetIP: '{{eventNow.custom.oscTarget}}',
|
||||
targetPort: 53000,
|
||||
address: '/test',
|
||||
args: '',
|
||||
}),
|
||||
).toMatchObject({ targetIP: '{{eventNow.custom.oscTarget}}' });
|
||||
});
|
||||
|
||||
it('rejects IPv6 targets', () => {
|
||||
expect(() =>
|
||||
parseOutput({ type: 'osc', targetIP: '::1', targetPort: 53000, address: '/test', args: '' }),
|
||||
).toThrow('Invalid OSC target');
|
||||
});
|
||||
});
|
||||
describe('handles HTTP outputs', () => {
|
||||
it('parses a valid payload', () => {
|
||||
@@ -41,6 +68,25 @@ describe('parseOutput', () => {
|
||||
};
|
||||
expect(() => parseOutput(payload)).toThrow('Unexpected payload type:');
|
||||
});
|
||||
|
||||
it('rejects malformed and unsupported URLs', () => {
|
||||
expect(() => parseOutput({ type: 'http', url: 'localhost:3000/hook' })).toThrow('Invalid HTTP URL');
|
||||
expect(() => parseOutput({ type: 'http', url: 'ftp://example.com/hook' })).toThrow('Invalid HTTP URL');
|
||||
});
|
||||
|
||||
it('allows runtime templates in HTTP URLs', () => {
|
||||
expect(parseOutput({ type: 'http', url: 'http://{{eventNow.customFields.webhookHost}}/hook' })).toEqual({
|
||||
type: 'http',
|
||||
url: 'http://{{eventNow.customFields.webhookHost}}/hook',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a runtime template for a complete HTTP URL', () => {
|
||||
expect(parseOutput({ type: 'http', url: '{{eventNow.customFields.webhookUrl}}' })).toEqual({
|
||||
type: 'http',
|
||||
url: '{{eventNow.customFields.webhookUrl}}',
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('handles Ontime outputs', () => {
|
||||
it('parses a valid payload', () => {
|
||||
|
||||
Reference in New Issue
Block a user