152 lines
6.4 KiB
TypeScript
152 lines
6.4 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
|
import {
|
|
cpuTemperature,
|
|
historyTemperatures,
|
|
validateTemperatures,
|
|
type HostTemperatureSensor
|
|
} from '../../src/lib/host-temperatures.js';
|
|
|
|
const sensor = (changes: Partial<HostTemperatureSensor> = {}): HostTemperatureSensor => ({
|
|
id: 'package',
|
|
device: 'coretemp',
|
|
label: 'Package id 0',
|
|
kind: 'cpu-package',
|
|
celsius: 65,
|
|
...changes
|
|
});
|
|
|
|
describe('temperature readings', () => {
|
|
it('keeps valid sensors while dropping malformed, duplicate and out-of-range entries', () => {
|
|
const good = sensor();
|
|
const readings = [
|
|
good,
|
|
good,
|
|
null,
|
|
'bad',
|
|
...[NaN, Infinity, -Infinity, null, '65', true, -101, 201].map((celsius) => ({
|
|
...good,
|
|
id: 'invalid',
|
|
celsius
|
|
})),
|
|
{ ...good, id: 'x'.repeat(101) },
|
|
{ ...good, label: '' },
|
|
{ ...good, kind: 'cpu-control' },
|
|
sensor({ id: 'zero', celsius: 0 })
|
|
];
|
|
expect(validateTemperatures(readings)).toEqual([good, sensor({ id: 'zero', celsius: 0 })]);
|
|
expect(validateTemperatures({})).toEqual([]);
|
|
expect(validateTemperatures(undefined)).toEqual([]);
|
|
});
|
|
it('chooses the hottest package and uses die/core readings only as fallbacks', () => {
|
|
const packages = [sensor(), sensor({ id: 'socket2', label: 'Package id 1', celsius: 72 })];
|
|
const core = sensor({ id: 'core', label: 'Core 0', kind: 'cpu-core', celsius: 80 });
|
|
const die = sensor({ id: 'die', label: 'Tdie', kind: 'cpu-die', celsius: 64 });
|
|
expect(cpuTemperature([...packages, core])?.celsius).toBe(72);
|
|
expect(cpuTemperature([die, core])?.celsius).toBe(64);
|
|
expect(cpuTemperature([core])?.celsius).toBe(80);
|
|
expect(cpuTemperature()).toBeNull();
|
|
const drive = sensor({
|
|
id: 'drive',
|
|
kind: 'nvme',
|
|
device: 'nvme',
|
|
label: 'Composite',
|
|
celsius: 50.9
|
|
});
|
|
expect(
|
|
historyTemperatures([
|
|
...packages,
|
|
core,
|
|
drive,
|
|
{ ...drive, id: 'internal', label: 'Sensor 1' }
|
|
])
|
|
).toEqual([{ ...packages[1], id: 'cpu', label: 'CPU temperature' }, drive]);
|
|
});
|
|
});
|
|
|
|
describe('temperature persistence', () => {
|
|
let fixture: Fixture;
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
fixture = await newFixture([]);
|
|
});
|
|
afterEach(async () => {
|
|
vi.restoreAllMocks();
|
|
await fixture.cleanup();
|
|
});
|
|
|
|
it('accepts older agents and faulty temperature payloads without losing host metrics', async () => {
|
|
const { createHost, heartbeat, validateInventory } =
|
|
await import('../../src/lib/server/hosts/index.js');
|
|
const host = createHost({ name: 'ec-2' });
|
|
expect(validateInventory({ cpuUsagePercent: 25 }).temperatures).toBeUndefined();
|
|
expect(
|
|
heartbeat(host.id, {
|
|
cpuUsagePercent: 25,
|
|
temperatures: [sensor(), { celsius: 'bad' }]
|
|
}).inventory
|
|
).toMatchObject({ cpuUsagePercent: 25, temperatures: [sensor()] });
|
|
expect(
|
|
heartbeat(host.id, { cpuUsagePercent: 30, temperatures: [] }).inventory.temperatures
|
|
).toEqual([]);
|
|
expect(
|
|
heartbeat(host.id, { cpuUsagePercent: 35, temperatures: 'bad' }).inventory.temperatures
|
|
).toEqual([]);
|
|
const { db } = await import('../../src/lib/server/db/index.js');
|
|
expect(
|
|
db()
|
|
.prepare('SELECT celsius FROM ec_host_temperature_samples WHERE host_id=?')
|
|
.all(host.id)
|
|
).toEqual([{ celsius: 65 }]);
|
|
});
|
|
|
|
it('retains one peak per minute and applies host history retention, including offline hosts', async () => {
|
|
const { createHost } = await import('../../src/lib/server/hosts/index.js');
|
|
const { recordTemperatures } = await import('../../src/lib/server/hosts/temperatures.js');
|
|
const { db } = await import('../../src/lib/server/db/index.js');
|
|
const { fleetSettings, saveFleetSettings } =
|
|
await import('../../src/lib/server/settings.js');
|
|
const { pruneSampleHistory } = await import('../../src/lib/server/retention.js');
|
|
const host = createHost({ name: 'ec-2' });
|
|
const minute = Math.floor((Date.now() - 3 * 86400_000) / 60_000) * 60_000;
|
|
for (const [i, celsius] of [65, 78, 67].entries())
|
|
recordTemperatures(
|
|
host.id,
|
|
{ temperatures: [sensor({ celsius })] },
|
|
new Date(minute + i * 15_000).toISOString()
|
|
);
|
|
expect(db().prepare('SELECT celsius FROM ec_host_temperature_samples').all()).toEqual([
|
|
{ celsius: 78 }
|
|
]);
|
|
saveFleetSettings({ ...fleetSettings(), hostResourceKeepDays: 0 });
|
|
pruneSampleHistory();
|
|
expect(db().prepare('SELECT COUNT(*) AS n FROM ec_host_temperature_samples').get()).toEqual(
|
|
{ n: 1 }
|
|
);
|
|
recordTemperatures(host.id, { temperatures: [sensor()] }, new Date().toISOString());
|
|
saveFleetSettings({ ...fleetSettings(), hostResourceKeepDays: 1 });
|
|
pruneSampleHistory();
|
|
expect(db().prepare('SELECT celsius FROM ec_host_temperature_samples').all()).toEqual([
|
|
{ celsius: 65 }
|
|
]);
|
|
});
|
|
|
|
it('upgrades an existing database without rewriting its resource samples', async () => {
|
|
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
|
const { createHost } = await import('../../src/lib/server/hosts/index.js');
|
|
const host = createHost({ name: 'old' });
|
|
db()
|
|
.prepare(
|
|
'INSERT INTO ec_host_resource_samples(host_id,sampled_at,cpu_usage_percent) VALUES(?,?,?)'
|
|
)
|
|
.run(host.id, '2026-10-13T12:00:00Z', 25);
|
|
db().exec('DROP TABLE ec_host_temperature_samples; PRAGMA user_version=22;');
|
|
closeDb();
|
|
expect(db().prepare('SELECT * FROM ec_host_temperature_samples').all()).toEqual([]);
|
|
expect(
|
|
db().prepare('SELECT cpu_usage_percent FROM ec_host_resource_samples').all()
|
|
).toEqual([{ cpu_usage_percent: 25 }]);
|
|
expect(db().pragma('foreign_key_check')).toEqual([]);
|
|
});
|
|
});
|