import { validateActivitySearch, validateBlocked, validateMode, validateText, validateTimestamp, MAX_CLIENTS, validateClients, } from "./search"; test("mode is the two-value union, defaulting to history", () => { expect(validateMode("live")).toBe("live"); expect(validateMode("history")).toBe("history"); expect(validateMode(undefined)).toBe("history"); expect(validateMode("Live")).toBe("history"); expect(validateMode("")).toBe("history"); expect(validateMode(0)).toBe("history"); expect(validateMode(["live"])).toBe("history"); }); test("a bound is a safe integer or nothing at all", () => { expect(validateTimestamp(1_700_000_000)).toBe(1_700_000_000); expect(validateTimestamp(0)).toBe(0); expect(validateTimestamp(-1)).toBe(-1); }); test.each([ ["a fraction", 1_700_000_000.5], ["Infinity", Number.POSITIVE_INFINITY], ["-Infinity", Number.NEGATIVE_INFINITY], ["NaN", Number.NaN], ["past the safe range", Number.MAX_SAFE_INTEGER + 1], ["1e21", 1e21], ["a numeric string", "1700000000"], ["an empty string", ""], ["null", null], ["undefined", undefined], ["a boolean", true], ["an array", [1_700_000_000]], ["a bigint", 1_700_000_000n], ])("a bound rejects %s", (_name, value) => { expect(validateTimestamp(value)).toBeUndefined(); }); test("blocked keeps false, which is the allowed-only filter", () => { expect(validateBlocked(true)).toBe(true); expect(validateBlocked(false)).toBe(false); }); test.each([ ["the string true", "true"], ["the string false", "false"], ["1", 1], ["0", 0], ["null", null], ["undefined", undefined], ])("blocked rejects %s", (_name, value) => { expect(validateBlocked(value)).toBeUndefined(); }); test("a text filter is trimmed, and an empty one is no filter", () => { expect(validateText("ads.example")).toBe("ads.example"); expect(validateText(" ads.example ")).toBe("ads.example"); expect(validateText("")).toBeUndefined(); expect(validateText(" ")).toBeUndefined(); expect(validateText("\t\n")).toBeUndefined(); expect(validateText(42)).toBeUndefined(); expect(validateText(undefined)).toBeUndefined(); }); test("a whole search normalizes every field and drops nothing else in", () => { expect( validateActivitySearch({ mode: "live", since: 1_700_000_000, until: 1_700_000_600, domain: " ads.example ", client: "192.0.2.10", blocked: false, unknown: "kept out", }), ).toEqual({ mode: "live", since: 1_700_000_000, until: 1_700_000_600, domain: "ads.example", client: "192.0.2.10", blocked: false, }); }); test("an empty search is history with no filters", () => { expect(validateActivitySearch({})).toEqual({ mode: "history", since: undefined, until: undefined, domain: undefined, client: undefined, blocked: undefined, }); }); test("a search of junk applies nothing", () => { expect( validateActivitySearch({ mode: "HISTORY ", since: "1700000000", until: Number.POSITIVE_INFINITY, domain: " ", client: null, blocked: "true", }), ).toEqual({ mode: "history", since: undefined, until: undefined, domain: undefined, client: undefined, blocked: undefined, }); }); test("the client list is canonicalized once, so the chips and the request agree", () => { expect(validateClients("192.0.2.10,192.0.2.11")).toBe("192.0.2.10,192.0.2.11"); // Blanks name no client and a repeat asks for the same client twice, so // neither changes which rows come back: dropping them is the same filter // written once, not a different one. expect(validateClients(" 192.0.2.10 , ,192.0.2.11,192.0.2.10,")).toBe("192.0.2.10,192.0.2.11"); expect(validateClients(",,")).toBeUndefined(); expect(validateClients("")).toBeUndefined(); expect(validateClients(null)).toBeUndefined(); }); test("a pasted list past the cap is cut to what the api will accept", () => { const addresses = Array.from({ length: MAX_CLIENTS + 8 }, (_, index) => `198.51.100.${index + 1}`); // The API refuses a longer list outright, so keeping the extra addresses // would show a filter that cannot be applied at all. expect(validateClients(addresses.join(","))).toBe(addresses.slice(0, MAX_CLIENTS).join(",")); });