141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
import * as http from 'node:http';
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { LunatoneClient, LunatoneIntegration } from '../../ts/integrations/lunatone/index.js';
|
|
|
|
interface ILunatoneTestServer {
|
|
url: string;
|
|
controls: Array<{ path?: string; body: Record<string, unknown> }>;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
const infoPayload = {
|
|
name: 'DALI-2 Gateway',
|
|
version: 'v1.17.0/1.4.3',
|
|
tier: 'plus',
|
|
uid: 'lunatone-uid-1',
|
|
descriptor: { lines: 1 },
|
|
device: {
|
|
serial: 1234,
|
|
gtin: 9010342013577,
|
|
pcb: '9a',
|
|
articleNumber: 89453886,
|
|
articleInfo: '',
|
|
productionYear: 2024,
|
|
productionWeek: 1,
|
|
},
|
|
lines: {
|
|
'0': {
|
|
lineStatus: 'ok',
|
|
device: {
|
|
serial: 1234,
|
|
gtin: 9010342013577,
|
|
pcb: '9a',
|
|
articleNumber: 89453886,
|
|
articleInfo: '',
|
|
productionYear: 2024,
|
|
productionWeek: 1,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const devicesPayload = {
|
|
devices: [
|
|
{
|
|
id: 1,
|
|
name: 'Office Light',
|
|
type: 'default',
|
|
available: true,
|
|
address: 0,
|
|
line: 0,
|
|
features: {
|
|
switchable: { status: true },
|
|
dimmable: { status: 75 },
|
|
colorRGB: { status: { r: 1, g: 0.5, b: 0 } },
|
|
},
|
|
groups: [],
|
|
daliTypes: [],
|
|
},
|
|
],
|
|
};
|
|
|
|
const readJson = async (requestArg: http.IncomingMessage): Promise<Record<string, unknown>> => {
|
|
const chunks: Buffer[] = [];
|
|
for await (const chunk of requestArg) {
|
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
}
|
|
const body = Buffer.concat(chunks).toString('utf8');
|
|
return body ? JSON.parse(body) as Record<string, unknown> : {};
|
|
};
|
|
|
|
const startLunatoneServer = async (): Promise<ILunatoneTestServer> => {
|
|
const controls: Array<{ path?: string; body: Record<string, unknown> }> = [];
|
|
const server = http.createServer(async (requestArg, responseArg) => {
|
|
const url = new URL(requestArg.url || '/', 'http://127.0.0.1');
|
|
responseArg.setHeader('content-type', 'application/json');
|
|
if (requestArg.method === 'GET' && url.pathname === '/info') {
|
|
responseArg.end(JSON.stringify(infoPayload));
|
|
return;
|
|
}
|
|
if (requestArg.method === 'GET' && url.pathname === '/devices') {
|
|
responseArg.end(JSON.stringify(devicesPayload));
|
|
return;
|
|
}
|
|
if (requestArg.method === 'POST' && url.pathname === '/device/1/control') {
|
|
controls.push({ path: requestArg.url, body: await readJson(requestArg) });
|
|
responseArg.end(JSON.stringify({ ok: true }));
|
|
return;
|
|
}
|
|
responseArg.statusCode = 404;
|
|
responseArg.end(JSON.stringify({ error: 'not found' }));
|
|
});
|
|
await listen(server);
|
|
const address = server.address();
|
|
const port = typeof address === 'object' && address ? address.port : 0;
|
|
return {
|
|
url: `http://127.0.0.1:${port}`,
|
|
controls,
|
|
close: async () => close(server),
|
|
};
|
|
};
|
|
|
|
tap.test('reads and controls Lunatone REST lights over native HTTP', async () => {
|
|
const server = await startLunatoneServer();
|
|
try {
|
|
const client = new LunatoneClient({ url: server.url, timeoutMs: 1000, name: 'Gateway' });
|
|
const snapshot = await client.getSnapshot(true);
|
|
const runtime = await new LunatoneIntegration().setup({ url: server.url, timeoutMs: 1000, name: 'Gateway' }, {});
|
|
const entities = await runtime.entities();
|
|
const turnOff = await runtime.callService?.({ domain: 'light', service: 'turn_off', target: { entityId: 'light.gateway_device_1' } });
|
|
|
|
expect(snapshot.online).toBeTrue();
|
|
expect(snapshot.source).toEqual('http');
|
|
expect(snapshot.entities.find((entityArg) => entityArg.id === 'device_1')?.state).toBeTrue();
|
|
expect(entities.some((entityArg) => entityArg.id === 'light.gateway_device_1' && entityArg.state === true)).toBeTrue();
|
|
expect(turnOff?.success).toBeTrue();
|
|
expect(server.controls[0]?.path).toEqual('/device/1/control');
|
|
expect(server.controls[0]?.body).toEqual({ dimmable: 0 });
|
|
await runtime.destroy();
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
const listen = async (serverArg: http.Server): Promise<void> => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
serverArg.once('error', reject);
|
|
serverArg.listen(0, '127.0.0.1', () => {
|
|
serverArg.off('error', reject);
|
|
resolve();
|
|
});
|
|
});
|
|
};
|
|
|
|
const close = async (serverArg: http.Server): Promise<void> => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
serverArg.close((errorArg) => errorArg ? reject(errorArg) : resolve());
|
|
});
|
|
};
|
|
|
|
export default tap.start();
|