Add native media and network integrations

This commit is contained in:
2026-05-05 16:20:10 +00:00
parent 1eebd71e7d
commit 489d9d5243
63 changed files with 8605 additions and 195 deletions
@@ -0,0 +1,52 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createAndroidtvDiscoveryDescriptor } from '../../ts/integrations/androidtv/index.js';
tap.test('matches Android TV mDNS setup hints', async () => {
const descriptor = createAndroidtvDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
type: '_androidtvremote2._tcp.local.',
name: 'Living Room TV._androidtvremote2._tcp.local.',
host: 'living-room-tv.local',
port: 6466,
txt: {
id: 'androidtv-123',
fn: 'Living Room TV',
md: 'Android TV',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('living-room-tv.local');
expect(result.candidate?.port).toEqual(5555);
expect(result.normalizedDeviceId).toEqual('androidtv-123');
});
tap.test('matches manual Android TV host entries', async () => {
const descriptor = createAndroidtvDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[1];
const result = await matcher.matches({
host: '192.168.1.55',
deviceName: 'Den Fire TV',
manufacturer: 'Amazon',
model: 'Fire TV Stick',
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.55');
expect(result.candidate?.metadata?.deviceClass).toEqual('firetv');
});
tap.test('validates ADB host candidates', async () => {
const descriptor = createAndroidtvDiscoveryDescriptor();
const validator = descriptor.getValidators()[0];
const result = await validator.validate({
source: 'custom',
integrationDomain: 'androidtv',
host: '192.168.1.56',
port: 5555,
model: 'Android TV',
}, {});
expect(result.matched).toBeTrue();
expect(result.normalizedDeviceId).toEqual('192.168.1.56');
});
export default tap.start();
@@ -0,0 +1,40 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { AndroidtvMapper } from '../../ts/integrations/androidtv/index.js';
const snapshot = {
deviceInfo: {
id: 'shield-tv-123',
name: 'Living Room Shield',
manufacturer: 'NVIDIA',
model: 'SHIELD Android TV',
deviceClass: 'androidtv' as const,
host: '192.168.1.57',
port: 5555,
},
state: {
rawState: 'playing',
available: true,
currentAppId: 'com.netflix.ninja',
runningAppIds: ['com.netflix.ninja', 'org.xbmc.kodi'],
volumeLevel: 0.42,
isVolumeMuted: false,
hdmiInput: 'HW1',
},
apps: [
{ id: 'com.netflix.ninja' },
{ id: 'org.xbmc.kodi', name: 'Kodi' },
],
};
tap.test('maps Android TV snapshots to media devices and entities', async () => {
const devices = AndroidtvMapper.toDevices(snapshot);
const entities = AndroidtvMapper.toEntities(snapshot);
expect(devices[0].id).toEqual('androidtv.device.shield_tv_123');
expect(devices[0].state.some((stateArg) => stateArg.featureId === 'volume' && stateArg.value === 42)).toBeTrue();
expect(entities[0].platform).toEqual('media_player');
expect(entities[0].state).toEqual('playing');
expect(entities[0].attributes?.source).toEqual('Netflix');
expect((entities[0].attributes?.sourceList as string[]).includes('Kodi')).toBeTrue();
});
export default tap.start();
@@ -0,0 +1,54 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createDenonavrDiscoveryDescriptor } from '../../ts/integrations/denonavr/index.js';
tap.test('matches Denon AVR SSDP records', async () => {
const descriptor = createDenonavrDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
st: 'urn:schemas-upnp-org:device:MediaRenderer:1',
usn: 'uuid:denon-avr-123::urn:schemas-upnp-org:device:MediaRenderer:1',
location: 'http://192.168.1.50:8080/description.xml',
upnp: {
manufacturer: 'Denon',
modelName: 'AVR-X1700H',
serialNumber: 'ABC12345',
friendlyName: 'Living Room AVR',
deviceType: 'urn:schemas-upnp-org:device:MediaRenderer:1',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.50');
expect(result.candidate?.manufacturer).toEqual('Denon');
expect(result.normalizedDeviceId).toEqual('AVR-X1700H-ABC12345');
});
tap.test('rejects HEOS speaker models', async () => {
const descriptor = createDenonavrDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
st: 'urn:schemas-upnp-org:device:MediaRenderer:1',
location: 'http://192.168.1.51:8080/description.xml',
upnp: {
manufacturer: 'Denon',
modelName: 'HEOS 5',
serialNumber: 'HEOS123',
},
}, {});
expect(result.matched).toBeFalse();
});
tap.test('validates manual Marantz candidates', async () => {
const descriptor = createDenonavrDiscoveryDescriptor();
const validator = descriptor.getValidators()[0];
const result = await validator.validate({
source: 'manual',
integrationDomain: 'denonavr',
host: '192.168.1.52',
manufacturer: 'Marantz',
model: 'SR6015',
}, {});
expect(result.matched).toBeTrue();
expect(result.confidence).toEqual('high');
});
export default tap.start();
@@ -0,0 +1,69 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DenonavrMapper, type IDenonavrSnapshot } from '../../ts/integrations/denonavr/index.js';
const snapshot: IDenonavrSnapshot = {
receiverInfo: {
host: '192.168.1.50',
port: 80,
name: 'Living Room AVR',
manufacturer: 'Denon',
modelName: 'AVR-X1700H',
serialNumber: 'ABC12345',
receiverType: 'avr-x',
},
zones: [{
zone: 'Main',
name: 'Main Zone',
power: 'ON',
state: 'playing',
volumeDb: -35,
muted: false,
source: 'Media Server',
sourceList: ['TV', 'Media Server', 'Bluetooth'],
soundMode: 'DOLBY DIGITAL',
soundModeRaw: 'DOLBY AUDIO - DOLBY DIGITAL',
dynamicEq: true,
ecoMode: 'Auto',
media: {
title: 'Track One',
artist: 'Artist One',
album: 'Album One',
contentType: 'music',
},
available: true,
}, {
zone: 'Zone2',
name: 'Patio',
power: 'STANDBY',
state: 'off',
volumeLevel: 0.25,
muted: true,
source: 'Tuner',
available: true,
}],
};
tap.test('maps Denon AVR snapshots to canonical devices', async () => {
const devices = DenonavrMapper.toDevices(snapshot);
expect(devices[0].id).toEqual('denonavr.receiver.abc12345');
expect(devices[0].manufacturer).toEqual('Denon');
expect(devices[0].state.some((stateArg) => stateArg.featureId === 'main_source' && stateArg.value === 'Media Server')).toBeTrue();
expect(devices[0].state.some((stateArg) => stateArg.featureId === 'zone2_power' && stateArg.value === 'off')).toBeTrue();
});
tap.test('maps Denon AVR zones to media, sensor, and switch entities', async () => {
const entities = DenonavrMapper.toEntities(snapshot);
const media = entities.find((entityArg) => entityArg.id === 'media_player.living_room_avr');
const source = entities.find((entityArg) => entityArg.id === 'sensor.living_room_avr_source');
const mute = entities.find((entityArg) => entityArg.id === 'switch.living_room_avr_zone2_mute');
const dynamicEq = entities.find((entityArg) => entityArg.id === 'switch.living_room_avr_dynamic_eq');
expect(media?.platform).toEqual('media_player');
expect(media?.state).toEqual('playing');
expect(media?.attributes?.volumeLevel).toEqual(0.45);
expect(source?.state).toEqual('Media Server');
expect(mute?.state).toEqual(true);
expect(dynamicEq?.state).toEqual(true);
});
export default tap.start();
+48
View File
@@ -0,0 +1,48 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createKodiDiscoveryDescriptor } from '../../ts/integrations/kodi/index.js';
tap.test('matches Kodi JSON-RPC mDNS records', async () => {
const descriptor = createKodiDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
type: '_xbmc-jsonrpc-h._tcp.local.',
name: 'Living Room Kodi._xbmc-jsonrpc-h._tcp.local.',
host: 'living-room-kodi.local',
port: 8080,
txt: {
uuid: 'kodi-uuid-123',
version: '21.0',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('living-room-kodi.local');
expect(result.candidate?.port).toEqual(8080);
expect(result.normalizedDeviceId).toEqual('kodi-uuid-123');
});
tap.test('matches manual Kodi host entries', async () => {
const descriptor = createKodiDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[1];
const result = await matcher.matches({
host: '192.168.1.70',
name: 'Living Room Kodi',
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.70');
expect(result.candidate?.port).toEqual(8080);
});
tap.test('validates Kodi candidates', async () => {
const descriptor = createKodiDiscoveryDescriptor();
const validator = descriptor.getValidators()[0];
const result = await validator.validate({
source: 'mdns',
integrationDomain: 'kodi',
host: '192.168.1.71',
port: 8080,
}, {});
expect(result.matched).toBeTrue();
expect(result.confidence).toEqual('high');
});
export default tap.start();
+58
View File
@@ -0,0 +1,58 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { KodiMapper, type IKodiSnapshot } from '../../ts/integrations/kodi/index.js';
const snapshot: IKodiSnapshot = {
deviceInfo: {
uuid: 'kodi-uuid-123',
name: 'Living Room Kodi',
host: '192.168.1.70',
port: 8080,
manufacturer: 'Kodi',
version: '21.0',
},
application: {
name: 'Kodi',
volume: 42,
muted: false,
version: { major: 21, minor: 0 },
},
players: [{ playerid: 1, type: 'video', playertype: 'internal' }],
player: { playerid: 1, type: 'video', playertype: 'internal' },
playerProperties: {
speed: 1,
time: { hours: 0, minutes: 3, seconds: 4 },
totaltime: { hours: 1, minutes: 2, seconds: 3 },
live: false,
},
item: {
id: 77,
type: 'movie',
title: 'The Test Movie',
file: 'smb://media/test.mkv',
thumbnail: 'image://poster.jpg/',
streamdetails: { video: [{ hdrtype: 'hdr10' }] },
},
online: true,
};
tap.test('maps Kodi JSON-RPC snapshots to media devices', async () => {
const devices = KodiMapper.toDevices(snapshot);
expect(devices[0].id).toEqual('kodi.device.kodi_uuid_123');
expect(devices[0].protocol).toEqual('http');
expect(devices[0].state.some((stateArg) => stateArg.featureId === 'volume' && stateArg.value === 42)).toBeTrue();
expect(devices[0].state.some((stateArg) => stateArg.featureId === 'current_title' && stateArg.value === 'The Test Movie')).toBeTrue();
});
tap.test('maps Kodi JSON-RPC snapshots to media player entities', async () => {
const entities = KodiMapper.toEntities(snapshot);
expect(entities[0].id).toEqual('media_player.living_room_kodi');
expect(entities[0].platform).toEqual('media_player');
expect(entities[0].state).toEqual('playing');
expect(entities[0].attributes?.volumeLevel).toEqual(0.42);
expect(entities[0].attributes?.mediaContentType).toEqual('movie');
expect(entities[0].attributes?.mediaDuration).toEqual(3723);
expect(entities[0].attributes?.mediaPosition).toEqual(184);
expect(entities[0].attributes?.dynamicRange).toEqual('hdr10');
});
export default tap.start();
@@ -0,0 +1,58 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createSamsungtvDiscoveryDescriptor } from '../../ts/integrations/samsungtv/index.js';
tap.test('matches Samsung TV SSDP MainTVAgent records', async () => {
const descriptor = createSamsungtvDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
st: 'urn:samsung.com:service:MainTVAgent2:1',
usn: 'uuid:tv-udn-123::urn:samsung.com:service:MainTVAgent2:1',
location: 'http://192.168.1.55:8001/api/v2/',
headers: {
manufacturer: 'Samsung Electronics',
modelName: 'QN90A',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.55');
expect(result.normalizedDeviceId).toEqual('tv-udn-123');
expect(result.candidate?.metadata?.ssdpMainTvAgentLocation).toEqual('http://192.168.1.55:8001/api/v2/');
});
tap.test('matches Samsung TV mDNS AirPlay records', async () => {
const descriptor = createSamsungtvDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[1];
const result = await matcher.matches({
type: '_airplay._tcp.local.',
name: 'Living Room TV',
host: 'living-room-tv.local',
port: 7000,
properties: {
manufacturer: 'Samsung Electronics',
deviceid: 'AA:BB:CC:DD:EE:FF',
model: 'Tizen TV',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('living-room-tv.local');
expect(result.candidate?.port).toEqual(8001);
expect(result.normalizedDeviceId).toEqual('AA:BB:CC:DD:EE:FF');
});
tap.test('validates Samsung TV candidates', async () => {
const descriptor = createSamsungtvDiscoveryDescriptor();
const validator = descriptor.getValidators()[0];
const result = await validator.validate({
source: 'manual',
integrationDomain: 'samsungtv',
host: '192.168.1.55',
manufacturer: 'Samsung',
}, {});
expect(result.matched).toBeTrue();
expect(result.confidence).toEqual('high');
});
export default tap.start();
@@ -0,0 +1,39 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { SamsungtvMapper } from '../../ts/integrations/samsungtv/index.js';
const snapshot = {
deviceInfo: {
id: 'tv-udn-123',
device: {
type: 'Samsung SmartTV',
name: '[TV] Living Room',
modelName: 'QN90A',
wifiMac: 'AA:BB:CC:DD:EE:FF',
PowerState: 'on',
},
},
state: {
playback: 'playing' as const,
volumeLevel: 35,
muted: false,
},
apps: [
{ id: '11101200001', name: 'Netflix' },
{ id: '3201512006785', name: 'Prime Video' },
],
activeApp: { id: '11101200001', name: 'Netflix' },
};
tap.test('maps Samsung TV snapshots to media devices and entities', async () => {
const devices = SamsungtvMapper.toDevices(snapshot);
const entities = SamsungtvMapper.toEntities(snapshot);
expect(devices[0].id).toEqual('samsungtv.device.tv_udn_123');
expect(devices[0].state.some((stateArg) => stateArg.featureId === 'source' && stateArg.value === 'Netflix')).toBeTrue();
expect(entities[0].id).toEqual('media_player.living_room');
expect(entities[0].platform).toEqual('media_player');
expect(entities[0].state).toEqual('playing');
expect((entities[0].attributes?.sourceList as string[]).includes('Netflix')).toBeTrue();
});
export default tap.start();
+59
View File
@@ -0,0 +1,59 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createTplinkDiscoveryDescriptor } from '../../ts/integrations/tplink/index.js';
tap.test('matches TP-Link Kasa/Tapo mDNS records', async () => {
const descriptor = createTplinkDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
type: '_tplink._tcp.local.',
name: 'Living Plug._tplink._tcp.local.',
host: 'living-plug.local',
port: 80,
txt: {
model: 'KP125M',
mac: 'F0-A7-31-00-11-22',
alias: 'Living Plug',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.integrationDomain).toEqual('tplink');
expect(result.normalizedDeviceId).toEqual('f0:a7:31:00:11:22');
});
tap.test('matches Home Assistant TP-Link DHCP rules', async () => {
const descriptor = createTplinkDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[1];
const result = await matcher.matches({
hostname: 'HS110-Office',
ipAddress: '192.168.1.44',
macAddress: '50:C7:BF:AA:BB:CC',
}, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.host).toEqual('192.168.1.44');
});
tap.test('validates manual snapshot candidates', async () => {
const descriptor = createTplinkDiscoveryDescriptor();
const manualMatcher = descriptor.getMatchers()[2];
const validator = descriptor.getValidators()[0];
const manual = await manualMatcher.matches({
host: '192.168.1.55',
model: 'Tapo P110',
alias: 'Desk Plug',
snapshot: {
connected: true,
devices: [],
entities: [],
events: [],
},
}, {});
const validated = await validator.validate(manual.candidate!, {});
expect(manual.matched).toBeTrue();
expect(validated.matched).toBeTrue();
expect(validated.metadata?.encryptedLocalProtocolImplemented).toEqual(false);
});
export default tap.start();
+97
View File
@@ -0,0 +1,97 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { TplinkMapper, type ITplinkSnapshot } from '../../ts/integrations/tplink/index.js';
const snapshot: ITplinkSnapshot = {
connected: true,
devices: [
{
id: 'bulb-1',
alias: 'Living Lamp',
model: 'KL130',
type: 'bulb',
macAddress: '1C:3B:F3:00:11:22',
state: {
state: true,
brightness: 80,
color_temperature: 3000,
rgb: { r: 255, g: 100, b: 10 },
current_consumption: 7.5,
rssi: -53,
},
},
{
id: 'strip-1',
alias: 'Office Strip',
model: 'HS300',
type: 'strip',
state: { state: true },
children: [
{
id: 'strip-1-outlet-1',
alias: 'Printer',
model: 'HS300 Outlet',
type: 'plug',
state: {
state: false,
current_consumption: 2.25,
},
},
],
},
{
id: 'sensor-1',
alias: 'Back Door',
model: 'T110',
type: 'sensor',
state: {
is_open: true,
battery_level: 92,
},
},
],
entities: [],
events: [],
};
tap.test('maps TP-Link plugs, strips, bulbs, and sensors', async () => {
const devices = TplinkMapper.toDevices(snapshot);
const entities = TplinkMapper.toEntities(snapshot);
expect(devices.some((deviceArg) => deviceArg.id === 'tplink.device.1c_3b_f3_00_11_22')).toBeTrue();
expect(devices.some((deviceArg) => deviceArg.id === 'tplink.device.strip_1_outlet_1')).toBeTrue();
expect(entities.find((entityArg) => entityArg.id === 'light.living_lamp')?.state).toEqual('on');
expect(entities.find((entityArg) => entityArg.id === 'switch.printer')?.state).toEqual('off');
expect(entities.find((entityArg) => entityArg.id === 'sensor.living_lamp_current_consumption')?.state).toEqual(7.5);
expect(entities.find((entityArg) => entityArg.id === 'binary_sensor.back_door_is_open')?.state).toEqual('on');
expect(entities.find((entityArg) => entityArg.id === 'sensor.back_door_battery_level')?.state).toEqual(92);
});
tap.test('maps canonical services to TP-Link feature commands', async () => {
const turnOnCommand = TplinkMapper.commandForService(snapshot, {
domain: 'light',
service: 'turn_on',
target: { entityId: 'light.living_lamp' },
data: { brightness_pct: 50, color_temp_kelvin: 2700, rgb_color: [10, 20, 30] },
});
const switchCommand = TplinkMapper.commandForService(snapshot, {
domain: 'switch',
service: 'turn_off',
target: { entityId: 'switch.printer' },
});
const numberCommand = TplinkMapper.commandForService(snapshot, {
domain: 'number',
service: 'set_value',
target: { deviceId: 'tplink.device.1c_3b_f3_00_11_22' },
data: { featureId: 'brightness', value: 30 },
});
expect(turnOnCommand?.payload.brightness).toEqual(50);
expect(turnOnCommand?.payload.color_temperature).toEqual(2700);
expect(turnOnCommand?.payload.rgb).toEqual({ r: 10, g: 20, b: 30 });
expect(switchCommand?.featureId).toEqual('state');
expect(switchCommand?.value).toEqual(false);
expect(numberCommand?.featureId).toEqual('brightness');
expect(numberCommand?.value).toEqual(30);
});
export default tap.start();
+59
View File
@@ -0,0 +1,59 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createUnifiDiscoveryDescriptor } from '../../ts/integrations/unifi/index.js';
tap.test('matches UniFi mDNS, SSDP, manual, and discovery records', async () => {
const descriptor = createUnifiDiscoveryDescriptor();
const mdnsMatcher = descriptor.getMatchers()[0];
const ssdpMatcher = descriptor.getMatchers()[1];
const manualMatcher = descriptor.getMatchers()[2];
const discoveryMatcher = descriptor.getMatchers()[3];
const mdnsResult = await mdnsMatcher.matches({
type: '_unifi._tcp.local.',
name: 'UniFi Network._unifi._tcp.local.',
host: 'unifi.local',
txt: {
mac: 'b4fbe4123456',
model: 'UniFi Dream Machine',
controller_uuid: 'controller-1',
},
}, {});
const ssdpResult = await ssdpMatcher.matches({
location: 'https://192.168.1.1:443/description.xml',
manufacturer: 'Ubiquiti Networks',
modelDescription: 'UniFi Dream Machine',
usn: 'uuid:udm-1',
}, {});
const manualResult = await manualMatcher.matches({
host: '192.168.1.2',
site: 'default',
model: 'UniFi Network',
}, {});
const discoveryResult = await discoveryMatcher.matches({
source_ip: '192.168.1.1',
hw_addr: 'B4:FB:E4:12:34:56',
services: { network: true },
}, {});
expect(mdnsResult.matched).toBeTrue();
expect(mdnsResult.normalizedDeviceId).toEqual('controller-1');
expect(ssdpResult.matched).toBeTrue();
expect(ssdpResult.candidate?.host).toEqual('192.168.1.1');
expect(manualResult.matched).toBeTrue();
expect(manualResult.candidate?.port).toEqual(443);
expect(discoveryResult.normalizedDeviceId).toEqual('b4:fb:e4:12:34:56');
});
tap.test('validates UniFi candidates', async () => {
const validator = createUnifiDiscoveryDescriptor().getValidators()[0];
const result = await validator.validate({
source: 'ssdp',
host: '192.168.1.1',
manufacturer: 'Ubiquiti Networks',
model: 'UniFi Dream Machine SE',
}, {});
expect(result.matched).toBeTrue();
expect(result.confidence).toEqual('high');
});
export default tap.start();
+113
View File
@@ -0,0 +1,113 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { UnifiMapper, type IUnifiSnapshot } from '../../ts/integrations/unifi/index.js';
const now = Math.floor(Date.now() / 1000);
const snapshot: IUnifiSnapshot = {
connected: true,
host: '192.168.1.1',
port: 443,
site: 'default',
controller: {
id: 'controller-1',
name: 'UniFi Network',
version: '9.0.0',
connected: true,
},
sites: [{ id: 'site-1', name: 'default', description: 'Default' }],
clients: [
{
mac: 'aa:bb:cc:dd:ee:ff',
name: 'Kitchen Phone',
ip: '192.168.1.55',
essid: 'Guest WiFi',
is_wired: false,
blocked: false,
last_seen: now,
ap_mac: 'b4:fb:e4:12:34:56',
'rx_bytes-r': 2000000,
'tx_bytes-r': 1000000,
rssi: -55,
},
],
devices: [
{
mac: 'b4:fb:e4:12:34:56',
name: 'Switch 24',
model: 'USW-24-PoE',
version: '6.6.1',
ip: '192.168.1.10',
state: 1,
uptime: 3600,
general_temperature: 42,
'system-stats': { cpu: '12.5', mem: '31.1' },
port_table: [
{
deviceMac: 'b4:fb:e4:12:34:56',
port_idx: 1,
name: 'Office AP',
enable: true,
up: true,
port_poe: true,
poe_mode: 'auto',
poe_power: '7.2',
speed: 1000,
'rx_bytes-r': 1024,
'tx_bytes-r': 2048,
},
],
},
],
wlans: [
{
_id: 'wlan-1',
name: 'Guest WiFi',
enabled: true,
security: 'wpapsk',
is_guest: true,
},
],
ports: [],
events: [],
};
tap.test('maps UniFi clients, devices, WLANs, and ports', async () => {
const devices = UnifiMapper.toDevices(snapshot);
const entities = UnifiMapper.toEntities(snapshot);
expect(devices.some((deviceArg) => deviceArg.id === 'unifi.client.aa_bb_cc_dd_ee_ff')).toBeTrue();
expect(devices.some((deviceArg) => deviceArg.id === 'unifi.device.b4_fb_e4_12_34_56')).toBeTrue();
expect(devices.some((deviceArg) => deviceArg.id === 'unifi.wlan.wlan_1')).toBeTrue();
expect(entities.find((entityArg) => entityArg.id === 'binary_sensor.kitchen_phone_connected')?.state).toEqual('on');
expect(entities.find((entityArg) => entityArg.id === 'switch.guest_wifi')?.state).toEqual('on');
expect(entities.find((entityArg) => entityArg.id === 'switch.switch_24_office_ap_poe')?.state).toEqual('on');
expect(entities.find((entityArg) => entityArg.id === 'sensor.switch_24_office_ap_poe_power')?.state).toEqual(7.2);
});
tap.test('maps services to UniFi commands', async () => {
const blockCommand = UnifiMapper.commandForService(snapshot, {
domain: 'unifi',
service: 'block_client',
target: {},
data: { mac: 'aa:bb:cc:dd:ee:ff' },
});
const wlanCommand = UnifiMapper.commandForService(snapshot, {
domain: 'switch',
service: 'turn_off',
target: { entityId: 'switch.guest_wifi' },
});
const poeCommand = UnifiMapper.commandForService(snapshot, {
domain: 'switch',
service: 'turn_off',
target: { entityId: 'switch.switch_24_office_ap_poe' },
});
expect(blockCommand?.type).toEqual('blockClient');
expect(blockCommand?.block).toBeTrue();
expect(wlanCommand?.type).toEqual('setWlanEnabled');
expect(wlanCommand?.enabled).toBeFalse();
expect(poeCommand?.type).toEqual('setPoePortEnabled');
expect(poeCommand?.portIdx).toEqual('1');
});
export default tap.start();