nupst/test/test.ts
2025-03-25 09:06:23 +00:00

336 lines
13 KiB
TypeScript

import { tap, expect } from '@push.rocks/tapbundle';
import { NupstSnmp } from '../ts/snmp.js';
import type { SnmpConfig, UpsStatus } from '../ts/snmp.js';
import { SnmpEncoder } from '../ts/snmp/encoder.js';
import { SnmpPacketCreator } from '../ts/snmp/packet-creator.js';
import { SnmpPacketParser } from '../ts/snmp/packet-parser.js';
import * as qenv from '@push.rocks/qenv';
const testQenv = new qenv.Qenv('./', '.nogit/');
// Create an SNMP instance with debug enabled
const snmp = new NupstSnmp(true);
// Load the test configuration from .nogit/env.json
const testConfig = await testQenv.getEnvVarOnDemandAsObject('testConfig');
tap.test('should log config', async () => {
console.log(testConfig);
});
tap.test('SNMP packet creation and parsing test', async () => {
// We'll test the internal methods that are now in separate classes
// Test OID conversion
const oidStr = '1.3.6.1.4.1.3808.1.1.1.4.1.1.0';
const oidArray = SnmpEncoder.oidToArray(oidStr);
console.log('OID array length:', oidArray.length);
console.log('OID array:', oidArray);
// The OID has 14 elements after splitting
expect(oidArray.length).toEqual(14);
expect(oidArray[0]).toEqual(1);
expect(oidArray[1]).toEqual(3);
// Test OID encoding
const encodedOid = SnmpEncoder.encodeOID(oidArray);
expect(encodedOid).toBeInstanceOf(Buffer);
// Test SNMP request creation
const request = SnmpPacketCreator.createSnmpGetRequest(oidStr, 'public', true);
expect(request).toBeInstanceOf(Buffer);
expect(request.length).toBeGreaterThan(20);
// Log the request for debugging
console.log('SNMP Request buffer:', request.toString('hex'));
// Test integer encoding
const int = SnmpEncoder.encodeInteger(42);
expect(int).toBeInstanceOf(Buffer);
expect(int.length).toBeGreaterThanOrEqual(1);
// Test SNMPv3 engine ID discovery message
const discoveryMsg = SnmpPacketCreator.createDiscoveryMessage(testConfig, 1);
expect(discoveryMsg).toBeInstanceOf(Buffer);
expect(discoveryMsg.length).toBeGreaterThan(20);
console.log('SNMPv3 Discovery message:', discoveryMsg.toString('hex'));
});
tap.test('SNMP response parsing simulation', async () => {
// Create a simulated SNMP response for parsing
// Simulate an INTEGER response (battery capacity)
const intResponse = Buffer.from([
0x30, 0x29, // Sequence, length 41
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1c, // GetResponse
0x02, 0x01, 0x01, // Integer (request ID), value 1
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x11, // Sequence (varbinds)
0x30, 0x0f, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x8c, 0x10, 0x01, 0x02, 0x01, 0x00, // OID (example)
0x02, 0x01, 0x64 // Integer (value), value 100 (100%)
]);
// Simulate a Gauge32 response (battery capacity)
const gauge32Response = Buffer.from([
0x30, 0x29, // Sequence, length 41
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1c, // GetResponse
0x02, 0x01, 0x01, // Integer (request ID), value 1
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x11, // Sequence (varbinds)
0x30, 0x0f, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x8c, 0x10, 0x01, 0x02, 0x01, 0x00, // OID (example)
0x42, 0x01, 0x64 // Gauge32 (value), value 100 (100%)
]);
// Simulate a TimeTicks response (battery runtime)
const timeTicksResponse = Buffer.from([
0x30, 0x29, // Sequence, length 41
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1c, // GetResponse
0x02, 0x01, 0x01, // Integer (request ID), value 1
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x11, // Sequence (varbinds)
0x30, 0x0f, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x8c, 0x10, 0x01, 0x02, 0x01, 0x00, // OID (example)
0x43, 0x01, 0x0f // TimeTicks (value), value 15 (0.15 seconds or 15/100 seconds)
]);
// Test parsing INTEGER response
const intValue = SnmpPacketParser.parseSnmpResponse(intResponse, testConfig, true);
console.log('Parsed INTEGER value:', intValue);
expect(intValue).toEqual(100);
// Test parsing Gauge32 response
const gauge32Value = SnmpPacketParser.parseSnmpResponse(gauge32Response, testConfig, true);
console.log('Parsed Gauge32 value:', gauge32Value);
expect(gauge32Value).toEqual(100);
// Test parsing TimeTicks response
const timeTicksValue = SnmpPacketParser.parseSnmpResponse(timeTicksResponse, testConfig, true);
console.log('Parsed TimeTicks value:', timeTicksValue);
expect(timeTicksValue).toEqual(15);
});
tap.test('CyberPower TimeTicks conversion', async () => {
// Test the conversion of TimeTicks to minutes for CyberPower UPS
// Set up a config for CyberPower
const cyberPowerConfig: SnmpConfig = {
...testConfig,
upsModel: 'cyberpower'
};
// Create a simulated TimeTicks response with a value of 104 (104/100 seconds)
const ticksResponse = Buffer.from([
0x30, 0x29, // Sequence
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1c, // GetResponse
0x02, 0x01, 0x01, // Integer (request ID), value 1
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x11, // Sequence (varbinds)
0x30, 0x0f, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x8c, 0x10, 0x01, 0x02, 0x04, 0x00, // OID (battery runtime)
0x43, 0x01, 0x68 // TimeTicks (value), value 104 (104/100 seconds)
]);
// Mock the getUpsStatus function to test our TimeTicks conversion logic
const mockGetUpsStatus = async () => {
// Parse the TimeTicks value from the response
const runtime = SnmpPacketParser.parseSnmpResponse(ticksResponse, testConfig, true);
console.log('Raw runtime value:', runtime);
// Create a sample UPS status result
const result = {
powerStatus: 'onBattery',
batteryCapacity: 100,
batteryRuntime: 0,
raw: {
powerStatus: 2,
batteryCapacity: 100,
batteryRuntime: runtime,
},
};
// Convert TimeTicks to minutes for CyberPower
if (cyberPowerConfig.upsModel === 'cyberpower' && runtime > 0) {
result.batteryRuntime = Math.floor(runtime / 6000);
console.log(`Converting CyberPower runtime from ${runtime} ticks to ${result.batteryRuntime} minutes`);
} else {
result.batteryRuntime = runtime;
}
return result;
};
// Call our mock function
const status = await mockGetUpsStatus();
// Assert the conversion worked correctly
console.log('Final status object:', status);
expect(status.batteryRuntime).toEqual(0); // 104 ticks / 6000 = 0.0173... rounds to 0 minutes
});
tap.test('Simulate fully charged online UPS', async () => {
// Test a realistic scenario of an online UPS with high battery capacity and ~30 mins runtime
// Create simulated responses for power status (online), battery capacity (95%), runtime (30 min)
// Power Status = 2 (online for CyberPower)
const powerStatusResponse = Buffer.from([
0x30, 0x29, // Sequence
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1c, // GetResponse
0x02, 0x01, 0x01, // Integer (request ID), value 1
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x11, // Sequence (varbinds)
0x30, 0x0f, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xed, 0x08, 0x01, 0x01, 0x01, 0x00, // OID (power status)
0x02, 0x01, 0x02 // Integer (value), value 2 (online)
]);
// Battery Capacity = 95% (as Gauge32)
const batteryCapacityResponse = Buffer.from([
0x30, 0x29, // Sequence
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1c, // GetResponse
0x02, 0x01, 0x02, // Integer (request ID), value 2
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x11, // Sequence (varbinds)
0x30, 0x0f, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xed, 0x08, 0x01, 0x02, 0x01, 0x00, // OID (battery capacity)
0x42, 0x01, 0x5F // Gauge32 (value), value 95 (95%)
]);
// Battery Runtime = 30 minutes (as TimeTicks)
// 30 minutes = 1800 seconds = 180000 ticks (in 1/100 seconds)
const batteryRuntimeResponse = Buffer.from([
0x30, 0x2c, // Sequence
0x02, 0x01, 0x00, // Integer (version), value 0
0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, // Octet String (community), value "public"
0xa2, 0x1f, // GetResponse
0x02, 0x01, 0x03, // Integer (request ID), value 3
0x02, 0x01, 0x00, // Integer (error status), value 0
0x02, 0x01, 0x00, // Integer (error index), value 0
0x30, 0x14, // Sequence (varbinds)
0x30, 0x12, // Sequence (varbind)
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xed, 0x08, 0x01, 0x02, 0x04, 0x00, // OID (battery runtime)
0x43, 0x04, 0x00, 0x02, 0xBF, 0x20 // TimeTicks (value), value 180000 (1800 seconds = 30 minutes)
]);
// Mock the getUpsStatus function to test with our simulated data
const mockGetUpsStatus = async () => {
console.log('Simulating UPS status request with synthetic data');
// Create a config that specifies this is a CyberPower UPS
const upsConfig: SnmpConfig = {
host: '192.168.1.1',
port: 161,
version: 1,
community: 'public',
timeout: 5000,
upsModel: 'cyberpower',
};
// Parse each simulated response
const powerStatus = SnmpPacketParser.parseSnmpResponse(powerStatusResponse, upsConfig, true);
console.log('Power status value:', powerStatus);
const batteryCapacity = SnmpPacketParser.parseSnmpResponse(batteryCapacityResponse, upsConfig, true);
console.log('Battery capacity value:', batteryCapacity);
const batteryRuntime = SnmpPacketParser.parseSnmpResponse(batteryRuntimeResponse, upsConfig, true);
console.log('Battery runtime value:', batteryRuntime);
// Convert TimeTicks to minutes for CyberPower UPSes
const runtimeMinutes = Math.floor(batteryRuntime / 6000);
console.log(`Converting ${batteryRuntime} ticks to ${runtimeMinutes} minutes`);
// Interpret power status for CyberPower
// CyberPower: 2=online, 3=on battery
let powerStatusText: 'online' | 'onBattery' | 'unknown' = 'unknown';
if (powerStatus === 2) {
powerStatusText = 'online';
} else if (powerStatus === 3) {
powerStatusText = 'onBattery';
}
// Create the status result
const result: UpsStatus = {
powerStatus: powerStatusText,
batteryCapacity: batteryCapacity,
batteryRuntime: runtimeMinutes,
raw: {
powerStatus,
batteryCapacity,
batteryRuntime,
},
};
return result;
};
// Call our mock function
const status = await mockGetUpsStatus();
// Assert that the values match our expectations
console.log('UPS Status Result:', status);
expect(status.powerStatus).toEqual('online');
expect(status.batteryCapacity).toEqual(95);
expect(status.batteryRuntime).toEqual(30);
});
// Test with real UPS using the configuration from .nogit/env.json
tap.test('Real UPS test', async () => {
try {
console.log('Testing with real UPS configuration...');
// Extract the correct SNMP config from the test configuration
const snmpConfig = testConfig.snmp;
console.log('SNMP Config:');
console.log(` Host: ${snmpConfig.host}:${snmpConfig.port}`);
console.log(` Version: SNMPv${snmpConfig.version}`);
console.log(` UPS Model: ${snmpConfig.upsModel}`);
// Use a short timeout for testing
const testSnmpConfig = {
...snmpConfig,
timeout: Math.min(snmpConfig.timeout, 10000) // Use at most 10 seconds for testing
};
// Try to get the UPS status
const status = await snmp.getUpsStatus(testSnmpConfig);
console.log('UPS Status:');
console.log(` Power Status: ${status.powerStatus}`);
console.log(` Battery Capacity: ${status.batteryCapacity}%`);
console.log(` Runtime Remaining: ${status.batteryRuntime} minutes`);
// Just make sure we got valid data types back
expect(status).toBeTruthy();
expect(['online', 'onBattery', 'unknown']).toContain(status.powerStatus);
expect(typeof status.batteryCapacity).toEqual('number');
expect(typeof status.batteryRuntime).toEqual('number');
} catch (error) {
console.log('Real UPS test failed:', error);
// Skip the test if we can't connect to the real UPS
console.log('Skipping this test since the UPS might not be available');
}
});
// Export the default tap object
export default tap.start();