Files
integrations/test/lookin/test.lookin.node.ts
T

140 lines
6.8 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import { HomeAssistantLookinIntegration, LookinClient, LookinConfigFlow, LookinIntegration, LookinMapper, createLookinDiscoveryDescriptor, lookinProfile, type ILookinSnapshot, type TLookinRawData } from '../../ts/integrations/lookin/index.js';
const rawData: TLookinRawData = {
device: {
id: 'lookin-device-1',
name: "LOOKin Device",
manufacturer: "LOOKin",
model: "LOOKin local integration",
serialNumber: 'lookin-serial-1',
},
entities: [
{ id: 'status', name: 'Status', platform: "climate", state: true, attributes: { domain: "lookin" } },
],
online: true,
updatedAt: '2026-01-01T00:00:00.000Z',
source: 'manual',
};
tap.test('matches manual LOOKin candidates and creates config flow output', async () => {
const descriptor = createLookinDiscoveryDescriptor();
const matcher = descriptor.getMatchers().find((matcherArg) => matcherArg.id === 'lookin-manual-match');
const result = await matcher!.matches({ source: 'manual', id: 'lookin-device-1', name: "LOOKin Device", metadata: { rawData } }, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.integrationDomain).toEqual("lookin");
const validation = await descriptor.getValidators()[0].validate(result.candidate!, {});
expect(validation.matched).toBeTrue();
const done = await (await new LookinConfigFlow().start(result.candidate!, {})).submit!({});
expect(done.kind).toEqual('done');
expect(done.config?.uniqueId).toEqual('lookin-device-1');
expect(done.config?.rawData).toEqual(rawData);
});
tap.test('maps LOOKin raw snapshots to runtime devices and entities', async () => {
const client = new LookinClient({ name: "LOOKin Runtime", rawData });
const snapshot = await client.getSnapshot();
const mappedSnapshot = LookinMapper.toSnapshotFromRaw({ name: "LOOKin Runtime" }, rawData);
const devices = LookinMapper.toDevices(mappedSnapshot);
const entities = LookinMapper.toEntities(mappedSnapshot);
expect(snapshot.online).toBeTrue();
expect(mappedSnapshot.source).toEqual('manual');
expect(devices[0].integrationDomain).toEqual("lookin");
expect(devices[0].manufacturer).toEqual("LOOKin");
expect(entities.some((entityArg) => entityArg.integrationDomain === "lookin" && entityArg.platform === "climate")).toBeTrue();
});
tap.test('polls LOOKin local HTTP endpoints and sends native IR commands', async () => {
const originalFetch = globalThis.fetch;
const requests: string[] = [];
globalThis.fetch = (async (inputArg: RequestInfo | URL) => {
const url = typeof inputArg === 'string' ? inputArg : inputArg instanceof URL ? inputArg.toString() : inputArg.url;
requests.push(url);
const parsed = new URL(url);
if (parsed.pathname === '/device') {
return jsonResponse({ Type: 'Remote', MRDC: '02FF', Status: 'ok', ID: '98f33093', Name: 'Living LOOKin', Firmware: '1.2.3' });
}
if (parsed.pathname === '/data') {
return jsonResponse([
{ Type: '01', UUID: 'TV01' },
{ Type: '03', UUID: 'LT01' },
{ Type: 'EF', UUID: 'AC01' },
]);
}
if (parsed.pathname === '/data/TV01') {
return jsonResponse({ Type: '01', UUID: 'TV01', Name: 'TV', Updated: '1', Status: '10F0', Functions: [{ Name: 'poweron', Type: 'button' }, { Name: 'poweroff', Type: 'button' }, { Name: 'volup', Type: 'button' }] });
}
if (parsed.pathname === '/data/LT01') {
return jsonResponse({ Type: '03', UUID: 'LT01', Name: 'Lamp', Updated: '1', Status: '1000', Functions: [{ Name: 'power', Type: 'button' }] });
}
if (parsed.pathname === '/data/AC01') {
return jsonResponse({ Type: 'EF', UUID: 'AC01', Name: 'AC', Updated: '1', Status: '1821', Extra: '', Functions: [{ Name: 'power', Type: 'button' }] });
}
if (parsed.pathname === '/sensors/meteo') {
return jsonResponse({ Humidity: '41.5', Pressure: '1000', Temperature: '22.3', Updated: '1' });
}
if (parsed.pathname === '/commands/ir/localremote/TV0106FF') {
return new Response(null, { status: 204 });
}
return new Response('not found', { status: 404 });
}) as typeof fetch;
try {
const client = new LookinClient({ host: 'lookin.local', timeoutMs: 1000 });
const snapshot = await client.getSnapshot(true);
expect(snapshot.online).toBeTrue();
expect(snapshot.device.id).toEqual('98F33093');
expect(snapshot.entities.some((entityArg) => entityArg.platform === 'media_player' && entityArg.id === 'TV01')).toBeTrue();
expect(snapshot.entities.some((entityArg) => entityArg.platform === 'light' && entityArg.id === 'LT01')).toBeTrue();
expect(snapshot.entities.some((entityArg) => entityArg.platform === 'climate' && entityArg.id === 'AC01')).toBeTrue();
expect(snapshot.entities.find((entityArg) => entityArg.id === 'temperature')?.state).toEqual(22.3);
const command = await client.execute({ domain: 'media_player', service: 'volume_up', target: {}, data: { uuid: 'TV01' } });
expect(command.success).toBeTrue();
expect(requests).toContain('http://lookin.local/device');
expect(requests).toContain('http://lookin.local/data/TV01');
expect(requests).toContain('http://lookin.local/commands/ir/localremote/TV0106FF');
expect(JSON.stringify(lookinProfile.metadata.localApi)).toContain('/device');
} finally {
globalThis.fetch = originalFetch;
}
});
tap.test('exposes LOOKin runtime, HA alias, and unsupported control without executor', async () => {
const integration = new LookinIntegration();
const alias = new HomeAssistantLookinIntegration();
expect(alias instanceof LookinIntegration).toBeTrue();
expect(alias.domain).toEqual("lookin");
expect(integration.status).toEqual("control-runtime");
expect(lookinProfile.metadata.configFlow).toEqual(true);
expect(lookinProfile.metadata.requirements).toEqual([
"aiolookin==1.0.0",
]);
const runtime = await integration.setup({ name: "LOOKin Runtime", rawData }, {});
const statusResult = await runtime.callService!({ domain: "lookin", service: 'status', target: {} });
const refresh = await runtime.callService!({ domain: "lookin", service: 'refresh', target: {} });
const snapshot = statusResult.data as ILookinSnapshot;
expect(statusResult.success).toBeTrue();
expect(refresh.success).toBeTrue();
expect(snapshot.online).toBeTrue();
expect((await runtime.devices())[0].name).toEqual("LOOKin Device");
const command = await runtime.callService!({ domain: "lookin", service: lookinProfile.controlServices?.[0] || 'turn_on', target: {} });
expect(command.success).toBeFalse();
expect(command.error!).toContain('requires an injected client.execute() or commandExecutor');
await runtime.destroy();
});
const jsonResponse = (valueArg: unknown): Response => {
return new Response(JSON.stringify(valueArg), { headers: { 'content-type': 'application/json' } });
};
export default tap.start();