Files
integrations/test/elgato/test.elgato.client_runtime.node.ts
T

116 lines
5.5 KiB
TypeScript

import { createServer } from 'node:http';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { ElgatoClient, ElgatoIntegration, ElgatoMapper, type IElgatoCommandRequest } from '../../ts/integrations/elgato/index.js';
tap.test('reads Elgato snapshots and executes light commands over native local HTTP', async () => {
const requests: Array<{ url: string; method?: string; body: string }> = [];
const server = createServer((requestArg, responseArg) => {
const chunks: Buffer[] = [];
requestArg.on('data', (chunkArg) => chunks.push(Buffer.from(chunkArg)));
requestArg.on('end', () => {
const body = Buffer.concat(chunks).toString('utf8');
requests.push({ url: requestArg.url || '', method: requestArg.method, body });
responseArg.setHeader('content-type', 'application/json');
if (requestArg.url === '/elgato/accessory-info' && requestArg.method === 'GET') {
responseArg.end(JSON.stringify({ productName: 'Elgato Key Light', serialNumber: 'ELG123', displayName: 'Desk Key Light', firmwareVersion: '1.0.3', firmwareBuildNumber: 42, hardwareBoardType: 7, macAddress: 'AABBCCDDEEFF', features: ['lights'] }));
return;
}
if (requestArg.url === '/elgato/lights/settings' && requestArg.method === 'GET') {
responseArg.end(JSON.stringify({ colorChangeDurationMs: 100, powerOnBehavior: 1, powerOnBrightness: 40, switchOffDurationMs: 300, switchOnDurationMs: 300 }));
return;
}
if (requestArg.url === '/elgato/lights' && requestArg.method === 'GET') {
responseArg.end(JSON.stringify({ numberOfLights: 1, lights: [{ on: 1, brightness: 35, temperature: 200 }] }));
return;
}
if (requestArg.url === '/elgato/lights' && requestArg.method === 'PUT') {
responseArg.end(JSON.stringify({ ok: true }));
return;
}
if (requestArg.url === '/elgato/identify' && requestArg.method === 'POST') {
responseArg.end(JSON.stringify({ ok: true }));
return;
}
responseArg.statusCode = 404;
responseArg.end(JSON.stringify({ error: 'not found' }));
});
});
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
try {
const address = server.address();
const port = typeof address === 'object' && address ? address.port : 0;
const client = new ElgatoClient({ host: '127.0.0.1', port, timeoutMs: 1000 });
const snapshot = await client.getSnapshot();
const light = await client.setLight({ on: true, brightness: 50, colorTemperatureKelvin: 5000 });
const identify = await client.identify();
expect(snapshot.online).toBeTrue();
expect(snapshot.source).toEqual('http');
expect(snapshot.device.serialNumber).toEqual('ELG123');
expect(snapshot.device.macAddress).toEqual('aa:bb:cc:dd:ee:ff');
expect(snapshot.light.colorTemperatureKelvin).toEqual(5000);
expect(light.ok).toBeTrue();
expect(identify.ok).toBeTrue();
const putBody = JSON.parse(requests.find((requestArg) => requestArg.url === '/elgato/lights' && requestArg.method === 'PUT')!.body) as { lights: Array<Record<string, unknown>> };
expect(putBody.lights[0].on).toEqual(1);
expect(putBody.lights[0].brightness).toEqual(50);
expect(putBody.lights[0].temperature).toEqual(200);
expect(requests.some((requestArg) => requestArg.url === '/elgato/identify' && requestArg.method === 'POST')).toBeTrue();
await client.destroy();
} finally {
await new Promise<void>((resolve, reject) => server.close((errorArg) => errorArg ? reject(errorArg) : resolve()));
}
});
tap.test('does not report live light command success without HTTP transport or executor', async () => {
const snapshot = ElgatoMapper.toSnapshot({
config: { name: 'Static Key Light' },
rawData: {
info: { productName: 'Elgato Key Light', serialNumber: 'ELGSTATIC', displayName: 'Static Key Light' },
settings: { colorChangeDurationMs: 100, powerOnBehavior: 1, powerOnBrightness: 40, switchOffDurationMs: 300, switchOnDurationMs: 300 },
state: { on: 1, brightness: 40, temperature: 200 },
},
online: true,
source: 'manual',
});
const runtime = await new ElgatoIntegration().setup({ snapshot }, {});
const result = await runtime.callService?.({ domain: 'light', service: 'turn_off', target: { entityId: 'light.static_key_light' }, data: {} });
expect(result?.success).toBeFalse();
expect(result?.error).toContain('Static snapshots/manual data are read-only');
await runtime.destroy();
});
tap.test('models Elgato commands through an injected executor', async () => {
const commands: IElgatoCommandRequest[] = [];
const snapshot = ElgatoMapper.toSnapshot({
config: { name: 'Executor Key Light' },
rawData: {
info: { productName: 'Elgato Key Light', serialNumber: 'ELGEXEC', displayName: 'Executor Key Light' },
settings: { colorChangeDurationMs: 100, powerOnBehavior: 1, powerOnBrightness: 40, switchOffDurationMs: 300, switchOnDurationMs: 300 },
state: { on: 1, brightness: 40, temperature: 200 },
},
online: true,
source: 'manual',
});
const runtime = await new ElgatoIntegration().setup({
snapshot,
commandExecutor: {
execute: async (requestArg) => {
commands.push(requestArg);
return { ok: true, accepted: requestArg.action };
},
},
}, {});
const result = await runtime.callService?.({ domain: 'button', service: 'press', target: { entityId: 'button.executor_key_light_identify' } });
expect(result?.success).toBeTrue();
expect(commands[0].action).toEqual('identify');
await runtime.destroy();
});
export default tap.start();