fix(companion): validate runtime and heartbeat loop options
This commit is contained in:
@@ -368,6 +368,20 @@
|
||||
],
|
||||
"test_status": "pnpm test:run src/companion/heartbeatLoop.test.ts src/companion/runtimeClient.test.ts src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
|
||||
},
|
||||
"companion-runtime-and-loop-option-validation": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-17",
|
||||
"updated": "2026-02-17",
|
||||
"summary": "Added constructor option validation guards for companion runtime utilities (`requestTimeoutMs > 0`, `intervalMs > 0`, and valid `maxConsecutiveFailures` bounds) with regression tests.",
|
||||
"files_modified": [
|
||||
"src/companion/runtimeClient.ts",
|
||||
"src/companion/runtimeClient.test.ts",
|
||||
"src/companion/heartbeatLoop.ts",
|
||||
"src/companion/heartbeatLoop.test.ts",
|
||||
"docs/plans/state.json"
|
||||
],
|
||||
"test_status": "pnpm test:run src/companion/runtimeClient.test.ts src/companion/heartbeatLoop.test.ts src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
|
||||
},
|
||||
"browser-tools-activation-clarity": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-17",
|
||||
|
||||
@@ -37,6 +37,16 @@ describe('CompanionHeartbeatLoop', () => {
|
||||
loop.stop();
|
||||
});
|
||||
|
||||
it('validates constructor options', () => {
|
||||
const publisher = { publishHeartbeat: vi.fn(async () => buildStatusResult()) };
|
||||
expect(() => new CompanionHeartbeatLoop(publisher, { intervalMs: 0 })).toThrow(
|
||||
'intervalMs must be a positive number',
|
||||
);
|
||||
expect(() => new CompanionHeartbeatLoop(publisher, { maxConsecutiveFailures: 0 })).toThrow(
|
||||
'maxConsecutiveFailures must be >= 1 when specified',
|
||||
);
|
||||
});
|
||||
|
||||
it('supports delayed first run when runImmediately=false', async () => {
|
||||
const publishHeartbeat = vi.fn(async () => buildStatusResult());
|
||||
const loop = new CompanionHeartbeatLoop({ publishHeartbeat }, { intervalMs: 500 });
|
||||
|
||||
@@ -31,11 +31,23 @@ export class CompanionHeartbeatLoop {
|
||||
private consecutiveFailures = 0;
|
||||
|
||||
constructor(publisher: HeartbeatPublisher, options: CompanionHeartbeatLoopOptions = {}) {
|
||||
const intervalMs = options.intervalMs ?? 30_000;
|
||||
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
||||
throw new Error('intervalMs must be a positive number');
|
||||
}
|
||||
const maxConsecutiveFailures = options.maxConsecutiveFailures ?? Number.POSITIVE_INFINITY;
|
||||
if (!Number.isFinite(maxConsecutiveFailures) && maxConsecutiveFailures !== Number.POSITIVE_INFINITY) {
|
||||
throw new Error('maxConsecutiveFailures must be a positive number or Infinity');
|
||||
}
|
||||
if (Number.isFinite(maxConsecutiveFailures) && maxConsecutiveFailures < 1) {
|
||||
throw new Error('maxConsecutiveFailures must be >= 1 when specified');
|
||||
}
|
||||
|
||||
this.publisher = publisher;
|
||||
this.intervalMs = options.intervalMs ?? 30_000;
|
||||
this.intervalMs = intervalMs;
|
||||
this.buildHeartbeat = options.buildHeartbeat;
|
||||
this.onError = options.onError;
|
||||
this.maxConsecutiveFailures = options.maxConsecutiveFailures ?? Number.POSITIVE_INFINITY;
|
||||
this.maxConsecutiveFailures = maxConsecutiveFailures;
|
||||
this.onFailureLimitReached = options.onFailureLimitReached;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,15 @@ afterAll(async () => {
|
||||
});
|
||||
|
||||
describe('CompanionRuntimeClient', () => {
|
||||
it('validates requestTimeoutMs option', () => {
|
||||
expect(() => {
|
||||
new CompanionRuntimeClient({
|
||||
url: 'ws://127.0.0.1:1',
|
||||
requestTimeoutMs: 0,
|
||||
});
|
||||
}).toThrow('requestTimeoutMs must be a positive number');
|
||||
});
|
||||
|
||||
it('dispatches gateway events to subscribed handlers and supports unsubscribe', () => {
|
||||
const client = new CompanionRuntimeClient({
|
||||
url: 'ws://127.0.0.1:1',
|
||||
|
||||
@@ -270,9 +270,13 @@ export class CompanionRuntimeClient {
|
||||
private readonly eventHandlers = new Set<CompanionEventHandler>();
|
||||
|
||||
constructor(options: CompanionRuntimeClientOptions) {
|
||||
const requestTimeoutMs = options.requestTimeoutMs ?? 15_000;
|
||||
if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) {
|
||||
throw new Error('requestTimeoutMs must be a positive number');
|
||||
}
|
||||
this.url = options.url;
|
||||
this.token = options.token;
|
||||
this.requestTimeoutMs = options.requestTimeoutMs ?? 15_000;
|
||||
this.requestTimeoutMs = requestTimeoutMs;
|
||||
this.autoConnect = options.autoConnect ?? false;
|
||||
this.websocketFactory = options.websocketFactory ?? ((url) => new WebSocket(url));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user