Files
Checkmate/server/test/unit/providers/network/advancedMatcher.test.ts
T
2026-07-06 09:39:59 -07:00

257 lines
9.9 KiB
TypeScript

import { describe, expect, it, jest } from "@jest/globals";
import { AdvancedMatcher } from "../../../../src/service/network/AdvancedMatcher.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const createMockJmespath = () => ({
search: jest.fn((data: any, expr: string) => {
// Simple dot-path evaluation for tests
return expr.split(".").reduce((acc: any, key: string) => acc?.[key], data);
}),
});
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
useAdvancedMatching: false,
jsonPath: undefined,
matchMethod: undefined,
expectedValue: undefined,
...overrides,
}) as Monitor;
// ── Tests ────────────────────────────────────────────────────────────────────
describe("AdvancedMatcher", () => {
describe("validate", () => {
it("returns ok when advanced matching is disabled", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate({ data: "test" }, makeMonitor({ useAdvancedMatching: false }));
expect(result).toEqual({ ok: true, message: "Success" });
});
// ── jsonPath extraction ──────────────────────────────────────────
describe("jsonPath extraction", () => {
it("extracts value using jsonPath", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate({ status: "healthy" }, makeMonitor({ useAdvancedMatching: true, jsonPath: "status" }));
expect(result).toEqual({ ok: true, message: "Success", extracted: "healthy" });
});
it("returns error when jsonPath evaluation throws", () => {
const jmespath = {
search: jest.fn().mockImplementation(() => {
throw new Error("bad expression");
}),
};
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({ data: "test" }, makeMonitor({ useAdvancedMatching: true, jsonPath: "invalid[" }));
expect(result).toEqual({ ok: false, message: "Error evaluating JSON path" });
});
});
// ── expectedValue matching ───────────────────────────────────────
describe("expectedValue with matchMethod", () => {
it("matches with 'equal' method", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ status: "ok" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "status", matchMethod: "equal", expectedValue: "ok" })
);
expect(result.ok).toBe(true);
});
it("fails with 'equal' method on mismatch", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ status: "error" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "status", matchMethod: "equal", expectedValue: "ok" })
);
expect(result.ok).toBe(false);
expect(result.message).toBe("Expected value did not match");
});
it("matches with 'include' method", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ msg: "server is healthy" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "msg", matchMethod: "include", expectedValue: "healthy" })
);
expect(result.ok).toBe(true);
});
it("fails with 'include' method when substring not found", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ msg: "server is down" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "msg", matchMethod: "include", expectedValue: "healthy" })
);
expect(result.ok).toBe(false);
});
it("matches with 'regex' method", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ code: "200" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "code", matchMethod: "regex", expectedValue: "^2\\d{2}$" })
);
expect(result.ok).toBe(true);
});
it("fails with 'regex' method on mismatch", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ code: "500" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "code", matchMethod: "regex", expectedValue: "^2\\d{2}$" })
);
expect(result.ok).toBe(false);
});
it("returns a clear error when the regex pattern is rejected by RE2", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ code: "200" },
// Backreferences are unsupported by RE2 and previously ReDoS-prone; RE2 throws on construction.
makeMonitor({ useAdvancedMatching: true, jsonPath: "code", matchMethod: "regex", expectedValue: "(a+)\\1" })
);
expect(result.ok).toBe(false);
expect(result.message).toBe("Invalid regex pattern");
expect(result.extracted).toBe("200");
});
it("returns a distinct error when the extracted value cannot be coerced to a string", () => {
// An object with a throwing toString/valueOf (like Object.create(null)) makes String() throw.
const unstringifiable = { toString: () => { throw new Error("nope"); } };
const jmespath = { search: jest.fn().mockReturnValue(unstringifiable) };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate(
{},
makeMonitor({ useAdvancedMatching: true, jsonPath: "field", matchMethod: "equal", expectedValue: "x" })
);
expect(result.ok).toBe(false);
expect(result.message).toBe("Error evaluating response value");
});
it("defaults to 'equal' comparison when matchMethod is undefined", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate({ val: "exact" }, makeMonitor({ useAdvancedMatching: true, jsonPath: "val", expectedValue: "exact" }));
expect(result.ok).toBe(true);
});
it("defaults to 'equal' comparison for unknown matchMethod", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ val: "exact" },
makeMonitor({ useAdvancedMatching: true, jsonPath: "val", matchMethod: "unknown" as any, expectedValue: "exact" })
);
expect(result.ok).toBe(true);
});
it("compares entire payload when jsonPath is not set", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
"hello",
makeMonitor({ useAdvancedMatching: true, jsonPath: undefined, matchMethod: "equal", expectedValue: "hello" })
);
expect(result.ok).toBe(true);
expect(result.extracted).toBe("hello");
});
it("includes extracted value in result", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate(
{ nested: { value: "42" } },
makeMonitor({ useAdvancedMatching: true, jsonPath: "nested.value", matchMethod: "equal", expectedValue: "42" })
);
expect(result.extracted).toBe("42");
});
});
// ── falsy value check (no expectedValue) ────────────────────────
describe("falsy value check (no expectedValue)", () => {
it("returns ok for truthy extracted value", () => {
const matcher = new AdvancedMatcher(createMockJmespath() as any);
const result = matcher.validate({ status: "up" }, makeMonitor({ useAdvancedMatching: true, jsonPath: "status" }));
expect(result.ok).toBe(true);
expect(result.extracted).toBe("up");
});
it("returns not ok for false", () => {
const jmespath = { search: jest.fn().mockReturnValue(false) };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({}, makeMonitor({ useAdvancedMatching: true, jsonPath: "missing" }));
expect(result.ok).toBe(false);
expect(result.message).toBe("Extracted value is falsy");
});
it("returns not ok for string 'false'", () => {
const jmespath = { search: jest.fn().mockReturnValue("false") };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({}, makeMonitor({ useAdvancedMatching: true, jsonPath: "field" }));
expect(result.ok).toBe(false);
});
it("returns not ok for undefined", () => {
const jmespath = { search: jest.fn().mockReturnValue(undefined) };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({}, makeMonitor({ useAdvancedMatching: true, jsonPath: "field" }));
expect(result.ok).toBe(false);
});
it("returns not ok for null", () => {
const jmespath = { search: jest.fn().mockReturnValue(null) };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({}, makeMonitor({ useAdvancedMatching: true, jsonPath: "field" }));
expect(result.ok).toBe(false);
});
it("returns ok for zero (not in falsy list)", () => {
const jmespath = { search: jest.fn().mockReturnValue(0) };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({}, makeMonitor({ useAdvancedMatching: true, jsonPath: "field" }));
expect(result.ok).toBe(true);
});
it("returns ok for empty string (not in falsy list)", () => {
const jmespath = { search: jest.fn().mockReturnValue("") };
const matcher = new AdvancedMatcher(jmespath as any);
const result = matcher.validate({}, makeMonitor({ useAdvancedMatching: true, jsonPath: "field" }));
expect(result.ok).toBe(true);
});
});
});
});