Files
integrations/test/doorbird/test.doorbird.client.node.ts
T

109 lines
5.5 KiB
TypeScript

import { createServer } from 'node:http';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DoorbirdClient, DoorbirdIntegration } from '../../ts/integrations/doorbird/index.js';
tap.test('reads DoorBird data and executes relay/light over native local HTTP', async () => {
const requests: Array<{ url: string; method?: string; authorization?: string }> = [];
const server = createServer((requestArg, responseArg) => {
requests.push({ url: requestArg.url || '', method: requestArg.method, authorization: requestArg.headers.authorization });
if (!requestArg.headers.authorization) {
responseArg.statusCode = 401;
responseArg.setHeader('www-authenticate', 'Basic realm="DoorBird"');
responseArg.end('auth required');
return;
}
if (requestArg.headers.authorization !== `Basic ${Buffer.from('user:secret').toString('base64')}`) {
responseArg.statusCode = 401;
responseArg.end('bad credentials');
return;
}
if (requestArg.url === '/bha-api/info.cgi') {
responseArg.setHeader('content-type', 'application/json');
responseArg.end(JSON.stringify({ BHA: { RETURNCODE: '1', VERSION: [{ FIRMWARE: '000122', BUILD_NUMBER: '42', 'DEVICE-TYPE': 'D1101V', PRIMARY_MAC_ADDR: '1CCAE3AABBCC', RELAYS: ['1', '2'] }] } }));
return;
}
if (requestArg.url === '/bha-api/monitor.cgi?check=doorbell') {
responseArg.setHeader('content-type', 'text/plain');
responseArg.end('doorbell=1');
return;
}
if (requestArg.url === '/bha-api/monitor.cgi?check=motionsensor') {
responseArg.setHeader('content-type', 'text/plain');
responseArg.end('motionsensor=0');
return;
}
if (requestArg.url === '/bha-api/favorites.cgi') {
responseArg.setHeader('content-type', 'application/json');
responseArg.end(JSON.stringify({ http: { '1': { title: 'Home Assistant (front_door_doorbell)', value: 'http://ha/api/doorbird/front?token=abc' } } }));
return;
}
if (requestArg.url === '/bha-api/schedule.cgi') {
responseArg.setHeader('content-type', 'application/json');
responseArg.end(JSON.stringify([{ input: 'doorbell', param: '', output: [{ enabled: '1', event: 'http', param: '1', schedule: { weekdays: [{ from: '104400', to: '104399' }] } }] }]));
return;
}
if (requestArg.url === '/bha-api/image.cgi') {
responseArg.setHeader('content-type', 'image/jpeg');
responseArg.end(Buffer.from([1, 2, 3, 4]));
return;
}
if (requestArg.url === '/bha-api/open-door.cgi?r=2') {
responseArg.setHeader('content-type', 'application/json');
responseArg.end(JSON.stringify({ BHA: { RETURNCODE: '1' } }));
return;
}
if (requestArg.url === '/bha-api/light-on.cgi') {
responseArg.setHeader('content-type', 'application/json');
responseArg.end(JSON.stringify({ BHA: { RETURNCODE: '1' } }));
return;
}
responseArg.statusCode = 404;
responseArg.end('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 supported = await DoorbirdClient.verifySupportedDevice('127.0.0.1', port, 1000);
const client = new DoorbirdClient({ host: '127.0.0.1', port, username: 'user', password: 'secret', timeoutMs: 1000, snapshotTimeoutMs: 1000, name: 'Front Door' });
const snapshot = await client.getSnapshot();
const image = await client.getSnapshotImage('live');
const relay = await client.execute({ action: 'energize_relay', relay: '2' });
const light = await client.execute({ action: 'turn_light_on' });
expect(supported).toBeTrue();
expect(snapshot.online).toBeTrue();
expect(snapshot.source).toEqual('http');
expect(snapshot.deviceInfo.macAddress).toEqual('1ccae3aabbcc');
expect(snapshot.status.doorbell).toBeTrue();
expect(snapshot.status.motion).toBeFalse();
expect(snapshot.favorites?.http?.['1'].title).toContain('Home Assistant');
expect(snapshot.schedule?.[0].input).toEqual('doorbell');
expect(image.contentType).toEqual('image/jpeg');
expect(Buffer.from(image.data).toString('hex')).toEqual('01020304');
expect(relay).toEqual({ ok: true, relay: '2' });
expect(light).toEqual({ ok: true });
expect(requests.some((requestArg) => requestArg.url === '/bha-api/monitor.cgi?check=doorbell' && !requestArg.authorization)).toBeTrue();
expect(requests.some((requestArg) => requestArg.url === '/bha-api/info.cgi' && requestArg.authorization?.startsWith('Basic '))).toBeTrue();
await client.destroy();
} finally {
await new Promise<void>((resolve, reject) => server.close((errorArg) => errorArg ? reject(errorArg) : resolve()));
}
});
tap.test('runtime status/snapshot services use snapshots without claiming command success', async () => {
const runtime = await new DoorbirdIntegration().setup({ rawInfo: { PRIMARY_MAC_ADDR: '1CCAE3AABBCC', RELAYS: ['1'] }, status: { doorbell: false, motion: false }, name: 'Manual DoorBird' }, {});
const status = await runtime.callService?.({ domain: 'doorbird', service: 'status', target: {} });
const command = await runtime.callService?.({ domain: 'doorbird', service: 'turn_light_on', target: {} });
expect(status?.success).toBeTrue();
expect((status?.data as { doorbell?: boolean }).doorbell).toBeFalse();
expect(command?.success).toBeFalse();
expect(command?.error).toContain('config.host');
await runtime.destroy();
});
export default tap.start();