c13d657e41
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
288 lines
9.9 KiB
TypeScript
288 lines
9.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
listPbsDatastoreNames,
|
|
normalizePbs,
|
|
normalizePmg,
|
|
normalizePve,
|
|
readBool,
|
|
readList,
|
|
readNumber,
|
|
readText,
|
|
} from './proxmox-normalize';
|
|
|
|
describe('nachsichtige Leser (Aufgabe 3, <behavior>)', () => {
|
|
it('readNumber: Zahl, umwandelbare Zeichenkette, sonst null', () => {
|
|
expect(readNumber(42)).toBe(42);
|
|
expect(readNumber('42')).toBe(42);
|
|
expect(readNumber('0.37')).toBe(0.37);
|
|
expect(readNumber('nicht-umwandelbar')).toBeNull();
|
|
expect(readNumber(undefined)).toBeNull();
|
|
expect(readNumber(null)).toBeNull();
|
|
expect(readNumber(Number.NaN)).toBeNull();
|
|
});
|
|
|
|
it('readText: nichtleere Zeichenkette oder Zahl, sonst null', () => {
|
|
expect(readText('hallo')).toBe('hallo');
|
|
expect(readText(42)).toBe('42');
|
|
expect(readText('')).toBeNull();
|
|
expect(readText(null)).toBeNull();
|
|
expect(readText(undefined)).toBeNull();
|
|
});
|
|
|
|
it('readBool: boolesch oder gaengige Wahr/Falsch-Formen, sonst null', () => {
|
|
expect(readBool(true)).toBe(true);
|
|
expect(readBool('true')).toBe(true);
|
|
expect(readBool(1)).toBe(true);
|
|
expect(readBool(false)).toBe(false);
|
|
expect(readBool('false')).toBe(false);
|
|
expect(readBool('irgendwas')).toBeNull();
|
|
});
|
|
|
|
it('readList: alles, was kein Array ist, wird eine leere Liste', () => {
|
|
expect(readList([1, 2])).toEqual([1, 2]);
|
|
expect(readList('kein-array')).toEqual([]);
|
|
expect(readList(null)).toEqual([]);
|
|
expect(readList(undefined)).toEqual([]);
|
|
expect(readList({})).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('normalizePve (Aufgabe 3, <behavior>)', () => {
|
|
it('Knotenzahl, laufende/gestoppte Gaeste, je Knoten Prozessorlast/Speicher, je Speicherort Belegung', () => {
|
|
const body = {
|
|
data: [
|
|
{ type: 'node', node: 'pve1', cpu: 0.25, maxcpu: 8, mem: 4_000_000_000, maxmem: 16_000_000_000 },
|
|
{ type: 'node', node: 'pve2', cpu: 0.1, maxcpu: 4, mem: 1_000_000_000, maxmem: 8_000_000_000 },
|
|
{ type: 'qemu', node: 'pve1', status: 'running' },
|
|
{ type: 'qemu', node: 'pve1', status: 'stopped' },
|
|
{ type: 'lxc', node: 'pve2', status: 'running' },
|
|
{ type: 'storage', node: 'pve1', storage: 'local-lvm', disk: 100, maxdisk: 500 },
|
|
],
|
|
};
|
|
|
|
const { metrics, errorKind } = normalizePve(body);
|
|
|
|
expect(errorKind).toBeNull();
|
|
expect(metrics.nodeCount).toBe(2);
|
|
expect(metrics.guestsRunning).toBe(2);
|
|
expect(metrics.guestsStopped).toBe(1);
|
|
expect(metrics.nodes).toEqual([
|
|
{ node: 'pve1', cpu: 0.25, maxcpu: 8, mem: 4_000_000_000, maxmem: 16_000_000_000 },
|
|
{ node: 'pve2', cpu: 0.1, maxcpu: 4, mem: 1_000_000_000, maxmem: 8_000_000_000 },
|
|
]);
|
|
expect(metrics.storages).toEqual([
|
|
{ storage: 'local-lvm', node: 'pve1', disk: 100, maxdisk: 500 },
|
|
]);
|
|
});
|
|
|
|
it('Feld fehlt -> null, nie 0/Wurf', () => {
|
|
const body = { data: [{ type: 'node', node: 'pve1' }] };
|
|
expect(() => normalizePve(body)).not.toThrow();
|
|
const { metrics } = normalizePve(body);
|
|
expect(metrics.nodes[0]).toEqual({ node: 'pve1', cpu: null, maxcpu: null, mem: null, maxmem: null });
|
|
});
|
|
|
|
it('Zahl kommt als Zeichenkette -> wird als Zahl gelesen', () => {
|
|
const body = { data: [{ type: 'node', node: 'pve1', cpu: '0.5', maxcpu: '4', mem: '100', maxmem: '200' }] };
|
|
const { metrics } = normalizePve(body);
|
|
expect(metrics.nodes[0]).toEqual({ node: 'pve1', cpu: 0.5, maxcpu: 4, mem: 100, maxmem: 200 });
|
|
});
|
|
|
|
it('Antwort ist HTML statt JSON-Objekt (hier: eine Zeichenkette) -> leeres Messwertobjekt, errorKind antwortform, kein Wurf', () => {
|
|
expect(() => normalizePve('<html>Anmeldeseite</html>')).not.toThrow();
|
|
const { metrics, errorKind } = normalizePve('<html>Anmeldeseite</html>');
|
|
expect(errorKind).toBe('antwortform');
|
|
expect(metrics).toEqual({
|
|
productType: 'pve',
|
|
nodeCount: 0,
|
|
guestsRunning: 0,
|
|
guestsStopped: 0,
|
|
nodes: [],
|
|
storages: [],
|
|
});
|
|
});
|
|
|
|
it('Antwort ist ein Array statt eines Objekts -> antwortform, kein Wurf', () => {
|
|
const { errorKind } = normalizePve([1, 2, 3]);
|
|
expect(errorKind).toBe('antwortform');
|
|
});
|
|
|
|
it('Antwort ist null -> antwortform, kein Wurf', () => {
|
|
const { errorKind } = normalizePve(null);
|
|
expect(errorKind).toBe('antwortform');
|
|
});
|
|
});
|
|
|
|
describe('normalizePbs (Aufgabe 3, <behavior>)', () => {
|
|
it('je Datenspeicher Gesamt/Belegt/Frei, letzter Sicherungszeitpunkt und letztes Pruefergebnis', () => {
|
|
const usage = {
|
|
data: [{ store: 'backup-store', total: 1000, used: 400, avail: 600 }],
|
|
};
|
|
const snapshotsByStore = {
|
|
'backup-store': {
|
|
data: [
|
|
{ 'backup-time': 1000, verification: { state: 'ok' } },
|
|
{ 'backup-time': 2000, verification: { state: 'failed' } },
|
|
],
|
|
},
|
|
};
|
|
|
|
const { metrics, errorKind } = normalizePbs(usage, snapshotsByStore);
|
|
|
|
expect(errorKind).toBeNull();
|
|
expect(metrics.datastores).toEqual([
|
|
{
|
|
name: 'backup-store',
|
|
total: 1000,
|
|
used: 400,
|
|
free: 600,
|
|
lastBackupAt: 2000,
|
|
lastVerifyState: 'failed',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('ein Datenspeicher ohne Sicherungen ergibt null (Frontend zeigt "noch keine Sicherung") und keinen Fehler', () => {
|
|
const usage = { data: [{ store: 'leer', total: 10, used: 0, avail: 10 }] };
|
|
const { metrics, errorKind } = normalizePbs(usage, { leer: { data: [] } });
|
|
expect(errorKind).toBeNull();
|
|
expect(metrics.datastores[0]).toMatchObject({ lastBackupAt: null, lastVerifyState: null });
|
|
});
|
|
|
|
it('Feld fehlt -> null, nie 0/Wurf', () => {
|
|
const usage = { data: [{ store: 'x' }] };
|
|
expect(() => normalizePbs(usage, {})).not.toThrow();
|
|
const { metrics } = normalizePbs(usage, {});
|
|
expect(metrics.datastores[0]).toEqual({
|
|
name: 'x',
|
|
total: null,
|
|
used: null,
|
|
free: null,
|
|
lastBackupAt: null,
|
|
lastVerifyState: null,
|
|
});
|
|
});
|
|
|
|
it('Zahl kommt als Zeichenkette -> wird als Zahl gelesen', () => {
|
|
const usage = { data: [{ store: 'x', total: '1000', used: '400', avail: '600' }] };
|
|
const { metrics } = normalizePbs(usage, {});
|
|
expect(metrics.datastores[0]).toMatchObject({ total: 1000, used: 400, free: 600 });
|
|
});
|
|
|
|
it('Antwort ist HTML statt JSON -> leeres Messwertobjekt, errorKind antwortform, kein Wurf', () => {
|
|
expect(() => normalizePbs('<html></html>', {})).not.toThrow();
|
|
const { metrics, errorKind } = normalizePbs('<html></html>', {});
|
|
expect(errorKind).toBe('antwortform');
|
|
expect(metrics.datastores).toEqual([]);
|
|
});
|
|
|
|
it('ein PBS-Server mit vielen Datenspeichern: listPbsDatastoreNames liefert alle Namen (Deckel lebt in proxmox.service.ts)', () => {
|
|
const usage = { data: Array.from({ length: 15 }, (_, i) => ({ store: `store-${i}` })) };
|
|
expect(listPbsDatastoreNames(usage)).toHaveLength(15);
|
|
});
|
|
});
|
|
|
|
describe('normalizePmg (Aufgabe 3, <behavior>)', () => {
|
|
it('Tageszahlen eingehend, ausgehend, Spam, Viren', () => {
|
|
const body = {
|
|
data: {
|
|
count_in: 100,
|
|
count_out: 50,
|
|
spamcount_in: 10,
|
|
spamcount_out: 2,
|
|
viruscount_in: 1,
|
|
viruscount_out: 0,
|
|
},
|
|
};
|
|
const { metrics, errorKind } = normalizePmg(body);
|
|
expect(errorKind).toBeNull();
|
|
expect(metrics).toEqual({
|
|
productType: 'pmg',
|
|
countIn: 100,
|
|
countOut: 50,
|
|
spamCount: 12,
|
|
virusCount: 1,
|
|
});
|
|
});
|
|
|
|
it('Feld fehlt -> null, nie 0/Wurf', () => {
|
|
const body = { data: {} };
|
|
expect(() => normalizePmg(body)).not.toThrow();
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics).toEqual({
|
|
productType: 'pmg',
|
|
countIn: null,
|
|
countOut: null,
|
|
spamCount: null,
|
|
virusCount: null,
|
|
});
|
|
});
|
|
|
|
it('Zahl kommt als Zeichenkette -> wird als Zahl gelesen', () => {
|
|
const body = { data: { count_in: '100', count_out: '50' } };
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.countIn).toBe(100);
|
|
expect(metrics.countOut).toBe(50);
|
|
});
|
|
|
|
it('Antwort ist HTML statt JSON -> leeres Messwertobjekt, errorKind antwortform, kein Wurf', () => {
|
|
expect(() => normalizePmg('<html></html>')).not.toThrow();
|
|
const { metrics, errorKind } = normalizePmg('<html></html>');
|
|
expect(errorKind).toBe('antwortform');
|
|
expect(metrics).toEqual({
|
|
productType: 'pmg',
|
|
countIn: null,
|
|
countOut: null,
|
|
spamCount: null,
|
|
virusCount: null,
|
|
});
|
|
});
|
|
|
|
it('nur eine Haelfte vorhanden -> null (Spam, nur spamcount_in)', () => {
|
|
const body = { data: { spamcount_in: 10 } };
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.spamCount).toBeNull();
|
|
});
|
|
|
|
it('nur eine Haelfte vorhanden -> null (Spam, nur spamcount_out)', () => {
|
|
const body = { data: { spamcount_out: 2 } };
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.spamCount).toBeNull();
|
|
});
|
|
|
|
it('nur eine Haelfte vorhanden -> null (Viren, nur viruscount_in)', () => {
|
|
const body = { data: { viruscount_in: 1 } };
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.virusCount).toBeNull();
|
|
});
|
|
|
|
it('nur eine Haelfte vorhanden -> null (Viren, nur viruscount_out)', () => {
|
|
const body = { data: { viruscount_out: 3 } };
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.virusCount).toBeNull();
|
|
});
|
|
|
|
it('eine Haelfte ist nicht lesbar -> null (Spam, spamcount_out ist Text)', () => {
|
|
const body = { data: { spamcount_in: 10, spamcount_out: 'abc' } };
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.spamCount).toBeNull();
|
|
});
|
|
|
|
it('Unabhaengigkeit der Paare: Spam unvollstaendig, Viren vollstaendig -> spamCount null, virusCount 1, countIn/countOut unberuehrt', () => {
|
|
const body = {
|
|
data: {
|
|
count_in: 100,
|
|
count_out: 50,
|
|
spamcount_in: 10,
|
|
viruscount_in: 1,
|
|
viruscount_out: 0,
|
|
},
|
|
};
|
|
const { metrics } = normalizePmg(body);
|
|
expect(metrics.spamCount).toBeNull();
|
|
expect(metrics.virusCount).toBe(1);
|
|
expect(metrics.countIn).toBe(100);
|
|
expect(metrics.countOut).toBe(50);
|
|
});
|
|
});
|