fix(core): tidy formatting and minor fixes across CLI, SNMP, HTTP server, migrations and packaging

This commit is contained in:
2026-01-29 17:10:17 +00:00
parent fda072d15e
commit ff2dc00f31
31 changed files with 693 additions and 362 deletions

View File

@@ -1,6 +1,7 @@
# Manual Docker Testing Scripts
This directory contains scripts for manually testing NUPST installation and migration in Docker containers with systemd support.
This directory contains scripts for manually testing NUPST installation and migration in Docker
containers with systemd support.
## Prerequisites
@@ -15,12 +16,14 @@ This directory contains scripts for manually testing NUPST installation and migr
Creates a Docker container with systemd and installs NUPST v3.
**What it does:**
- Creates Ubuntu 22.04 container with systemd enabled
- Installs NUPST v3 from commit `806f81c6` (last v3 version)
- Enables and starts the systemd service
- Leaves container running for testing
**Usage:**
```bash
chmod +x 01-setup-v3-container.sh
./01-setup-v3-container.sh
@@ -33,6 +36,7 @@ chmod +x 01-setup-v3-container.sh
Tests the migration from v3 to v4.
**What it does:**
- Checks current v3 installation
- Pulls v4 code from `migration/deno-v4` branch
- Runs install.sh (should auto-detect and migrate)
@@ -40,6 +44,7 @@ Tests the migration from v3 to v4.
- Tests basic commands
**Usage:**
```bash
chmod +x 02-test-v3-to-v4-migration.sh
./02-test-v3-to-v4-migration.sh
@@ -52,6 +57,7 @@ chmod +x 02-test-v3-to-v4-migration.sh
Removes the test container.
**Usage:**
```bash
chmod +x 03-cleanup.sh
./03-cleanup.sh
@@ -134,16 +140,19 @@ docker rm -f nupst-test-v3
## Troubleshooting
### Container won't start
- Ensure Docker daemon is running
- Check you have privileged access
- Try: `docker logs nupst-test-v3`
### Systemd not working in container
- Requires Linux host (not macOS/Windows)
- Needs `--privileged` and cgroup volume mounts
- Check: `docker exec nupst-test-v3 systemctl --version`
### Migration fails
- Check logs: `docker exec nupst-test-v3 journalctl -xe`
- Verify install.sh ran: `docker exec nupst-test-v3 ls -la /opt/nupst/`
- Check service: `docker exec nupst-test-v3 systemctl status nupst`

View File

@@ -5,8 +5,8 @@
* Run with: deno run --allow-all test/showcase.ts
*/
import { logger, type ITableColumn } from '../ts/logger.ts';
import { theme, symbols, getBatteryColor, formatPowerStatus } from '../ts/colors.ts';
import { type ITableColumn, logger } from '../ts/logger.ts';
import { formatPowerStatus, getBatteryColor, symbols, theme } from '../ts/colors.ts';
console.log('');
console.log('═'.repeat(80));
@@ -38,31 +38,51 @@ logger.logBoxEnd();
console.log('');
logger.logBox('Success Box (Green)', [
'Used for successful operations',
'Installation complete, service started, etc.',
], 60, 'success');
logger.logBox(
'Success Box (Green)',
[
'Used for successful operations',
'Installation complete, service started, etc.',
],
60,
'success',
);
console.log('');
logger.logBox('Error Box (Red)', [
'Used for critical errors and failures',
'Configuration errors, connection failures, etc.',
], 60, 'error');
logger.logBox(
'Error Box (Red)',
[
'Used for critical errors and failures',
'Configuration errors, connection failures, etc.',
],
60,
'error',
);
console.log('');
logger.logBox('Warning Box (Yellow)', [
'Used for warnings and deprecations',
'Old command format, missing config, etc.',
], 60, 'warning');
logger.logBox(
'Warning Box (Yellow)',
[
'Used for warnings and deprecations',
'Old command format, missing config, etc.',
],
60,
'warning',
);
console.log('');
logger.logBox('Info Box (Cyan)', [
'Used for informational messages',
'Version info, update available, etc.',
], 60, 'info');
logger.logBox(
'Info Box (Cyan)',
[
'Used for informational messages',
'Version info, update available, etc.',
],
60,
'info',
);
console.log('');
@@ -112,15 +132,24 @@ const upsColumns: ITableColumn[] = [
{ header: 'ID', key: 'id' },
{ header: 'Name', key: 'name' },
{ header: 'Host', key: 'host' },
{ header: 'Status', key: 'status', color: (v) => {
if (v.includes('Online')) return theme.success(v);
if (v.includes('Battery')) return theme.warning(v);
return theme.dim(v);
}},
{ header: 'Battery', key: 'battery', align: 'right', color: (v) => {
const pct = parseInt(v);
return getBatteryColor(pct)(v);
}},
{
header: 'Status',
key: 'status',
color: (v) => {
if (v.includes('Online')) return theme.success(v);
if (v.includes('Battery')) return theme.warning(v);
return theme.dim(v);
},
},
{
header: 'Battery',
key: 'battery',
align: 'right',
color: (v) => {
const pct = parseInt(v);
return getBatteryColor(pct)(v);
},
},
{ header: 'Runtime', key: 'runtime', align: 'right' },
];

View File

@@ -1,9 +1,9 @@
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 { ISnmpConfig, TUpsModel, IOidSet } from '../ts/snmp/types.ts';
import type { IOidSet, ISnmpConfig, TUpsModel } from '../ts/snmp/types.ts';
import { shortId } from '../ts/helpers/shortid.ts';
import { TIMING, SNMP, THRESHOLDS, HTTP_SERVER, UI } from '../ts/constants.ts';
import { HTTP_SERVER, SNMP, THRESHOLDS, TIMING, UI } from '../ts/constants.ts';
import { Action, type IActionContext } from '../ts/actions/base-action.ts';
import * as qenv from 'npm:@push.rocks/qenv@^6.0.0';
@@ -56,8 +56,14 @@ Deno.test('SNMP constants: port is 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');
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', () => {
@@ -92,21 +98,21 @@ Deno.test('UpsOidSets: all models have OID sets', () => {
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')) {
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}`
`${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')) {
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`);
@@ -200,11 +206,11 @@ Deno.test('Action.shouldExecute: onlyPowerChanges mode', () => {
assertEquals(
action.testShouldExecute(createMockContext({ triggerReason: 'powerStatusChange' })),
true
true,
);
assertEquals(
action.testShouldExecute(createMockContext({ triggerReason: 'thresholdViolation' })),
false
false,
);
});
@@ -218,13 +224,13 @@ Deno.test('Action.shouldExecute: onlyThresholds mode', () => {
// Below thresholds - should execute
assertEquals(
action.testShouldExecute(createMockContext({ batteryCapacity: 50, batteryRuntime: 10 })),
true
true,
);
// Above thresholds - should not execute
assertEquals(
action.testShouldExecute(createMockContext({ batteryCapacity: 100, batteryRuntime: 60 })),
false
false,
);
});
@@ -248,7 +254,7 @@ Deno.test('Action.shouldExecute: powerChangesAndThresholds mode (default)', () =
// Power change - should execute
assertEquals(
action.testShouldExecute(createMockContext({ triggerReason: 'powerStatusChange' })),
true
true,
);
// Threshold violation - should execute
@@ -257,7 +263,7 @@ Deno.test('Action.shouldExecute: powerChangesAndThresholds mode (default)', () =
triggerReason: 'thresholdViolation',
batteryCapacity: 50,
})),
true
true,
);
// No power change and above thresholds - should not execute
@@ -267,7 +273,7 @@ Deno.test('Action.shouldExecute: powerChangesAndThresholds mode (default)', () =
batteryCapacity: 100,
batteryRuntime: 60,
})),
false
false,
);
});
@@ -279,11 +285,11 @@ Deno.test('Action.shouldExecute: anyChange mode always returns true', () => {
assertEquals(
action.testShouldExecute(createMockContext({ triggerReason: 'powerStatusChange' })),
true
true,
);
assertEquals(
action.testShouldExecute(createMockContext({ triggerReason: 'thresholdViolation' })),
true
true,
);
});
@@ -339,7 +345,7 @@ async function testUpsConnection(
assertExists(status, 'Status should exist');
assert(
['online', 'onBattery', 'unknown'].includes(status.powerStatus),
`Power status should be valid: ${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');
@@ -347,9 +353,12 @@ async function testUpsConnection(
// Validate ranges
assert(
status.batteryCapacity >= 0 && status.batteryCapacity <= 100,
`Battery capacity should be 0-100: ${status.batteryCapacity}`
`Battery capacity should be 0-100: ${status.batteryCapacity}`,
);
assert(
status.batteryRuntime >= 0,
`Battery runtime should be non-negative: ${status.batteryRuntime}`,
);
assert(status.batteryRuntime >= 0, `Battery runtime should be non-negative: ${status.batteryRuntime}`);
}
// Create SNMP instance for integration tests