Files
Termix/plugins/automations/tests/backend/docker-watcher.test.ts
T

109 lines
3.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
diffContainerStates,
parseContainerStates,
} from "../../src/backend/docker-watcher.js";
/**
* The polling side needs SSH, so what is tested here is the pure part: turning
* `docker ps` output into states, and turning two snapshots into events.
*/
describe("parseContainerStates", () => {
it("reads name, state and health out of the ps output", () => {
const output = [
'{"name":"web","state":"running","status":"Up 2 hours"}',
'{"name":"db","state":"exited","status":"Exited (0) 5 minutes ago"}',
'{"name":"api","state":"running","status":"Up 1 hour (unhealthy)"}',
].join("\n");
const states = parseContainerStates(output);
expect(states.get("web")).toEqual({ state: "running", unhealthy: false });
expect(states.get("db")).toEqual({ state: "exited", unhealthy: false });
expect(states.get("api")).toEqual({ state: "running", unhealthy: true });
});
it("skips blank and malformed lines rather than failing the poll", () => {
const output = [
'{"name":"web","state":"running","status":"Up"}',
"",
"not json at all",
'{"state":"running"}',
].join("\n");
const states = parseContainerStates(output);
expect([...states.keys()]).toEqual(["web"]);
});
it("returns nothing for empty output", () => {
expect(parseContainerStates("").size).toBe(0);
});
});
describe("diffContainerStates", () => {
const running = { state: "running", unhealthy: false };
const exited = { state: "exited", unhealthy: false };
it("reports a container that stopped", () => {
const events = diffContainerStates(
new Map([["web", running]]),
new Map([["web", exited]]),
);
expect(events).toEqual([{ container: "web", event: "exited" }]);
});
it("reports a container that started", () => {
const events = diffContainerStates(
new Map([["web", exited]]),
new Map([["web", running]]),
);
expect(events).toEqual([{ container: "web", event: "started" }]);
});
it("reports a container that began restarting", () => {
const events = diffContainerStates(
new Map([["web", running]]),
new Map([["web", { state: "restarting", unhealthy: false }]]),
);
expect(events).toEqual([{ container: "web", event: "restarting" }]);
});
it("reports a container that went unhealthy while still running", () => {
const events = diffContainerStates(
new Map([["web", running]]),
new Map([["web", { state: "running", unhealthy: true }]]),
);
expect(events).toEqual([{ container: "web", event: "unhealthy" }]);
});
it("stays quiet when nothing changed", () => {
const events = diffContainerStates(
new Map([["web", running]]),
new Map([["web", running]]),
);
expect(events).toEqual([]);
});
it("does not re-announce a container that is still unhealthy", () => {
const unhealthy = { state: "running", unhealthy: true };
const events = diffContainerStates(
new Map([["web", unhealthy]]),
new Map([["web", unhealthy]]),
);
expect(events).toEqual([]);
});
// A first sighting is a baseline, not an event: otherwise every container
// running at boot would report itself as freshly started.
it("treats a newly seen container as a baseline", () => {
const events = diffContainerStates(new Map(), new Map([["web", running]]));
expect(events).toEqual([]);
});
it("ignores a container that disappeared", () => {
const events = diffContainerStates(new Map([["web", running]]), new Map());
expect(events).toEqual([]);
});
});