214 lines
9.3 KiB
TypeScript
214 lines
9.3 KiB
TypeScript
import { describe, expect, it, jest } from "@jest/globals";
|
|
import { CheckProducer } from "../../../src/worker/worker.check-producer.ts";
|
|
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
|
|
import { createMockLogger } from "../../helpers/createMockLogger.ts";
|
|
|
|
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
|
|
({
|
|
id: "m1",
|
|
teamId: "team",
|
|
type: "http",
|
|
interval: 60000,
|
|
status: "up",
|
|
...overrides,
|
|
}) as Monitor;
|
|
|
|
// A maintenance window straddling "now" — active per isWindowActive.
|
|
const activeWindow = () => {
|
|
const now = Date.now();
|
|
return { active: true, start: new Date(now - 1000).toISOString(), end: new Date(now + 1000).toISOString(), repeat: 0 };
|
|
};
|
|
|
|
const createProducer = (overrides?: Record<string, any>) => {
|
|
const defaults = {
|
|
logger: createMockLogger(),
|
|
monitorsRepository: { updateById: jest.fn().mockResolvedValue({}), findDockerTlsKeyById: jest.fn().mockResolvedValue(null) },
|
|
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([]) },
|
|
checkService: { toCheck: jest.fn().mockReturnValue({ id: "check-1" }) },
|
|
networkService: { requestStatus: jest.fn().mockResolvedValue({ monitorId: "m1", status: true, code: 200, message: "OK" }) },
|
|
proxyResolver: { resolve: jest.fn().mockResolvedValue(undefined) },
|
|
buffer: { addToBuffer: jest.fn() },
|
|
dockerLogsService: { buildDockerLogs: jest.fn().mockResolvedValue([]) },
|
|
...overrides,
|
|
};
|
|
const producer = new CheckProducer(
|
|
defaults.monitorsRepository as any,
|
|
defaults.maintenanceWindowsRepository as any,
|
|
defaults.checkService as any,
|
|
defaults.networkService as any,
|
|
defaults.proxyResolver as any,
|
|
defaults.buffer as any,
|
|
defaults.dockerLogsService as any,
|
|
defaults.logger as any
|
|
);
|
|
return { producer, defaults };
|
|
};
|
|
|
|
describe("CheckProducer", () => {
|
|
it("throws when monitor id is missing", async () => {
|
|
const { producer } = createProducer();
|
|
await expect(producer.produce({} as Monitor)).rejects.toThrow("No monitor id");
|
|
});
|
|
|
|
// ── maintenance gate ──────────────────────────────────────────────────────
|
|
|
|
it("skips the check and flips status to 'maintenance' when in a maintenance window", async () => {
|
|
const { producer, defaults } = createProducer({
|
|
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([activeWindow()]) },
|
|
});
|
|
|
|
const result = await producer.produce(makeMonitor({ status: "up" }));
|
|
|
|
expect(result).toBeNull();
|
|
expect(defaults.monitorsRepository.updateById).toHaveBeenCalledWith("m1", "team", { status: "maintenance", statusWindow: [] });
|
|
expect(defaults.networkService.requestStatus).not.toHaveBeenCalled();
|
|
expect(defaults.buffer.addToBuffer).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not re-write status when already in maintenance", async () => {
|
|
const { producer, defaults } = createProducer({
|
|
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([activeWindow()]) },
|
|
});
|
|
|
|
const result = await producer.produce(makeMonitor({ status: "maintenance" }));
|
|
|
|
expect(result).toBeNull();
|
|
expect(defaults.monitorsRepository.updateById).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("proceeds with the check when the maintenance window is inactive", async () => {
|
|
const { producer, defaults } = createProducer({
|
|
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([{ ...activeWindow(), active: false }]) },
|
|
});
|
|
|
|
await producer.produce(makeMonitor());
|
|
|
|
expect(defaults.networkService.requestStatus).toHaveBeenCalled();
|
|
});
|
|
|
|
// ── proxy resolution ──────────────────────────────────────────────────────
|
|
|
|
it("passes the resolved proxy url into requestStatus", async () => {
|
|
const { producer, defaults } = createProducer({
|
|
proxyResolver: { resolve: jest.fn().mockResolvedValue("http://proxy.example.com:8080") },
|
|
});
|
|
const monitor = makeMonitor();
|
|
|
|
await producer.produce(monitor);
|
|
|
|
expect(defaults.networkService.requestStatus).toHaveBeenCalledWith(monitor, { proxyUrl: "http://proxy.example.com:8080" });
|
|
});
|
|
|
|
it("still produces a check when the resolver returns undefined", async () => {
|
|
const { producer, defaults } = createProducer();
|
|
const monitor = makeMonitor();
|
|
|
|
const result = await producer.produce(monitor);
|
|
|
|
expect(defaults.networkService.requestStatus).toHaveBeenCalledWith(monitor, { proxyUrl: undefined });
|
|
expect(result).toEqual({ status: expect.objectContaining({ monitorId: "m1" }), check: { id: "check-1" } });
|
|
});
|
|
|
|
// ── acquire / record ──────────────────────────────────────────────────────
|
|
|
|
// ── docker tls key resolution ─────────────────────────────────────────────
|
|
|
|
it("fetches the stored key for a docker monitor with a key set and passes it in the context", async () => {
|
|
const { producer, defaults } = createProducer({
|
|
monitorsRepository: { updateById: jest.fn(), findDockerTlsKeyById: jest.fn().mockResolvedValue("v1.abc123.iv.tag.data") },
|
|
});
|
|
const monitor = makeMonitor({ type: "docker", url: "tcp://host", dockerTlsKeySet: true });
|
|
|
|
await producer.produce(monitor);
|
|
|
|
expect(defaults.monitorsRepository.findDockerTlsKeyById).toHaveBeenCalledWith("m1");
|
|
expect(defaults.networkService.requestStatus).toHaveBeenCalledWith(monitor, { proxyUrl: undefined, dockerTlsKey: "v1.abc123.iv.tag.data" });
|
|
});
|
|
|
|
it("does not fetch a key for a docker monitor without one set", async () => {
|
|
const { producer, defaults } = createProducer();
|
|
const monitor = makeMonitor({ type: "docker", url: "unix:///var/run/docker.sock", dockerTlsKeySet: false });
|
|
|
|
await producer.produce(monitor);
|
|
|
|
expect(defaults.monitorsRepository.findDockerTlsKeyById).not.toHaveBeenCalled();
|
|
expect(defaults.networkService.requestStatus).toHaveBeenCalledWith(monitor, { proxyUrl: undefined, dockerTlsKey: undefined });
|
|
});
|
|
|
|
it("does not fetch a key for non-docker monitors even if the flag is set", async () => {
|
|
const { producer, defaults } = createProducer();
|
|
|
|
await producer.produce(makeMonitor({ type: "http", dockerTlsKeySet: true }));
|
|
|
|
expect(defaults.monitorsRepository.findDockerTlsKeyById).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("passes undefined when the flag is set but the repository has no key", async () => {
|
|
const { producer, defaults } = createProducer();
|
|
const monitor = makeMonitor({ type: "docker", url: "tcp://host", dockerTlsKeySet: true });
|
|
|
|
await producer.produce(monitor);
|
|
|
|
expect(defaults.monitorsRepository.findDockerTlsKeyById).toHaveBeenCalledWith("m1");
|
|
expect(defaults.networkService.requestStatus).toHaveBeenCalledWith(monitor, { proxyUrl: undefined, dockerTlsKey: undefined });
|
|
});
|
|
|
|
it("throws when the network response is null", async () => {
|
|
const { producer } = createProducer({
|
|
networkService: { requestStatus: jest.fn().mockResolvedValue(null) },
|
|
});
|
|
|
|
await expect(producer.produce(makeMonitor())).rejects.toThrow("No network response");
|
|
});
|
|
|
|
it("returns null, warns, and does not buffer when toCheck yields nothing", async () => {
|
|
const { producer, defaults } = createProducer({
|
|
checkService: { toCheck: jest.fn().mockReturnValue(null) },
|
|
});
|
|
|
|
const result = await producer.produce(makeMonitor());
|
|
|
|
expect(result).toBeNull();
|
|
expect(defaults.logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining("No check could be built") }));
|
|
expect(defaults.buffer.addToBuffer).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("buffers the built check and returns the status and check on success", async () => {
|
|
const status = { monitorId: "m1", status: false, code: 500, message: "Error" };
|
|
const check = { id: "check-1" };
|
|
const { producer, defaults } = createProducer({
|
|
networkService: { requestStatus: jest.fn().mockResolvedValue(status) },
|
|
checkService: { toCheck: jest.fn().mockReturnValue(check) },
|
|
});
|
|
|
|
const result = await producer.produce(makeMonitor());
|
|
|
|
expect(defaults.buffer.addToBuffer).toHaveBeenCalledWith(check);
|
|
expect(result).toEqual({ status, check });
|
|
});
|
|
|
|
it("builds and buffers docker logs for a docker status", async () => {
|
|
const status = { type: "docker", monitorId: "m1", teamId: "team", status: true, code: 200, message: "OK", payload: {} };
|
|
const dockerLogs = [{ id: "docker-log-1" }, { id: "docker-log-2" }];
|
|
const { producer, defaults } = createProducer({
|
|
networkService: { requestStatus: jest.fn().mockResolvedValue(status) },
|
|
buffer: { addToBuffer: jest.fn(), addDockerLogToBuffer: jest.fn() },
|
|
dockerLogsService: { buildDockerLogs: jest.fn().mockResolvedValue(dockerLogs) },
|
|
});
|
|
|
|
await producer.produce(makeMonitor({ type: "docker" }));
|
|
|
|
expect(defaults.dockerLogsService.buildDockerLogs).toHaveBeenCalledWith(status);
|
|
expect(defaults.buffer.addDockerLogToBuffer).toHaveBeenNthCalledWith(1, dockerLogs[0]);
|
|
expect(defaults.buffer.addDockerLogToBuffer).toHaveBeenNthCalledWith(2, dockerLogs[1]);
|
|
});
|
|
|
|
it("does not build docker logs for a non-docker status", async () => {
|
|
const { producer, defaults } = createProducer();
|
|
|
|
await producer.produce(makeMonitor());
|
|
|
|
expect(defaults.dockerLogsService.buildDockerLogs).not.toHaveBeenCalled();
|
|
});
|
|
});
|