116 lines
5.5 KiB
TypeScript
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();
|