feat(unifi): implement comprehensive UniFi API client with controllers, protect, access, account, managers, resources, HTTP client, interfaces, logging, plugins, and tests
This commit is contained in:
328
test/test.info.ts
Normal file
328
test/test.info.ts
Normal file
@@ -0,0 +1,328 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
import * as unifi from '../ts/index.js';
|
||||
|
||||
// =============================================================================
|
||||
// SITE INFO - Pretty print comprehensive site information
|
||||
// Tests may use live production keys to test specific features at scale.
|
||||
// Make sure to avoid dangerous, destructive or security relevant operations.
|
||||
// =============================================================================
|
||||
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
let testController: unifi.UnifiController;
|
||||
|
||||
// Helper to print section headers
|
||||
const printHeader = (title: string) => {
|
||||
console.log('');
|
||||
console.log('='.repeat(60));
|
||||
console.log(` ${title}`);
|
||||
console.log('='.repeat(60));
|
||||
};
|
||||
|
||||
// Helper to print sub-section headers
|
||||
const printSubHeader = (title: string) => {
|
||||
console.log('');
|
||||
console.log(`--- ${title} ---`);
|
||||
};
|
||||
|
||||
// Helper to format bytes
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
tap.test('setup - create UnifiController', async () => {
|
||||
const host = await testQenv.getEnvVarOnDemand('UNIFI_CONSOLE_IP');
|
||||
const apiKey = await testQenv.getEnvVarOnDemand('UNIFI_NETWORK_DEV_KEY');
|
||||
|
||||
testController = new unifi.UnifiController({
|
||||
host,
|
||||
apiKey,
|
||||
controllerType: 'unifi-os',
|
||||
verifySsl: false,
|
||||
});
|
||||
|
||||
expect(testController.isAuthenticated()).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('INFO - Sites Overview', async () => {
|
||||
printHeader('SITES OVERVIEW');
|
||||
|
||||
const sites = await testController.listSites();
|
||||
console.log(`Total Sites: ${sites.length}`);
|
||||
|
||||
for (const site of sites) {
|
||||
console.log('');
|
||||
console.log(` Site: ${site.name || site.desc || 'Unnamed'}`);
|
||||
console.log(` ID: ${site._id}`);
|
||||
console.log(` Description: ${site.desc || 'N/A'}`);
|
||||
if (site.role) console.log(` Role: ${site.role}`);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Devices by Type', async () => {
|
||||
printHeader('DEVICES');
|
||||
|
||||
const devices = await testController.deviceManager.listDevices();
|
||||
const accessPoints = await testController.deviceManager.getAccessPoints();
|
||||
const switches = await testController.deviceManager.getSwitches();
|
||||
const gateways = await testController.deviceManager.getGateways();
|
||||
|
||||
console.log(`Total Devices: ${devices.length}`);
|
||||
console.log(` - Access Points: ${accessPoints.length}`);
|
||||
console.log(` - Switches: ${switches.length}`);
|
||||
console.log(` - Gateways: ${gateways.length}`);
|
||||
|
||||
// Access Points
|
||||
if (accessPoints.length > 0) {
|
||||
printSubHeader('Access Points');
|
||||
for (const ap of accessPoints) {
|
||||
const status = ap.isOnline() ? 'ONLINE' : 'OFFLINE';
|
||||
console.log(` [${status}] ${ap.getDisplayName()}`);
|
||||
console.log(` Model: ${ap.model} | IP: ${ap.ip || 'N/A'}`);
|
||||
console.log(` MAC: ${ap.mac} | Version: ${ap.version || 'N/A'}`);
|
||||
if (ap.uptime) {
|
||||
const uptimeHours = Math.floor(ap.uptime / 3600);
|
||||
const uptimeDays = Math.floor(uptimeHours / 24);
|
||||
console.log(` Uptime: ${uptimeDays}d ${uptimeHours % 24}h`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switches
|
||||
if (switches.length > 0) {
|
||||
printSubHeader('Switches');
|
||||
for (const sw of switches) {
|
||||
const status = sw.isOnline() ? 'ONLINE' : 'OFFLINE';
|
||||
console.log(` [${status}] ${sw.getDisplayName()}`);
|
||||
console.log(` Model: ${sw.model} | IP: ${sw.ip || 'N/A'}`);
|
||||
console.log(` MAC: ${sw.mac} | Ports: ${sw.port_table?.length || 'N/A'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Gateways
|
||||
if (gateways.length > 0) {
|
||||
printSubHeader('Gateways');
|
||||
for (const gw of gateways) {
|
||||
const status = gw.isOnline() ? 'ONLINE' : 'OFFLINE';
|
||||
console.log(` [${status}] ${gw.getDisplayName()}`);
|
||||
console.log(` Model: ${gw.model} | IP: ${gw.ip || 'N/A'}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Networks (VLANs)', async () => {
|
||||
printHeader('NETWORKS');
|
||||
|
||||
const response = await testController.getNetworks() as any;
|
||||
const networks = response?.data || [];
|
||||
|
||||
console.log(`Total Networks: ${networks.length}`);
|
||||
|
||||
for (const net of networks) {
|
||||
console.log('');
|
||||
console.log(` Network: ${net.name}`);
|
||||
console.log(` Purpose: ${net.purpose || 'N/A'}`);
|
||||
if (net.vlan_enabled) {
|
||||
console.log(` VLAN ID: ${net.vlan}`);
|
||||
}
|
||||
if (net.ip_subnet) {
|
||||
console.log(` Subnet: ${net.ip_subnet}`);
|
||||
}
|
||||
if (net.dhcpd_enabled !== undefined) {
|
||||
console.log(` DHCP: ${net.dhcpd_enabled ? 'Enabled' : 'Disabled'}`);
|
||||
if (net.dhcpd_enabled && net.dhcpd_start && net.dhcpd_stop) {
|
||||
console.log(` DHCP Range: ${net.dhcpd_start} - ${net.dhcpd_stop}`);
|
||||
}
|
||||
}
|
||||
if (net.igmp_snooping !== undefined) {
|
||||
console.log(` IGMP Snooping: ${net.igmp_snooping ? 'Enabled' : 'Disabled'}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Wireless Networks (WLANs)', async () => {
|
||||
printHeader('WIRELESS NETWORKS');
|
||||
|
||||
const response = await testController.getWlans() as any;
|
||||
const wlans = response?.data || [];
|
||||
|
||||
console.log(`Total WLANs: ${wlans.length}`);
|
||||
|
||||
for (const wlan of wlans) {
|
||||
console.log('');
|
||||
console.log(` SSID: ${wlan.name}`);
|
||||
console.log(` Enabled: ${wlan.enabled !== false ? 'Yes' : 'No'}`);
|
||||
console.log(` Security: ${wlan.security || 'open'}`);
|
||||
if (wlan.wpa_mode) {
|
||||
console.log(` WPA Mode: ${wlan.wpa_mode}`);
|
||||
}
|
||||
if (wlan.networkconf_id) {
|
||||
console.log(` Network ID: ${wlan.networkconf_id}`);
|
||||
}
|
||||
if (wlan.is_guest !== undefined) {
|
||||
console.log(` Guest Network: ${wlan.is_guest ? 'Yes' : 'No'}`);
|
||||
}
|
||||
if (wlan.hide_ssid !== undefined) {
|
||||
console.log(` Hidden: ${wlan.hide_ssid ? 'Yes' : 'No'}`);
|
||||
}
|
||||
if (wlan.wlan_band) {
|
||||
console.log(` Band: ${wlan.wlan_band}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Firewall Rules', async () => {
|
||||
printHeader('FIREWALL RULES');
|
||||
|
||||
const response = await testController.getFirewallRules() as any;
|
||||
const rules = response?.data || [];
|
||||
|
||||
console.log(`Total Firewall Rules: ${rules.length}`);
|
||||
|
||||
for (const rule of rules) {
|
||||
const enabled = rule.enabled !== false ? 'ON' : 'OFF';
|
||||
console.log('');
|
||||
console.log(` [${enabled}] ${rule.name || 'Unnamed Rule'}`);
|
||||
console.log(` Action: ${rule.action || 'N/A'} | Ruleset: ${rule.ruleset || 'N/A'}`);
|
||||
if (rule.src_firewallgroup_ids?.length > 0) {
|
||||
console.log(` Source Groups: ${rule.src_firewallgroup_ids.length}`);
|
||||
}
|
||||
if (rule.dst_firewallgroup_ids?.length > 0) {
|
||||
console.log(` Dest Groups: ${rule.dst_firewallgroup_ids.length}`);
|
||||
}
|
||||
if (rule.protocol) {
|
||||
console.log(` Protocol: ${rule.protocol}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (rules.length === 0) {
|
||||
console.log(' No custom firewall rules configured');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Port Forwards', async () => {
|
||||
printHeader('PORT FORWARDS');
|
||||
|
||||
const response = await testController.getPortForwards() as any;
|
||||
const forwards = response?.data || [];
|
||||
|
||||
console.log(`Total Port Forwards: ${forwards.length}`);
|
||||
|
||||
for (const fwd of forwards) {
|
||||
const enabled = fwd.enabled !== false ? 'ON' : 'OFF';
|
||||
console.log('');
|
||||
console.log(` [${enabled}] ${fwd.name || 'Unnamed'}`);
|
||||
console.log(` External: ${fwd.dst_port || 'N/A'} -> Internal: ${fwd.fwd}:${fwd.fwd_port || fwd.dst_port}`);
|
||||
console.log(` Protocol: ${fwd.proto || 'tcp_udp'}`);
|
||||
}
|
||||
|
||||
if (forwards.length === 0) {
|
||||
console.log(' No port forwards configured');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Connected Clients Summary', async () => {
|
||||
printHeader('CLIENTS SUMMARY');
|
||||
|
||||
const allClients = await testController.clientManager.listActiveClients();
|
||||
const wirelessClients = await testController.clientManager.getWirelessClients();
|
||||
const wiredClients = await testController.clientManager.getWiredClients();
|
||||
|
||||
console.log(`Total Active Clients: ${allClients.length}`);
|
||||
console.log(` - Wireless: ${wirelessClients.length}`);
|
||||
console.log(` - Wired: ${wiredClients.length}`);
|
||||
|
||||
// Calculate total bandwidth
|
||||
let totalTx = 0;
|
||||
let totalRx = 0;
|
||||
for (const client of allClients) {
|
||||
totalTx += client.tx_bytes || 0;
|
||||
totalRx += client.rx_bytes || 0;
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(`Total Data Transfer:`);
|
||||
console.log(` - Upload (TX): ${formatBytes(totalTx)}`);
|
||||
console.log(` - Download (RX): ${formatBytes(totalRx)}`);
|
||||
console.log(` - Combined: ${formatBytes(totalTx + totalRx)}`);
|
||||
|
||||
// Top 5 clients by data usage
|
||||
printSubHeader('Top 5 Clients by Data Usage');
|
||||
const sortedClients = [...allClients].sort((a, b) =>
|
||||
(b.getDataUsage() || 0) - (a.getDataUsage() || 0)
|
||||
).slice(0, 5);
|
||||
|
||||
for (const client of sortedClients) {
|
||||
const usage = client.getDataUsage();
|
||||
console.log(` ${client.getDisplayName()}`);
|
||||
console.log(` IP: ${client.ip || 'N/A'} | Usage: ${formatBytes(usage)}`);
|
||||
console.log(` Type: ${client.getConnectionType()}`);
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - System Health', async () => {
|
||||
printHeader('SYSTEM HEALTH');
|
||||
|
||||
const response = await testController.getHealth() as any;
|
||||
const healthData = response?.data || [];
|
||||
|
||||
for (const subsystem of healthData) {
|
||||
const status = subsystem.status === 'ok' ? 'OK' : subsystem.status?.toUpperCase() || 'UNKNOWN';
|
||||
console.log(` [${status}] ${subsystem.subsystem}`);
|
||||
|
||||
if (subsystem.num_user !== undefined) {
|
||||
console.log(` Users: ${subsystem.num_user}`);
|
||||
}
|
||||
if (subsystem.num_guest !== undefined) {
|
||||
console.log(` Guests: ${subsystem.num_guest}`);
|
||||
}
|
||||
if (subsystem.num_ap !== undefined) {
|
||||
console.log(` APs: ${subsystem.num_ap}`);
|
||||
}
|
||||
if (subsystem.num_adopted !== undefined) {
|
||||
console.log(` Adopted: ${subsystem.num_adopted}`);
|
||||
}
|
||||
if (subsystem.tx_bytes_r !== undefined) {
|
||||
console.log(` TX Rate: ${formatBytes(subsystem.tx_bytes_r)}/s`);
|
||||
}
|
||||
if (subsystem.rx_bytes_r !== undefined) {
|
||||
console.log(` RX Rate: ${formatBytes(subsystem.rx_bytes_r)}/s`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('INFO - Recent Alerts', async () => {
|
||||
printHeader('RECENT ALERTS');
|
||||
|
||||
const response = await testController.getAlerts() as any;
|
||||
const alerts = response?.data || [];
|
||||
|
||||
console.log(`Total Alerts: ${alerts.length}`);
|
||||
|
||||
// Show last 10 alerts
|
||||
const recentAlerts = alerts.slice(0, 10);
|
||||
|
||||
for (const alert of recentAlerts) {
|
||||
const time = alert.time ? new Date(alert.time).toLocaleString() : 'Unknown time';
|
||||
const archived = alert.archived ? '[ARCHIVED]' : '';
|
||||
console.log('');
|
||||
console.log(` ${time} ${archived}`);
|
||||
console.log(` Type: ${alert.key || 'N/A'}`);
|
||||
if (alert.msg) {
|
||||
console.log(` Message: ${alert.msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (alerts.length === 0) {
|
||||
console.log(' No alerts');
|
||||
} else if (alerts.length > 10) {
|
||||
console.log(` ... and ${alerts.length - 10} more alerts`);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
143
test/test.network.ts
Normal file
143
test/test.network.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
import * as unifi from '../ts/index.js';
|
||||
|
||||
// =============================================================================
|
||||
// NETWORK CONTROLLER API INTEGRATION TESTS (Local API)
|
||||
// Tests may use live production keys to test specific features at scale.
|
||||
// Make sure to avoid dangerous, destructive or security relevant operations.
|
||||
// =============================================================================
|
||||
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
let testController: unifi.UnifiController;
|
||||
|
||||
tap.test('setup - create UnifiController with API key', async () => {
|
||||
const host = await testQenv.getEnvVarOnDemand('UNIFI_CONSOLE_IP');
|
||||
const apiKey = await testQenv.getEnvVarOnDemand('UNIFI_NETWORK_DEV_KEY');
|
||||
|
||||
testController = new unifi.UnifiController({
|
||||
host,
|
||||
apiKey,
|
||||
controllerType: 'unifi-os',
|
||||
verifySsl: false,
|
||||
});
|
||||
|
||||
// With API key, already authenticated
|
||||
expect(testController.isAuthenticated()).toBeTrue();
|
||||
console.log('UnifiController created with API key authentication');
|
||||
});
|
||||
|
||||
// READ-ONLY: List devices
|
||||
tap.test('READ-ONLY - should list devices', async () => {
|
||||
const devices = await testController.deviceManager.listDevices();
|
||||
|
||||
console.log(`Found ${devices.length} devices`);
|
||||
expect(devices).toBeArray();
|
||||
|
||||
for (const device of devices) {
|
||||
expect(device).toBeInstanceOf(unifi.UnifiDevice);
|
||||
console.log(` - ${device.getDisplayName()} (${device.model}) - ${device.isOnline() ? 'online' : 'offline'}`);
|
||||
}
|
||||
});
|
||||
|
||||
// READ-ONLY: List access points
|
||||
tap.test('READ-ONLY - should list access points', async () => {
|
||||
const accessPoints = await testController.deviceManager.getAccessPoints();
|
||||
|
||||
console.log(`Found ${accessPoints.length} access points`);
|
||||
expect(accessPoints).toBeArray();
|
||||
|
||||
for (const ap of accessPoints) {
|
||||
console.log(` - AP: ${ap.getDisplayName()} (${ap.ip})`);
|
||||
}
|
||||
});
|
||||
|
||||
// READ-ONLY: List switches
|
||||
tap.test('READ-ONLY - should list switches', async () => {
|
||||
const switches = await testController.deviceManager.getSwitches();
|
||||
|
||||
console.log(`Found ${switches.length} switches`);
|
||||
expect(switches).toBeArray();
|
||||
|
||||
for (const sw of switches) {
|
||||
console.log(` - Switch: ${sw.getDisplayName()} (${sw.ip})`);
|
||||
}
|
||||
});
|
||||
|
||||
// READ-ONLY: List gateways
|
||||
tap.test('READ-ONLY - should list gateways', async () => {
|
||||
const gateways = await testController.deviceManager.getGateways();
|
||||
|
||||
console.log(`Found ${gateways.length} gateways`);
|
||||
expect(gateways).toBeArray();
|
||||
|
||||
for (const gw of gateways) {
|
||||
console.log(` - Gateway: ${gw.getDisplayName()} (${gw.ip})`);
|
||||
}
|
||||
});
|
||||
|
||||
// READ-ONLY: List active clients
|
||||
tap.test('READ-ONLY - should list active clients', async () => {
|
||||
const clients = await testController.clientManager.listActiveClients();
|
||||
|
||||
console.log(`Found ${clients.length} active clients`);
|
||||
expect(clients).toBeArray();
|
||||
|
||||
for (const client of clients.slice(0, 10)) {
|
||||
console.log(` - ${client.getDisplayName()} (${client.ip || 'no IP'}) - ${client.getConnectionType()}`);
|
||||
}
|
||||
if (clients.length > 10) {
|
||||
console.log(` ... and ${clients.length - 10} more`);
|
||||
}
|
||||
});
|
||||
|
||||
// READ-ONLY: List wireless clients
|
||||
tap.test('READ-ONLY - should list wireless clients', async () => {
|
||||
const wirelessClients = await testController.clientManager.getWirelessClients();
|
||||
|
||||
console.log(`Found ${wirelessClients.length} wireless clients`);
|
||||
expect(wirelessClients).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: List wired clients
|
||||
tap.test('READ-ONLY - should list wired clients', async () => {
|
||||
const wiredClients = await testController.clientManager.getWiredClients();
|
||||
|
||||
console.log(`Found ${wiredClients.length} wired clients`);
|
||||
expect(wiredClients).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get system info
|
||||
tap.test('READ-ONLY - should get system info', async () => {
|
||||
const sysInfo = await testController.getSystemInfo();
|
||||
|
||||
console.log('System info retrieved');
|
||||
expect(sysInfo).toBeDefined();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get health
|
||||
tap.test('READ-ONLY - should get health status', async () => {
|
||||
const health = await testController.getHealth();
|
||||
|
||||
console.log('Health status retrieved');
|
||||
expect(health).toBeDefined();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get WLANs
|
||||
tap.test('READ-ONLY - should list WLANs', async () => {
|
||||
const wlans = await testController.getWlans();
|
||||
|
||||
console.log('WLANs retrieved');
|
||||
expect(wlans).toBeDefined();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get networks
|
||||
tap.test('READ-ONLY - should list networks', async () => {
|
||||
const networks = await testController.getNetworks();
|
||||
|
||||
console.log('Networks retrieved');
|
||||
expect(networks).toBeDefined();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
147
test/test.protect.ts
Normal file
147
test/test.protect.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
import * as unifi from '../ts/index.js';
|
||||
|
||||
// =============================================================================
|
||||
// PROTECT API INTEGRATION TESTS (Local NVR API)
|
||||
// Tests may use live production keys to test specific features at scale.
|
||||
// Make sure to avoid dangerous, destructive or security relevant operations.
|
||||
// =============================================================================
|
||||
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
let testProtect: unifi.UnifiProtect;
|
||||
|
||||
tap.test('setup - create UnifiProtect with API key', async () => {
|
||||
const host = await testQenv.getEnvVarOnDemand('UNIFI_CONSOLE_IP');
|
||||
const apiKey = await testQenv.getEnvVarOnDemand('UNIFI_PROTECT_DEV_KEY');
|
||||
|
||||
testProtect = new unifi.UnifiProtect({
|
||||
host,
|
||||
apiKey,
|
||||
verifySsl: false,
|
||||
});
|
||||
|
||||
expect(testProtect.isAuthenticated()).toBeTrue();
|
||||
console.log('UnifiProtect created with API key authentication');
|
||||
|
||||
// Load bootstrap data
|
||||
await testProtect.refreshBootstrap();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get NVR info
|
||||
tap.test('READ-ONLY - should get NVR info', async () => {
|
||||
const nvrInfo = testProtect.getNvrInfo();
|
||||
|
||||
if (nvrInfo) {
|
||||
console.log(`NVR: ${nvrInfo.name} (${nvrInfo.type})`);
|
||||
console.log(` Firmware: ${nvrInfo.firmwareVersion}`);
|
||||
console.log(` Cloud connected: ${testProtect.isCloudConnected()}`);
|
||||
} else {
|
||||
console.log('NVR info not available in bootstrap (may be different API version)');
|
||||
}
|
||||
// Don't fail - NVR info may not be present in all API versions
|
||||
});
|
||||
|
||||
// READ-ONLY: Get storage info
|
||||
tap.test('READ-ONLY - should get storage info', async () => {
|
||||
const storageInfo = testProtect.getStorageInfo();
|
||||
|
||||
if (storageInfo && storageInfo.totalSize) {
|
||||
const totalGB = (storageInfo.totalSize / 1024 / 1024 / 1024).toFixed(2);
|
||||
const usedGB = storageInfo.usedSpace ? (storageInfo.usedSpace / 1024 / 1024 / 1024).toFixed(2) : 'unknown';
|
||||
console.log(`Storage: ${usedGB} GB used of ${totalGB} GB`);
|
||||
} else {
|
||||
console.log('Storage info not available');
|
||||
}
|
||||
// Don't fail - storage info may not be present
|
||||
});
|
||||
|
||||
// READ-ONLY: List cameras
|
||||
tap.test('READ-ONLY - should list cameras', async () => {
|
||||
const cameras = await testProtect.cameraManager.listCameras();
|
||||
|
||||
console.log(`Found ${cameras.length} cameras`);
|
||||
expect(cameras).toBeArray();
|
||||
|
||||
for (const camera of cameras) {
|
||||
expect(camera).toBeInstanceOf(unifi.UnifiCamera);
|
||||
console.log(` - ${camera.name} (${camera.type}) - ${camera.isOnline() ? 'online' : 'offline'}`);
|
||||
}
|
||||
});
|
||||
|
||||
// READ-ONLY: List online cameras
|
||||
tap.test('READ-ONLY - should list online cameras', async () => {
|
||||
const onlineCameras = await testProtect.cameraManager.getOnlineCameras();
|
||||
|
||||
console.log(`Found ${onlineCameras.length} online cameras`);
|
||||
expect(onlineCameras).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: List offline cameras
|
||||
tap.test('READ-ONLY - should list offline cameras', async () => {
|
||||
const offlineCameras = await testProtect.cameraManager.getOfflineCameras();
|
||||
|
||||
console.log(`Found ${offlineCameras.length} offline cameras`);
|
||||
expect(offlineCameras).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: List doorbells
|
||||
tap.test('READ-ONLY - should list doorbells', async () => {
|
||||
const doorbells = await testProtect.cameraManager.getDoorbells();
|
||||
|
||||
console.log(`Found ${doorbells.length} doorbells`);
|
||||
expect(doorbells).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: List smart detect cameras
|
||||
tap.test('READ-ONLY - should list smart detect cameras', async () => {
|
||||
const smartCameras = await testProtect.cameraManager.getSmartDetectCameras();
|
||||
|
||||
console.log(`Found ${smartCameras.length} cameras with smart detect`);
|
||||
expect(smartCameras).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: List cameras with recent motion
|
||||
tap.test('READ-ONLY - should list cameras with recent motion', async () => {
|
||||
const recentMotion = await testProtect.cameraManager.getCamerasWithRecentMotion(300);
|
||||
|
||||
console.log(`Found ${recentMotion.length} cameras with motion in last 5 minutes`);
|
||||
expect(recentMotion).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get motion events
|
||||
tap.test('READ-ONLY - should get recent motion events', async () => {
|
||||
const events = await testProtect.cameraManager.getAllMotionEvents({ limit: 10 });
|
||||
|
||||
// API might return object or array, handle both
|
||||
const eventsArray = Array.isArray(events) ? events : [];
|
||||
console.log(`Retrieved ${eventsArray.length} recent motion events`);
|
||||
expect(eventsArray).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get lights
|
||||
tap.test('READ-ONLY - should get lights', async () => {
|
||||
const lights = testProtect.getLights();
|
||||
|
||||
console.log(`Found ${lights.length} lights`);
|
||||
expect(lights).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get sensors
|
||||
tap.test('READ-ONLY - should get sensors', async () => {
|
||||
const sensors = testProtect.getSensors();
|
||||
|
||||
console.log(`Found ${sensors.length} sensors`);
|
||||
expect(sensors).toBeArray();
|
||||
});
|
||||
|
||||
// READ-ONLY: Get liveviews
|
||||
tap.test('READ-ONLY - should get liveviews', async () => {
|
||||
const liveviews = await testProtect.getLiveviews();
|
||||
|
||||
console.log('Liveviews retrieved');
|
||||
expect(liveviews).toBeDefined();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
42
test/test.site.ts
Normal file
42
test/test.site.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
import * as unifi from '../ts/index.js';
|
||||
|
||||
// =============================================================================
|
||||
// SITE API INTEGRATION TESTS (Local Controller)
|
||||
// Tests may use live production keys to test specific features at scale.
|
||||
// Make sure to avoid dangerous, destructive or security relevant operations.
|
||||
// =============================================================================
|
||||
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
let testController: unifi.UnifiController;
|
||||
|
||||
tap.test('setup - create UnifiController with API key', async () => {
|
||||
const host = await testQenv.getEnvVarOnDemand('UNIFI_CONSOLE_IP');
|
||||
const apiKey = await testQenv.getEnvVarOnDemand('UNIFI_NETWORK_DEV_KEY');
|
||||
|
||||
testController = new unifi.UnifiController({
|
||||
host,
|
||||
apiKey,
|
||||
controllerType: 'unifi-os',
|
||||
verifySsl: false,
|
||||
});
|
||||
|
||||
expect(testController.isAuthenticated()).toBeTrue();
|
||||
console.log('UnifiController created with API key authentication');
|
||||
});
|
||||
|
||||
// READ-ONLY: List sites
|
||||
tap.test('READ-ONLY - should list sites', async () => {
|
||||
const sites = await testController.listSites();
|
||||
|
||||
console.log(`Found ${sites.length} sites`);
|
||||
expect(sites).toBeArray();
|
||||
|
||||
for (const site of sites) {
|
||||
console.log(` - Site: ${site.name} (${site._id})`);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
181
test/test.ts
181
test/test.ts
@@ -1,8 +1,179 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as unifi from '../ts/index.js'
|
||||
import * as unifi from '../ts/index.js';
|
||||
|
||||
tap.test('first test', async () => {
|
||||
console.log(unifi)
|
||||
})
|
||||
// =============================================================================
|
||||
// UNIT TESTS - No credentials needed, tests class structure and exports
|
||||
// =============================================================================
|
||||
|
||||
export default tap.start()
|
||||
tap.test('should export all main classes', async () => {
|
||||
// Entry point classes
|
||||
expect(unifi.UnifiAccount).toBeDefined();
|
||||
expect(unifi.UnifiController).toBeDefined();
|
||||
expect(unifi.UnifiProtect).toBeDefined();
|
||||
expect(unifi.UnifiAccess).toBeDefined();
|
||||
|
||||
// Manager classes
|
||||
expect(unifi.SiteManager).toBeDefined();
|
||||
expect(unifi.HostManager).toBeDefined();
|
||||
expect(unifi.DeviceManager).toBeDefined();
|
||||
expect(unifi.ClientManager).toBeDefined();
|
||||
expect(unifi.CameraManager).toBeDefined();
|
||||
expect(unifi.DoorManager).toBeDefined();
|
||||
|
||||
// Resource classes
|
||||
expect(unifi.UnifiSite).toBeDefined();
|
||||
expect(unifi.UnifiHost).toBeDefined();
|
||||
expect(unifi.UnifiDevice).toBeDefined();
|
||||
expect(unifi.UnifiClient).toBeDefined();
|
||||
expect(unifi.UnifiCamera).toBeDefined();
|
||||
expect(unifi.UnifiDoor).toBeDefined();
|
||||
|
||||
// HTTP client
|
||||
expect(unifi.UnifiHttp).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('should instantiate UnifiAccount', async () => {
|
||||
const account = new unifi.UnifiAccount({
|
||||
apiKey: 'test-api-key',
|
||||
});
|
||||
|
||||
expect(account).toBeInstanceOf(unifi.UnifiAccount);
|
||||
expect(account.siteManager).toBeInstanceOf(unifi.SiteManager);
|
||||
expect(account.hostManager).toBeInstanceOf(unifi.HostManager);
|
||||
});
|
||||
|
||||
tap.test('should instantiate UnifiController', async () => {
|
||||
const controller = new unifi.UnifiController({
|
||||
host: '192.168.1.1',
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
controllerType: 'unifi-os',
|
||||
});
|
||||
|
||||
expect(controller).toBeInstanceOf(unifi.UnifiController);
|
||||
expect(controller.deviceManager).toBeInstanceOf(unifi.DeviceManager);
|
||||
expect(controller.clientManager).toBeInstanceOf(unifi.ClientManager);
|
||||
expect(controller.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('should instantiate UnifiProtect', async () => {
|
||||
const protect = new unifi.UnifiProtect({
|
||||
host: '192.168.1.1',
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
expect(protect).toBeInstanceOf(unifi.UnifiProtect);
|
||||
expect(protect.cameraManager).toBeInstanceOf(unifi.CameraManager);
|
||||
expect(protect.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('should instantiate UnifiAccess', async () => {
|
||||
const access = new unifi.UnifiAccess({
|
||||
host: '192.168.1.1',
|
||||
token: 'test-bearer-token',
|
||||
});
|
||||
|
||||
expect(access).toBeInstanceOf(unifi.UnifiAccess);
|
||||
expect(access.doorManager).toBeInstanceOf(unifi.DoorManager);
|
||||
});
|
||||
|
||||
tap.test('should create UnifiSite from API object', async () => {
|
||||
const site = unifi.UnifiSite.createFromApiObject({
|
||||
siteId: 'site-123',
|
||||
name: 'Test Site',
|
||||
description: 'A test site',
|
||||
isDefault: true,
|
||||
timezone: 'UTC',
|
||||
});
|
||||
|
||||
expect(site.siteId).toEqual('site-123');
|
||||
expect(site.name).toEqual('Test Site');
|
||||
expect(site.isDefault).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('should create UnifiDevice from API object', async () => {
|
||||
const device = unifi.UnifiDevice.createFromApiObject({
|
||||
_id: 'dev-123',
|
||||
mac: '00:11:22:33:44:55',
|
||||
model: 'UAP-AC-Pro',
|
||||
type: 'uap',
|
||||
name: 'Living Room AP',
|
||||
site_id: 'default',
|
||||
adopted: true,
|
||||
ip: '192.168.1.100',
|
||||
state: 1,
|
||||
version: '6.0.0',
|
||||
});
|
||||
|
||||
expect(device._id).toEqual('dev-123');
|
||||
expect(device.mac).toEqual('00:11:22:33:44:55');
|
||||
expect(device.isOnline()).toBeTrue();
|
||||
expect(device.isAccessPoint()).toBeTrue();
|
||||
expect(device.isSwitch()).toBeFalse();
|
||||
expect(device.getDisplayName()).toEqual('Living Room AP');
|
||||
});
|
||||
|
||||
tap.test('should create UnifiClient from API object', async () => {
|
||||
const client = unifi.UnifiClient.createFromApiObject({
|
||||
_id: 'client-123',
|
||||
mac: 'aa:bb:cc:dd:ee:ff',
|
||||
site_id: 'default',
|
||||
is_wired: false,
|
||||
hostname: 'laptop',
|
||||
ip: '192.168.1.50',
|
||||
essid: 'HomeWiFi',
|
||||
signal: -55,
|
||||
tx_bytes: 1073741824,
|
||||
rx_bytes: 2147483648,
|
||||
});
|
||||
|
||||
expect(client._id).toEqual('client-123');
|
||||
expect(client.isWireless()).toBeTrue();
|
||||
expect(client.getConnectionType()).toEqual('Wireless (HomeWiFi)');
|
||||
expect(client.getSignalQuality()).toEqual('Good');
|
||||
expect(client.getDataUsage()).toEqual(3221225472);
|
||||
});
|
||||
|
||||
tap.test('should create UnifiCamera from API object', async () => {
|
||||
const camera = unifi.UnifiCamera.createFromApiObject({
|
||||
id: 'cam-123',
|
||||
mac: '00:11:22:33:44:66',
|
||||
host: '192.168.1.101',
|
||||
name: 'Front Door',
|
||||
type: 'UVC-G4-Doorbell',
|
||||
state: 'CONNECTED',
|
||||
isConnected: true,
|
||||
isRecording: true,
|
||||
featureFlags: {
|
||||
hasSmartDetect: true,
|
||||
hasMic: true,
|
||||
hasSpeaker: true,
|
||||
},
|
||||
lastMotion: Date.now() - 30000,
|
||||
});
|
||||
|
||||
expect(camera.id).toEqual('cam-123');
|
||||
expect(camera.isOnline()).toBeTrue();
|
||||
expect(camera.isDoorbell()).toBeTrue();
|
||||
expect(camera.hasSmartDetect()).toBeTrue();
|
||||
expect(camera.hasRecentMotion(60)).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('should create UnifiDoor from API object', async () => {
|
||||
const door = unifi.UnifiDoor.createFromApiObject({
|
||||
unique_id: 'door-123',
|
||||
name: 'Main Entrance',
|
||||
alias: 'Front Door',
|
||||
door_lock_relay_status: 'lock',
|
||||
door_position_status: 'close',
|
||||
});
|
||||
|
||||
expect(door.unique_id).toEqual('door-123');
|
||||
expect(door.getDisplayName()).toEqual('Front Door');
|
||||
expect(door.isLocked()).toBeTrue();
|
||||
expect(door.isClosed()).toBeTrue();
|
||||
expect(door.getStatus()).toEqual('Locked, Closed');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
|
||||
Reference in New Issue
Block a user