test: cover tool.ts uptime math (ParseUptime, formulas, UptimeCalculator)

This commit is contained in:
Florian Kostenzer
2026-07-13 22:24:26 +02:00
parent fca7624252
commit 180c963b67
+125
View File
@@ -9,7 +9,13 @@ import {
GetMinuteStartTimestampUTC, GetMinuteStartTimestampUTC,
GetNowTimestampUTC, GetNowTimestampUTC,
GetNowTimestampUTCInMs, GetNowTimestampUTCInMs,
IsValidUptimeFormula,
ParsePercentage,
ParseUptime,
UnparsePercentage,
UptimeCalculator,
} from "./tool"; } from "./tool";
import type { TimestampStatusCount } from "./types/db";
// All fixed timestamps are at 12:00 UTC so that local-date-based functions // All fixed timestamps are at 12:00 UTC so that local-date-based functions
// (GetDayStartTimestampUTC mixes local date parts with Date.UTC) produce the // (GetDayStartTimestampUTC mixes local date parts with Date.UTC) produce the
@@ -79,3 +85,122 @@ describe("timestamp helpers", () => {
expect(BeginningOfMinute({ date, timeZone: "UTC" })).toBe(NOON_UTC); expect(BeginningOfMinute({ date, timeZone: "UTC" })).toBe(NOON_UTC);
}); });
}); });
describe("ParseUptime", () => {
it("returns '-' when there is no data", () => {
expect(ParseUptime(0, 0)).toBe("-");
});
it("returns '0' when nothing is up", () => {
expect(ParseUptime(0, 10)).toBe("0");
});
it("returns whole numbers without decimals", () => {
expect(ParseUptime(10, 10)).toBe("100");
expect(ParseUptime(5, 10)).toBe("50");
});
it("keeps 4 decimal places and strips trailing zeros", () => {
expect(ParseUptime(1, 3)).toBe("33.3333");
expect(ParseUptime(2, 3)).toBe("66.6667");
expect(ParseUptime(1, 8)).toBe("12.5");
expect(ParseUptime(999, 1000)).toBe("99.9");
});
});
describe("ParsePercentage / UnparsePercentage", () => {
it("formats numbers as percentage strings", () => {
expect(ParsePercentage(NaN)).toBe("-");
expect(ParsePercentage(0)).toBe("0");
expect(ParsePercentage(100)).toBe("100");
expect(ParsePercentage(99.9)).toBe("99.9000");
});
it("parses percentage strings back to numbers", () => {
expect(UnparsePercentage("99.9")).toBe(99.9);
expect(UnparsePercentage("0")).toBe(0);
expect(Number.isNaN(UnparsePercentage("-"))).toBe(true);
});
});
describe("IsValidUptimeFormula", () => {
it("accepts single variables and operator chains", () => {
expect(IsValidUptimeFormula("up")).toBe(true);
expect(IsValidUptimeFormula("up + down")).toBe(true);
expect(IsValidUptimeFormula("up+down+degraded+maintenance")).toBe(true);
expect(IsValidUptimeFormula("up - down * maintenance / degraded")).toBe(true);
});
it("is case-insensitive", () => {
expect(IsValidUptimeFormula("UP + Maintenance")).toBe(true);
});
it("rejects malformed formulas", () => {
expect(IsValidUptimeFormula("")).toBe(false);
expect(IsValidUptimeFormula(" ")).toBe(false);
expect(IsValidUptimeFormula("up +")).toBe(false);
expect(IsValidUptimeFormula("+ up")).toBe(false);
expect(IsValidUptimeFormula("banana + up")).toBe(false);
expect(IsValidUptimeFormula("up ** down")).toBe(false);
expect(IsValidUptimeFormula("up down")).toBe(false);
expect(IsValidUptimeFormula("42")).toBe(false);
});
});
describe("UptimeCalculator", () => {
const day = (overrides: Partial<TimestampStatusCount>): TimestampStatusCount => ({
ts: 0,
countOfUp: 0,
countOfDown: 0,
countOfDegraded: 0,
countOfMaintenance: 0,
avgLatency: 0,
maxLatency: 0,
minLatency: 0,
...overrides,
});
it("computes uptime and latency aggregates with the default formulas", () => {
// Defaults (global-constants): numerator "up + maintenance + degraded",
// denominator "up + down + degraded + maintenance"
const data = [
day({ countOfUp: 30, countOfDown: 10, avgLatency: 100, maxLatency: 150, minLatency: 50 }),
day({ countOfUp: 50, countOfDegraded: 10, avgLatency: 200, maxLatency: 300, minLatency: 80 }),
];
expect(UptimeCalculator(data)).toEqual({
uptime: "90", // (80 up + 0 maintenance + 10 degraded) / 100 total
avgLatency: "150ms", // (100 + 200) / 2
maxLatency: "300ms",
minLatency: "50ms",
});
});
it("supports a custom numerator formula", () => {
const data = [day({ countOfUp: 30, countOfDown: 10 }), day({ countOfUp: 50, countOfDegraded: 10 })];
expect(UptimeCalculator(data, "up").uptime).toBe("80");
});
it("falls back to the default formula when the custom one is invalid", () => {
const data = [day({ countOfUp: 30, countOfDown: 10 }), day({ countOfUp: 50, countOfDegraded: 10 })];
expect(UptimeCalculator(data, "banana + up").uptime).toBe("90");
});
it("returns '-' uptime and empty latencies for no data", () => {
expect(UptimeCalculator([])).toEqual({
uptime: "-",
avgLatency: "",
maxLatency: "",
minLatency: "",
});
});
it("only counts entries with a positive avgLatency toward the average", () => {
const data = [
day({ countOfUp: 10, avgLatency: 0 }),
day({ countOfUp: 10, avgLatency: 100, maxLatency: 100, minLatency: 100 }),
];
const result = UptimeCalculator(data);
expect(result.uptime).toBe("100");
expect(result.avgLatency).toBe("100ms"); // (0 + 100) / 1 entry with latency > 0
});
});