Resolve legacy monitor status issues: handle unknown statuses, fix stuck states from old boolean values, and add regression tests. Adjust BaseChart text color for theme compatibility.
This commit is contained in:
2 files changed
+44
-2
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js";
|
||||
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
||||
import type { Check, CheckDiskInfo, CheckSnapshot } from "@/domain/checks/check.type.js";
|
||||
import type { Monitor, MonitorStatus } from "@/domain/monitors/monitor.type.js";
|
||||
import { MonitorStatuses, type Monitor, type MonitorStatus } from "@/domain/monitors/monitor.type.js";
|
||||
import type {
|
||||
DockerStatusPayload,
|
||||
GameStatusPayload,
|
||||
@@ -218,9 +218,12 @@ export class StatusService implements IStatusService {
|
||||
const patch: Partial<Monitor> = {};
|
||||
|
||||
// Resolve "initializing" and "maintenance" up front, incidents should be created on initialization && down
|
||||
// Unknown values (e.g. legacy boolean statuses from old versions) are resolved the same way,
|
||||
// otherwise computeReachability can never transition them and they stay stuck forever.
|
||||
const isKnownStatus = (MonitorStatuses as readonly string[]).includes(monitor.status);
|
||||
let newStatus: MonitorStatus = monitor.status;
|
||||
let statusChanged = false;
|
||||
if (monitor.status === "initializing" || monitor.status === "maintenance") {
|
||||
if (monitor.status === "initializing" || monitor.status === "maintenance" || !isKnownStatus) {
|
||||
newStatus = status === true ? "up" : "down";
|
||||
patch.status = newStatus;
|
||||
statusChanged = newStatus === "down";
|
||||
|
||||
@@ -498,6 +498,45 @@ describe("StatusService", () => {
|
||||
expect(result.monitor.status).toBe("down");
|
||||
});
|
||||
|
||||
it("resolves a legacy boolean status ('true') to 'up' on a passing check (regression: pre-upgrade monitors stuck forever)", async () => {
|
||||
// Old Checkmate versions stored monitor.status as a boolean. Pre-fix, a monitor
|
||||
// carrying status='true' never matched the initializing override and
|
||||
// computeReachability only transitions to 'up' from 'down', so the legacy value
|
||||
// was written back unchanged on every evaluation — stuck forever while the UI
|
||||
// rendered it as 'initializing'.
|
||||
const monitor = makeMonitor({
|
||||
statusWindow: [true, true, true, true, true],
|
||||
statusWindowSize: 5,
|
||||
statusWindowThreshold: 80,
|
||||
status: "true" as MonitorStatus,
|
||||
});
|
||||
const { service, monitorsRepository } = createService();
|
||||
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
|
||||
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
|
||||
|
||||
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }), monitor);
|
||||
|
||||
expect(result.monitor.status).toBe("up");
|
||||
expect(result.statusChanged).toBe(false); // was effectively up already — no notification storm
|
||||
});
|
||||
|
||||
it("resolves a legacy boolean status ('false') to 'down' on a failing check (regression mirror)", async () => {
|
||||
const monitor = makeMonitor({
|
||||
statusWindow: [false, false, false, false, false],
|
||||
statusWindowSize: 5,
|
||||
statusWindowThreshold: 80,
|
||||
status: "false" as MonitorStatus,
|
||||
});
|
||||
const { service, monitorsRepository } = createService();
|
||||
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
|
||||
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
|
||||
|
||||
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
|
||||
|
||||
expect(result.monitor.status).toBe("down");
|
||||
expect(result.statusChanged).toBe(true); // down must surface so an incident is opened
|
||||
});
|
||||
|
||||
it("during warmup, sub-threshold raw checks no longer flip stored status (regression: down-during-init incident not resolving)", async () => {
|
||||
// Once the monitor has left 'initializing', the warmup branch must NOT silently
|
||||
// flip status from raw checks. Otherwise a down check during warmup writes
|
||||
|
||||
Reference in new issue
Block a user