import { assert, assertEquals, assertExists } from 'jsr:@std/assert@^1.0.0'; import { NupstSnmp } from '../ts/snmp/manager.ts'; import { UpsOidSets } from '../ts/snmp/oid-sets.ts'; import type { IOidSet, ISnmpConfig, TUpsModel } from '../ts/snmp/types.ts'; import { analyzeConfigReload, shouldRefreshPauseState, shouldReloadConfig, } from '../ts/config-watch.ts'; import { type IPauseState, loadPauseSnapshot } from '../ts/pause-state.ts'; import { shortId } from '../ts/helpers/shortid.ts'; import { HTTP_SERVER, SNMP, THRESHOLDS, TIMING, UI } from '../ts/constants.ts'; import { Action, type IActionContext } from '../ts/actions/base-action.ts'; import { applyDefaultShutdownDelay, buildUpsActionContext, decideUpsActionExecution, } from '../ts/action-orchestration.ts'; import { buildShutdownErrorRow, buildShutdownStatusRow, selectEmergencyCandidate, } from '../ts/shutdown-monitoring.ts'; import { buildFailedUpsPollSnapshot, buildSuccessfulUpsPollSnapshot, hasThresholdViolation, } from '../ts/ups-monitoring.ts'; import { createInitialUpsStatus } from '../ts/ups-status.ts'; import * as qenv from 'npm:@push.rocks/qenv@^6.0.0'; const testQenv = new qenv.Qenv('./', '.nogit/'); // ============================================================================= // UNIT TESTS - No external dependencies required // ============================================================================= // ----------------------------------------------------------------------------- // shortId() Tests // ----------------------------------------------------------------------------- Deno.test('shortId: generates 6-character string', () => { const id = shortId(); assertEquals(id.length, 6); }); Deno.test('shortId: contains only alphanumeric characters', () => { const id = shortId(); const alphanumericRegex = /^[a-zA-Z0-9]+$/; assert(alphanumericRegex.test(id), `ID "${id}" contains non-alphanumeric characters`); }); Deno.test('shortId: generates unique IDs', () => { const ids = new Set(); const count = 100; for (let i = 0; i < count; i++) { ids.add(shortId()); } // All IDs should be unique (statistically extremely likely for 100 IDs) assertEquals(ids.size, count, 'Generated IDs should be unique'); }); // ----------------------------------------------------------------------------- // Constants Tests // ----------------------------------------------------------------------------- Deno.test('TIMING constants: all values are positive numbers', () => { for (const [key, value] of Object.entries(TIMING)) { assert(typeof value === 'number', `TIMING.${key} should be a number`); assert(value > 0, `TIMING.${key} should be positive`); } }); Deno.test('SNMP constants: port is 161', () => { assertEquals(SNMP.DEFAULT_PORT, 161); }); Deno.test('SNMP constants: timeouts increase with security level', () => { assert( SNMP.TIMEOUT_NO_AUTH_MS <= SNMP.TIMEOUT_AUTH_MS, 'Auth timeout should be >= noAuth timeout', ); assert( SNMP.TIMEOUT_AUTH_MS <= SNMP.TIMEOUT_AUTH_PRIV_MS, 'AuthPriv timeout should be >= Auth timeout', ); }); Deno.test('THRESHOLDS constants: defaults are reasonable', () => { assert(THRESHOLDS.DEFAULT_BATTERY_PERCENT > 0 && THRESHOLDS.DEFAULT_BATTERY_PERCENT <= 100); assert(THRESHOLDS.DEFAULT_RUNTIME_MINUTES > 0); assert(THRESHOLDS.EMERGENCY_RUNTIME_MINUTES < THRESHOLDS.DEFAULT_RUNTIME_MINUTES); }); Deno.test('HTTP_SERVER constants: valid defaults', () => { assertEquals(HTTP_SERVER.DEFAULT_PORT, 8080); assert(HTTP_SERVER.DEFAULT_PATH.startsWith('/')); }); Deno.test('UI constants: box widths are ascending', () => { assert(UI.DEFAULT_BOX_WIDTH < UI.WIDE_BOX_WIDTH); assert(UI.WIDE_BOX_WIDTH < UI.EXTRA_WIDE_BOX_WIDTH); }); // ----------------------------------------------------------------------------- // Pause State Tests // ----------------------------------------------------------------------------- Deno.test('loadPauseSnapshot: reports paused state for valid pause file', async () => { const tempDir = await Deno.makeTempDir(); const pauseFilePath = `${tempDir}/pause.json`; const pauseState: IPauseState = { pausedAt: 1000, pausedBy: 'cli', reason: 'maintenance', resumeAt: 5000, }; try { await Deno.writeTextFile(pauseFilePath, JSON.stringify(pauseState)); const snapshot = loadPauseSnapshot(pauseFilePath, false, 2000); assertEquals(snapshot.isPaused, true); assertEquals(snapshot.pauseState, pauseState); assertEquals(snapshot.transition, 'paused'); } finally { await Deno.remove(tempDir, { recursive: true }); } }); Deno.test('loadPauseSnapshot: auto-resumes expired pause file', async () => { const tempDir = await Deno.makeTempDir(); const pauseFilePath = `${tempDir}/pause.json`; const pauseState: IPauseState = { pausedAt: 1000, pausedBy: 'cli', resumeAt: 1500, }; try { await Deno.writeTextFile(pauseFilePath, JSON.stringify(pauseState)); const snapshot = loadPauseSnapshot(pauseFilePath, true, 2000); assertEquals(snapshot.isPaused, false); assertEquals(snapshot.pauseState, null); assertEquals(snapshot.transition, 'autoResumed'); let fileExists = true; try { await Deno.stat(pauseFilePath); } catch { fileExists = false; } assertEquals(fileExists, false); } finally { await Deno.remove(tempDir, { recursive: true }); } }); Deno.test('loadPauseSnapshot: reports resumed when pause file disappears', async () => { const tempDir = await Deno.makeTempDir(); try { const snapshot = loadPauseSnapshot(`${tempDir}/pause.json`, true, 2000); assertEquals(snapshot.isPaused, false); assertEquals(snapshot.pauseState, null); assertEquals(snapshot.transition, 'resumed'); } finally { await Deno.remove(tempDir, { recursive: true }); } }); // ----------------------------------------------------------------------------- // Config Watch Tests // ----------------------------------------------------------------------------- Deno.test('shouldReloadConfig: matches modify events for config.json', () => { assertEquals( shouldReloadConfig({ kind: 'modify', paths: ['/etc/nupst/config.json'] }), true, ); assertEquals( shouldReloadConfig({ kind: 'create', paths: ['/etc/nupst/config.json'] }), false, ); assertEquals( shouldReloadConfig({ kind: 'modify', paths: ['/etc/nupst/other.json'] }), false, ); }); Deno.test('shouldRefreshPauseState: matches create/modify/remove pause events', () => { assertEquals( shouldRefreshPauseState({ kind: 'create', paths: ['/etc/nupst/pause'] }), true, ); assertEquals( shouldRefreshPauseState({ kind: 'remove', paths: ['/etc/nupst/pause'] }), true, ); assertEquals( shouldRefreshPauseState({ kind: 'modify', paths: ['/etc/nupst/config.json'] }), false, ); }); Deno.test('analyzeConfigReload: detects monitoring start and device count changes', () => { assertEquals(analyzeConfigReload(0, 2), { transition: 'monitoringWillStart', message: 'Configuration reloaded! Found 2 UPS device(s)', shouldInitializeUpsStatus: false, shouldLogMonitoringStart: true, }); assertEquals(analyzeConfigReload(2, 3), { transition: 'deviceCountChanged', message: 'Configuration reloaded! UPS devices: 2 -> 3', shouldInitializeUpsStatus: true, shouldLogMonitoringStart: false, }); assertEquals(analyzeConfigReload(2, 2), { transition: 'reloaded', message: 'Configuration reloaded successfully', shouldInitializeUpsStatus: false, shouldLogMonitoringStart: false, }); }); // ----------------------------------------------------------------------------- // UPS Status Tests // ----------------------------------------------------------------------------- Deno.test('createInitialUpsStatus: creates default daemon UPS status shape', () => { assertEquals(createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1234), { id: 'ups-1', name: 'Main UPS', powerStatus: 'unknown', batteryCapacity: 100, batteryRuntime: 999, outputLoad: 0, outputPower: 0, outputVoltage: 0, outputCurrent: 0, lastStatusChange: 1234, lastCheckTime: 0, consecutiveFailures: 0, unreachableSince: 0, }); }); // ----------------------------------------------------------------------------- // Action Orchestration Tests // ----------------------------------------------------------------------------- Deno.test('buildUpsActionContext: includes previous power status and timestamp', () => { const status = { ...createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1000), powerStatus: 'onBattery' as const, batteryCapacity: 42, batteryRuntime: 15, }; const previousStatus = { ...createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 500), powerStatus: 'online' as const, }; assertEquals( buildUpsActionContext( { id: 'ups-1', name: 'Main UPS' }, status, previousStatus, 'thresholdViolation', 9999, ), { upsId: 'ups-1', upsName: 'Main UPS', powerStatus: 'onBattery', batteryCapacity: 42, batteryRuntime: 15, previousPowerStatus: 'online', timestamp: 9999, triggerReason: 'thresholdViolation', }, ); }); Deno.test('decideUpsActionExecution: suppresses actions while paused', () => { const decision = decideUpsActionExecution( true, { id: 'ups-1', name: 'Main UPS', actions: [{ type: 'shutdown' }] }, createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1000), undefined, 'powerStatusChange', 9999, ); assertEquals(decision, { type: 'suppressed', message: '[PAUSED] Actions suppressed for UPS Main UPS (trigger: powerStatusChange)', }); }); Deno.test('decideUpsActionExecution: falls back to legacy shutdown without actions', () => { const decision = decideUpsActionExecution( false, { id: 'ups-1', name: 'Main UPS' }, createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1000), undefined, 'thresholdViolation', 9999, ); assertEquals(decision, { type: 'legacyShutdown', reason: 'UPS "Main UPS" battery or runtime below threshold', }); }); Deno.test('decideUpsActionExecution: returns executable action plan when actions exist', () => { const decision = decideUpsActionExecution( false, { id: 'ups-1', name: 'Main UPS', actions: [{ type: 'shutdown' }] }, { ...createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1000), powerStatus: 'onBattery', batteryCapacity: 55, batteryRuntime: 18, }, { ...createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 500), powerStatus: 'online', }, 'powerStatusChange', 9999, ); assertEquals(decision, { type: 'execute', actions: [{ type: 'shutdown' }], context: { upsId: 'ups-1', upsName: 'Main UPS', powerStatus: 'onBattery', batteryCapacity: 55, batteryRuntime: 18, previousPowerStatus: 'online', timestamp: 9999, triggerReason: 'powerStatusChange', }, }); }); Deno.test('applyDefaultShutdownDelay: applies only to shutdown actions without explicit delay', () => { const actions = [ { type: 'shutdown' as const }, { type: 'shutdown' as const, shutdownDelay: 0 }, { type: 'shutdown' as const, shutdownDelay: 9 }, { type: 'webhook' as const }, ]; assertEquals(applyDefaultShutdownDelay(actions, 7), [ { type: 'shutdown', shutdownDelay: 7 }, { type: 'shutdown', shutdownDelay: 0 }, { type: 'shutdown', shutdownDelay: 9 }, { type: 'webhook' }, ]); }); // ----------------------------------------------------------------------------- // Shutdown Monitoring Tests // ----------------------------------------------------------------------------- Deno.test('buildShutdownStatusRow: marks critical rows below emergency runtime threshold', () => { const snapshot = buildShutdownStatusRow( 'Main UPS', { powerStatus: 'onBattery', batteryCapacity: 25, batteryRuntime: 4, outputLoad: 15, outputPower: 100, outputVoltage: 230, outputCurrent: 0.4, raw: {}, }, 5, { battery: (value) => `B:${value}`, runtime: (value) => `R:${value}`, ok: (text) => `ok:${text}`, critical: (text) => `critical:${text}`, error: (text) => `error:${text}`, }, ); assertEquals(snapshot.isCritical, true); assertEquals(snapshot.row, { name: 'Main UPS', battery: 'B:25', runtime: 'R:4', status: 'critical:CRITICAL!', }); }); Deno.test('buildShutdownErrorRow: builds shutdown error table row', () => { assertEquals(buildShutdownErrorRow('Main UPS', (text) => `error:${text}`), { name: 'Main UPS', battery: 'error:N/A', runtime: 'error:N/A', status: 'error:ERROR', }); }); Deno.test('selectEmergencyCandidate: keeps first critical UPS candidate', () => { const firstCandidate = selectEmergencyCandidate( null, { id: 'ups-1', name: 'UPS 1' }, { powerStatus: 'onBattery', batteryCapacity: 40, batteryRuntime: 4, outputLoad: 10, outputPower: 60, outputVoltage: 230, outputCurrent: 0.3, raw: {}, }, 5, ); const secondCandidate = selectEmergencyCandidate( firstCandidate, { id: 'ups-2', name: 'UPS 2' }, { powerStatus: 'onBattery', batteryCapacity: 30, batteryRuntime: 3, outputLoad: 15, outputPower: 70, outputVoltage: 230, outputCurrent: 0.4, raw: {}, }, 5, ); assertEquals(secondCandidate, firstCandidate); }); // ----------------------------------------------------------------------------- // UPS Monitoring Tests // ----------------------------------------------------------------------------- Deno.test('buildSuccessfulUpsPollSnapshot: marks recovery from unreachable', () => { const currentStatus = { ...createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1000), powerStatus: 'unreachable' as const, unreachableSince: 2000, consecutiveFailures: 3, }; const snapshot = buildSuccessfulUpsPollSnapshot( { id: 'ups-1', name: 'Main UPS' }, { powerStatus: 'online', batteryCapacity: 95, batteryRuntime: 40, outputLoad: 10, outputPower: 50, outputVoltage: 230, outputCurrent: 0.5, raw: {}, }, currentStatus, 8000, ); assertEquals(snapshot.transition, 'recovered'); assertEquals(snapshot.downtimeSeconds, 6); assertEquals(snapshot.updatedStatus.powerStatus, 'online'); assertEquals(snapshot.updatedStatus.consecutiveFailures, 0); assertEquals(snapshot.updatedStatus.lastStatusChange, 8000); }); Deno.test('buildFailedUpsPollSnapshot: marks UPS unreachable at failure threshold', () => { const currentStatus = { ...createInitialUpsStatus({ id: 'ups-1', name: 'Main UPS' }, 1000), powerStatus: 'onBattery' as const, consecutiveFailures: 2, }; const snapshot = buildFailedUpsPollSnapshot( { id: 'ups-1', name: 'Main UPS' }, currentStatus, 9000, ); assertEquals(snapshot.transition, 'unreachable'); assertEquals(snapshot.failures, 3); assertEquals(snapshot.updatedStatus.powerStatus, 'unreachable'); assertEquals(snapshot.updatedStatus.unreachableSince, 9000); assertEquals(snapshot.updatedStatus.lastStatusChange, 9000); }); Deno.test('hasThresholdViolation: only fires on battery when any action threshold is exceeded', () => { assertEquals( hasThresholdViolation('online', 40, 10, [ { type: 'shutdown', thresholds: { battery: 50, runtime: 20 } }, ]), false, ); assertEquals( hasThresholdViolation('onBattery', 40, 10, [ { type: 'shutdown', thresholds: { battery: 50, runtime: 20 } }, ]), true, ); assertEquals( hasThresholdViolation('onBattery', 90, 60, [ { type: 'shutdown', thresholds: { battery: 50, runtime: 20 } }, ]), false, ); }); // ----------------------------------------------------------------------------- // UpsOidSets Tests // ----------------------------------------------------------------------------- const UPS_MODELS: TUpsModel[] = ['cyberpower', 'apc', 'eaton', 'tripplite', 'liebert', 'custom']; Deno.test('UpsOidSets: all models have OID sets', () => { for (const model of UPS_MODELS) { const oidSet = UpsOidSets.getOidSet(model); assertExists(oidSet, `OID set for ${model} should exist`); } }); Deno.test('UpsOidSets: all non-custom models have complete OIDs', () => { const requiredOids = ['POWER_STATUS', 'BATTERY_CAPACITY', 'BATTERY_RUNTIME', 'OUTPUT_LOAD']; for (const model of UPS_MODELS.filter((m) => m !== 'custom')) { const oidSet = UpsOidSets.getOidSet(model); for (const oid of requiredOids) { const value = oidSet[oid as keyof IOidSet]; assert( typeof value === 'string' && value.length > 0, `${model} should have non-empty ${oid}`, ); } } }); Deno.test('UpsOidSets: power status values defined for non-custom models', () => { for (const model of UPS_MODELS.filter((m) => m !== 'custom')) { const oidSet = UpsOidSets.getOidSet(model); assertExists(oidSet.POWER_STATUS_VALUES, `${model} should have POWER_STATUS_VALUES`); assertExists(oidSet.POWER_STATUS_VALUES?.online, `${model} should have online value`); assertExists(oidSet.POWER_STATUS_VALUES?.onBattery, `${model} should have onBattery value`); } }); Deno.test('UpsOidSets: getStandardOids returns RFC 1628 OIDs', () => { const standardOids = UpsOidSets.getStandardOids(); assert('power status' in standardOids); assert('battery capacity' in standardOids); assert('battery runtime' in standardOids); // RFC 1628 OIDs start with 1.3.6.1.2.1.33 for (const oid of Object.values(standardOids)) { assert(oid.startsWith('1.3.6.1.2.1.33'), `Standard OID should be RFC 1628: ${oid}`); } }); // ----------------------------------------------------------------------------- // Action Base Class Tests // ----------------------------------------------------------------------------- // Create a concrete implementation for testing class TestAction extends Action { readonly type = 'test'; executeCallCount = 0; execute(_context: IActionContext): Promise { this.executeCallCount++; return Promise.resolve(); } // Expose protected methods for testing public testShouldExecute(context: IActionContext): boolean { return this.shouldExecute(context); } public testAreThresholdsExceeded(batteryCapacity: number, batteryRuntime: number): boolean { return this.areThresholdsExceeded(batteryCapacity, batteryRuntime); } } function createMockContext(overrides: Partial = {}): IActionContext { return { upsId: 'test-ups', upsName: 'Test UPS', powerStatus: 'online', batteryCapacity: 100, batteryRuntime: 60, previousPowerStatus: 'online', timestamp: Date.now(), triggerReason: 'powerStatusChange', ...overrides, }; } Deno.test('Action.areThresholdsExceeded: returns false when no thresholds configured', () => { const action = new TestAction({ type: 'shutdown' }); assertEquals(action.testAreThresholdsExceeded(50, 30), false); }); Deno.test('Action.areThresholdsExceeded: returns true when battery below threshold', () => { const action = new TestAction({ type: 'shutdown', thresholds: { battery: 60, runtime: 20 }, }); assertEquals(action.testAreThresholdsExceeded(59, 30), true); // Battery below assertEquals(action.testAreThresholdsExceeded(60, 30), false); // Battery at threshold assertEquals(action.testAreThresholdsExceeded(100, 30), false); // Battery above }); Deno.test('Action.areThresholdsExceeded: returns true when runtime below threshold', () => { const action = new TestAction({ type: 'shutdown', thresholds: { battery: 60, runtime: 20 }, }); assertEquals(action.testAreThresholdsExceeded(100, 19), true); // Runtime below assertEquals(action.testAreThresholdsExceeded(100, 20), false); // Runtime at threshold assertEquals(action.testAreThresholdsExceeded(100, 60), false); // Runtime above }); Deno.test('Action.shouldExecute: onlyPowerChanges mode', () => { const action = new TestAction({ type: 'shutdown', triggerMode: 'onlyPowerChanges', }); assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'powerStatusChange' })), true, ); assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'thresholdViolation' })), false, ); }); Deno.test('Action.shouldExecute: onlyThresholds mode', () => { const action = new TestAction({ type: 'shutdown', triggerMode: 'onlyThresholds', thresholds: { battery: 60, runtime: 20 }, }); // Below thresholds - should execute assertEquals( action.testShouldExecute(createMockContext({ batteryCapacity: 50, batteryRuntime: 10 })), true, ); // Above thresholds - should not execute assertEquals( action.testShouldExecute(createMockContext({ batteryCapacity: 100, batteryRuntime: 60 })), false, ); }); Deno.test('Action.shouldExecute: onlyThresholds mode without thresholds returns false', () => { const action = new TestAction({ type: 'shutdown', triggerMode: 'onlyThresholds', // No thresholds configured }); assertEquals(action.testShouldExecute(createMockContext()), false); }); Deno.test('Action.shouldExecute: powerChangesAndThresholds mode (default)', () => { const action = new TestAction({ type: 'shutdown', thresholds: { battery: 60, runtime: 20 }, // No triggerMode = defaults to powerChangesAndThresholds }); // Power change - should execute assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'powerStatusChange' })), true, ); // Threshold violation - should execute assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'thresholdViolation', batteryCapacity: 50, })), true, ); // No power change and above thresholds - should not execute assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'thresholdViolation', batteryCapacity: 100, batteryRuntime: 60, })), false, ); }); Deno.test('Action.shouldExecute: anyChange mode always returns true', () => { const action = new TestAction({ type: 'shutdown', triggerMode: 'anyChange', }); assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'powerStatusChange' })), true, ); assertEquals( action.testShouldExecute(createMockContext({ triggerReason: 'thresholdViolation' })), true, ); }); // ----------------------------------------------------------------------------- // NupstSnmp Class Tests (Unit tests - no real UPS needed) // ----------------------------------------------------------------------------- Deno.test('NupstSnmp: can be instantiated', () => { const snmp = new NupstSnmp(false); assertExists(snmp); }); Deno.test('NupstSnmp: debug mode can be enabled', () => { const snmpDebug = new NupstSnmp(true); const snmpNormal = new NupstSnmp(false); assertExists(snmpDebug); assertExists(snmpNormal); }); // ============================================================================= // INTEGRATION TESTS - Require real UPS (loaded from .nogit/env.json) // ============================================================================= // Helper function to run UPS test with config async function testUpsConnection( snmp: NupstSnmp, config: Record, description: string, ): Promise { console.log(`Testing ${description}...`); const snmpConfig = config.snmp as ISnmpConfig; 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 reasonable timeout for testing const testSnmpConfig = { ...snmpConfig, timeout: Math.min(snmpConfig.timeout, SNMP.MAX_TEST_TIMEOUT_MS), }; 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`); // Validate response structure assertExists(status, 'Status should exist'); assert( ['online', 'onBattery', 'unknown'].includes(status.powerStatus), `Power status should be valid: ${status.powerStatus}`, ); assertEquals(typeof status.batteryCapacity, 'number', 'Battery capacity should be a number'); assertEquals(typeof status.batteryRuntime, 'number', 'Battery runtime should be a number'); // Validate ranges assert( status.batteryCapacity >= 0 && status.batteryCapacity <= 100, `Battery capacity should be 0-100: ${status.batteryCapacity}`, ); assert( status.batteryRuntime >= 0, `Battery runtime should be non-negative: ${status.batteryRuntime}`, ); } // Create SNMP instance for integration tests const snmp = new NupstSnmp(true); // Load test configurations const testConfigV1 = await testQenv.getEnvVarOnDemandAsObject('testConfigV1'); const testConfigV3 = await testQenv.getEnvVarOnDemandAsObject('testConfigV3'); Deno.test('Integration: Real UPS test v1', async () => { await testUpsConnection(snmp, testConfigV1, 'SNMPv1 connection'); }); Deno.test('Integration: Real UPS test v3', async () => { await testUpsConnection(snmp, testConfigV3, 'SNMPv3 connection'); });