Add native local infrastructure integrations
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { createServer } from 'node:http';
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { GlancesClient, type IGlancesRawData } from '../../ts/integrations/glances/index.js';
|
||||
|
||||
const rawData: IGlancesRawData = {
|
||||
system: { hostname: 'nas', os_name: 'Linux', platform: 'Linux' },
|
||||
quicklook: { cpu: 12.5 },
|
||||
mem: { percent: 55.5, used: 2147483648, free: 1073741824 },
|
||||
fs: [{ mnt_point: '/', used: 107374182400, size: 214748364800, percent: 50 }],
|
||||
network: [{ interface_name: 'eth0', rx: 600, tx: 300, time_since_update: 3, is_up: true, speed: 1073741824 }],
|
||||
load: { min1: 0.12, min5: 0.24, min15: 0.42 },
|
||||
processcount: { running: 3, total: 144, thread: 300, sleeping: 141 },
|
||||
sensors: [{ label: 'CPU Core', type: 'temperature_core', value: 48.2 }],
|
||||
};
|
||||
|
||||
tap.test('probes Glances v4 then v3 over local HTTP', async () => {
|
||||
const requests: string[] = [];
|
||||
const server = createServer((requestArg, responseArg) => {
|
||||
requests.push(requestArg.url || '');
|
||||
if (requestArg.url === '/api/4/all') {
|
||||
responseArg.statusCode = 404;
|
||||
responseArg.end('no v4 here');
|
||||
return;
|
||||
}
|
||||
if (requestArg.url === '/api/3/all') {
|
||||
expect(requestArg.headers.authorization).toEqual(`Basic ${Buffer.from('glances:secret').toString('base64')}`);
|
||||
responseArg.setHeader('content-type', 'application/json');
|
||||
responseArg.end(JSON.stringify(rawData));
|
||||
return;
|
||||
}
|
||||
responseArg.statusCode = 404;
|
||||
responseArg.end('not found');
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
try {
|
||||
const address = server.address();
|
||||
const port = typeof address === 'object' && address ? address.port : 0;
|
||||
const snapshot = await new GlancesClient({ host: '127.0.0.1', port, username: 'glances', password: 'secret', timeoutMs: 1000 }).getSnapshot();
|
||||
|
||||
expect(requests).toEqual(['/api/4/all', '/api/3/all']);
|
||||
expect(snapshot.online).toBeTrue();
|
||||
expect(snapshot.apiVersion).toEqual(3);
|
||||
expect(snapshot.host.hostname).toEqual('nas');
|
||||
expect(snapshot.sensorData.network?.eth0?.rx).toEqual(200);
|
||||
expect(snapshot.sensors.find((sensorArg) => sensorArg.key === 'cpu_use_percent')?.value).toEqual(12.5);
|
||||
} finally {
|
||||
await new Promise<void>((resolve, reject) => server.close((errorArg) => errorArg ? reject(errorArg) : resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('does not fake refresh success without HTTP endpoint or snapshot', async () => {
|
||||
const client = new GlancesClient({});
|
||||
const snapshot = await client.getSnapshot();
|
||||
const result = await client.refresh();
|
||||
|
||||
expect(snapshot.online).toBeFalse();
|
||||
expect(snapshot.sensors.length).toEqual(0);
|
||||
expect(result.success).toBeFalse();
|
||||
expect(result.error).toContain('endpoint');
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,48 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { GlancesConfigFlow, createGlancesDiscoveryDescriptor } from '../../ts/integrations/glances/index.js';
|
||||
|
||||
tap.test('matches and validates manual Glances entries', async () => {
|
||||
const descriptor = createGlancesDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers()[0];
|
||||
const result = await matcher.matches({ host: '192.168.1.70', name: 'NAS Glances' }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.integrationDomain).toEqual('glances');
|
||||
expect(result.candidate?.port).toEqual(61208);
|
||||
|
||||
const validator = descriptor.getValidators()[0];
|
||||
const validation = await validator.validate(result.candidate!, {});
|
||||
expect(validation.matched).toBeTrue();
|
||||
expect(validation.normalizedDeviceId).toEqual('192.168.1.70:61208');
|
||||
});
|
||||
|
||||
tap.test('matches HTTP API candidates and creates config flow output', async () => {
|
||||
const descriptor = createGlancesDiscoveryDescriptor();
|
||||
const httpMatcher = descriptor.getMatchers()[1];
|
||||
const result = await httpMatcher.matches({ url: 'http://nas.local:61208/api/4/all' }, {});
|
||||
|
||||
expect(result.matched).toBeTrue();
|
||||
expect(result.candidate?.host).toEqual('nas.local');
|
||||
expect(result.candidate?.metadata?.apiVersion).toEqual(4);
|
||||
|
||||
const step = await new GlancesConfigFlow().start(result.candidate!, {});
|
||||
const done = await step.submit!({ username: 'admin', password: 'secret', apiVersion: '4' });
|
||||
|
||||
expect(done.kind).toEqual('done');
|
||||
expect(done.config?.host).toEqual('nas.local');
|
||||
expect(done.config?.port).toEqual(61208);
|
||||
expect(done.config?.apiVersion).toEqual(4);
|
||||
expect(done.config?.username).toEqual('admin');
|
||||
});
|
||||
|
||||
tap.test('rejects candidates without Glances hints or usable data', async () => {
|
||||
const descriptor = createGlancesDiscoveryDescriptor();
|
||||
const matcher = descriptor.getMatchers()[0];
|
||||
const result = await matcher.matches({ name: 'Generic service' }, {});
|
||||
expect(result.matched).toBeFalse();
|
||||
|
||||
const validation = await descriptor.getValidators()[0].validate({ source: 'manual', name: 'Glances' }, {});
|
||||
expect(validation.matched).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { GlancesMapper, type IGlancesRawData } from '../../ts/integrations/glances/index.js';
|
||||
|
||||
const rawData: IGlancesRawData = {
|
||||
system: { hostname: 'nas', os_name: 'Linux', platform: 'Linux', hr_name: 'Ubuntu 24.04' },
|
||||
quicklook: { cpu: 12.5 },
|
||||
percpu: [{ cpu_number: 0, total: 10 }],
|
||||
mem: { percent: 55.5, used: 2147483648, free: 1073741824 },
|
||||
memswap: { percent: 10, used: 1073741824, free: 9663676416 },
|
||||
fs: [{ mnt_point: '/', used: 107374182400, size: 214748364800, free: 107374182400, percent: 50 }],
|
||||
diskio: [{ disk_name: 'sda', read_bytes: 1000, write_bytes: 2000, time_since_update: 2 }],
|
||||
network: [{ interface_name: 'eth0', bytes_recv_rate_per_sec: 1234, bytes_sent_rate_per_sec: 5678, is_up: true, speed: 1073741824 }],
|
||||
load: { min1: 0.12, min5: 0.24, min15: 0.42 },
|
||||
processcount: { running: 3, total: 144, thread: 300, sleeping: 141 },
|
||||
sensors: [{ label: 'CPU Core', type: 'temperature_core', value: 48.2 }],
|
||||
};
|
||||
|
||||
tap.test('maps raw Glances API snapshots to CPU memory disk network load temperature and process sensors', async () => {
|
||||
const snapshot = GlancesMapper.toSnapshot({ config: { name: 'NAS' }, rawData, apiVersion: 4, online: true, source: 'manual' });
|
||||
const entities = GlancesMapper.toEntities(snapshot);
|
||||
const devices = GlancesMapper.toDevices(snapshot);
|
||||
|
||||
expect(snapshot.sensorData.mem?.memory_use).toEqual(2048);
|
||||
expect(snapshot.sensorData.fs?.['/']?.disk_size).toEqual(200);
|
||||
expect(snapshot.sensorData.network?.eth0?.rx).toEqual(1234);
|
||||
expect(devices[0].id).toEqual('glances.host.nas');
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'cpu_use_percent')?.state).toEqual(12.5);
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'memory_use_percent')?.state).toEqual(55.5);
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'disk_glances_use_percent')?.state).toEqual(50);
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'network_eth0_rx')?.state).toEqual(1234);
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'processor_load')?.state).toEqual(0.42);
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'sensor_cpu_core_temperature_core')?.attributes?.deviceClass).toEqual('temperature');
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'process_running')?.state).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('maps manual HA sensor data without inventing absent values', async () => {
|
||||
const snapshot = GlancesMapper.toSnapshot({
|
||||
config: {
|
||||
name: 'Manual Glances',
|
||||
haSensorData: {
|
||||
cpu: { cpu_use_percent: 7 },
|
||||
processcount: { process_total: 9 },
|
||||
},
|
||||
},
|
||||
online: true,
|
||||
source: 'manual',
|
||||
});
|
||||
const entities = GlancesMapper.toEntities(snapshot);
|
||||
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'cpu_use_percent')?.state).toEqual(7);
|
||||
expect(entities.find((entityArg) => entityArg.attributes?.key === 'process_total')?.state).toEqual(9);
|
||||
expect(entities.some((entityArg) => entityArg.attributes?.key === 'memory_use_percent')).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user