feat(cli,snmp): fix APC runtime unit defaults and add interactive action editing
This commit is contained in:
+140
-1
@@ -1,6 +1,10 @@
|
||||
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 {
|
||||
convertRuntimeValueToMinutes,
|
||||
getDefaultRuntimeUnitForUpsModel,
|
||||
} from '../ts/snmp/runtime-units.ts';
|
||||
import type { IOidSet, ISnmpConfig, TUpsModel } from '../ts/snmp/types.ts';
|
||||
import {
|
||||
analyzeConfigReload,
|
||||
@@ -10,7 +14,7 @@ import {
|
||||
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 { Action, type IActionConfig, type IActionContext } from '../ts/actions/base-action.ts';
|
||||
import {
|
||||
applyDefaultShutdownDelay,
|
||||
buildUpsActionContext,
|
||||
@@ -35,6 +39,9 @@ import {
|
||||
evaluateGroupActionThreshold,
|
||||
} from '../ts/group-monitoring.ts';
|
||||
import { createInitialUpsStatus } from '../ts/ups-status.ts';
|
||||
import { MigrationV4_2ToV4_3 } from '../ts/migrations/migration-v4.2-to-v4.3.ts';
|
||||
import { MigrationV4_3ToV4_4 } from '../ts/migrations/migration-v4.3-to-v4.4.ts';
|
||||
import { ActionHandler } from '../ts/cli/action-handler.ts';
|
||||
|
||||
import * as qenv from 'npm:@push.rocks/qenv@^6.0.0';
|
||||
const testQenv = new qenv.Qenv('./', '.nogit/');
|
||||
@@ -794,6 +801,74 @@ Deno.test('UpsOidSets: getStandardOids returns RFC 1628 OIDs', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Runtime Unit Tests
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Deno.test('getDefaultRuntimeUnitForUpsModel: APC defaults to ticks', () => {
|
||||
assertEquals(getDefaultRuntimeUnitForUpsModel('apc'), 'ticks');
|
||||
assertEquals(getDefaultRuntimeUnitForUpsModel('cyberpower'), 'ticks');
|
||||
assertEquals(getDefaultRuntimeUnitForUpsModel('eaton'), 'seconds');
|
||||
});
|
||||
|
||||
Deno.test('convertRuntimeValueToMinutes: APC and explicit overrides convert correctly', () => {
|
||||
assertEquals(convertRuntimeValueToMinutes({ upsModel: 'apc' }, 12000), 2);
|
||||
assertEquals(convertRuntimeValueToMinutes({ upsModel: 'eaton' }, 600), 10);
|
||||
assertEquals(convertRuntimeValueToMinutes({ upsModel: 'apc', runtimeUnit: 'minutes' }, 12), 12);
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Migration Tests
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Deno.test('MigrationV4_2ToV4_3: assigns ticks to APC runtimeUnit', () => {
|
||||
const migration = new MigrationV4_2ToV4_3();
|
||||
const migrated = migration.migrate({
|
||||
version: '4.2',
|
||||
upsDevices: [
|
||||
{
|
||||
name: 'APC Rack UPS',
|
||||
snmp: {
|
||||
upsModel: 'apc',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const migratedDevice = (migrated.upsDevices as Array<Record<string, unknown>>)[0];
|
||||
const snmp = migratedDevice.snmp as Record<string, unknown>;
|
||||
assertEquals(migrated.version, '4.3');
|
||||
assertEquals(snmp.runtimeUnit, 'ticks');
|
||||
});
|
||||
|
||||
Deno.test('MigrationV4_3ToV4_4: corrects APC minutes runtimeUnit to ticks', () => {
|
||||
const migration = new MigrationV4_3ToV4_4();
|
||||
const migrated = migration.migrate({
|
||||
version: '4.3',
|
||||
upsDevices: [
|
||||
{
|
||||
name: 'APC Rack UPS',
|
||||
snmp: {
|
||||
upsModel: 'apc',
|
||||
runtimeUnit: 'minutes',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Eaton UPS',
|
||||
snmp: {
|
||||
upsModel: 'eaton',
|
||||
runtimeUnit: 'seconds',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const migratedDevices = migrated.upsDevices as Array<Record<string, unknown>>;
|
||||
assertEquals(migrated.version, '4.4');
|
||||
assertEquals((migratedDevices[0].snmp as Record<string, unknown>).runtimeUnit, 'ticks');
|
||||
assertEquals((migratedDevices[1].snmp as Record<string, unknown>).runtimeUnit, 'seconds');
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Action Base Class Tests
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -954,6 +1029,70 @@ Deno.test('Action.shouldExecute: anyChange mode always returns true', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Action Handler Tests
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Deno.test('ActionHandler.runEditProcess: updates an existing shutdown action', async () => {
|
||||
const config: {
|
||||
version: string;
|
||||
defaultShutdownDelay: number;
|
||||
checkInterval: number;
|
||||
upsDevices: Array<{ id: string; name: string; groups: string[]; actions: IActionConfig[] }>;
|
||||
groups: [];
|
||||
} = {
|
||||
version: '4.4',
|
||||
defaultShutdownDelay: 5,
|
||||
checkInterval: 30000,
|
||||
upsDevices: [{
|
||||
id: 'ups-1',
|
||||
name: 'UPS 1',
|
||||
groups: [],
|
||||
actions: [{
|
||||
type: 'shutdown',
|
||||
triggerMode: 'onlyThresholds',
|
||||
thresholds: {
|
||||
battery: 40,
|
||||
runtime: 12,
|
||||
},
|
||||
}],
|
||||
}],
|
||||
groups: [],
|
||||
};
|
||||
let savedConfig: typeof config | undefined;
|
||||
|
||||
const daemonMock = {
|
||||
loadConfig: async () => config,
|
||||
saveConfig: (nextConfig: typeof config) => {
|
||||
savedConfig = JSON.parse(JSON.stringify(nextConfig));
|
||||
},
|
||||
getConfig: () => config,
|
||||
};
|
||||
|
||||
const nupstMock = {
|
||||
getDaemon: () => daemonMock,
|
||||
} as unknown as ConstructorParameters<typeof ActionHandler>[0];
|
||||
|
||||
const handler = new ActionHandler(nupstMock);
|
||||
const answers = ['', '12', '25', '8', '3'];
|
||||
let answerIndex = 0;
|
||||
const prompt = async (_question: string): Promise<string> => answers[answerIndex++] ?? '';
|
||||
|
||||
await handler.runEditProcess('ups-1', '0', prompt);
|
||||
|
||||
assertExists(savedConfig);
|
||||
assertEquals(answerIndex, answers.length);
|
||||
assertEquals(savedConfig.upsDevices[0].actions[0], {
|
||||
type: 'shutdown',
|
||||
shutdownDelay: 12,
|
||||
thresholds: {
|
||||
battery: 25,
|
||||
runtime: 8,
|
||||
},
|
||||
triggerMode: 'powerChangesAndThresholds',
|
||||
});
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// NupstSnmp Class Tests (Unit tests - no real UPS needed)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user