MiniSearch / server /statusEndpointServerHook.test.ts
system's picture
system HF Staff
Sync from felladrin/MiniSearch@fd8be33 (part 2)
e759542 verified
Raw History Blame Contribute Delete
4.62 kB
import type { IncomingMessage, ServerResponse } from "node:http";
import { describe, expect, it, vi } from "vitest";
import { getFallbackProviderStats } from "./fallbackSearchService.ts";
import {
incrementSearchesFailedOnFallback,
incrementSearchesServedByFallback,
} from "./searchesSinceLastRestart.ts";
import { statusEndpointServerHook } from "./statusEndpointServerHook.ts";
// The health getters probe live services; this test is about the payload,
// so they answer healthy without a network.
vi.mock("./biEncoderService.ts", () => ({
getBiEncoderStatus: async () => true,
}));
vi.mock("./rerankerService.ts", () => ({
getRerankerStatus: async () => true,
}));
vi.mock("./webSearchService.ts", () => ({
getWebSearchServiceStatus: async () => "degraded",
getSearchCircuitStats: () => ({
circuitState: "closed",
circuitOpens: 0,
}),
}));
type Handler = (
request: IncomingMessage,
response: ServerResponse,
next: () => void,
) => Promise<void>;
type RecordedResponse = ServerResponse & {
setHeader: ReturnType<typeof vi.fn>;
end: ReturnType<typeof vi.fn>;
};
function callStatus(): Promise<Record<string, unknown>> {
const use = vi.fn();
// Vite sets VITE_BUILD_DATE_TIME for every serve (see vite.config.ts).
statusEndpointServerHook({
middlewares: { use },
config: { define: { VITE_BUILD_DATE_TIME: new Date().toISOString() } },
} as unknown as Parameters<typeof statusEndpointServerHook>[0]);
const response = {
setHeader: vi.fn(),
end: vi.fn(),
} as unknown as RecordedResponse;
const request = {
url: "/status",
headers: {},
} as unknown as IncomingMessage;
return (use.mock.calls[0][0] as Handler)(
request,
response,
() => undefined,
).then(() => JSON.parse(response.end.mock.calls[0][0]));
}
describe("statusEndpointServerHook", () => {
it("publishes the web search verdict and the per-engine tally", async () => {
const status = await callStatus();
// Passed through rather than recomputed: the hook used to derive this
// field itself from a liveness probe, which reported an open circuit as
// healthy.
expect(status.webSearchServiceStatus).toBe("degraded");
expect(status.searches).toMatchObject({
degradedSearchTypes: [],
unresponsiveEngines: {},
});
});
it("publishes the two fallback counters as plain counts", async () => {
const before = await callStatus();
incrementSearchesServedByFallback();
incrementSearchesFailedOnFallback();
incrementSearchesFailedOnFallback();
const status = await callStatus();
// Exact values rather than expect.any(Number), which a pair of swapped
// getters would satisfy too. The counters are module state that no test
// here resets, so the deltas are what each getter moved.
expect(status.searchesServedByFallback).toBe(
(before.searchesServedByFallback as number) + 1,
);
expect(status.searchesFailedOnFallback).toBe(
(before.searchesFailedOnFallback as number) + 2,
);
});
it("publishes the per-provider fallback counters under searches", async () => {
const status = await callStatus();
expect(status.searches).toMatchObject({
fallbackProviders: getFallbackProviderStats(),
});
expect(
Object.keys(
(status.searches as { fallbackProviders: object }).fallbackProviders,
),
).toEqual(["parallel", "youcom"]);
});
it("publishes the page-read circuit count beside the outcome counters", async () => {
const status = await callStatus();
// Counts and nothing else: the hosts the breaker boxes come from search
// results, so naming one here would say something about a query.
expect(status.pageReads).toMatchObject({
circuitOpens: expect.any(Number),
skipped: { skippedByBreaker: expect.any(Number) },
});
});
it("keeps startedAt fixed at the restart while uptime advances", async () => {
const first = await callStatus();
expect(typeof first.startedAt).toBe("string");
const startedAt = Date.parse(first.startedAt as string);
expect(Number.isNaN(startedAt)).toBe(false);
vi.useFakeTimers();
try {
vi.setSystemTime(startedAt + 600_000);
const later = await callStatus();
// The module captured its start once, on import: startedAt is the same
// instant on every call while uptime advances. Replacing the capture
// with a per-call Date.now() fails this.
expect(later.startedAt).toBe(first.startedAt);
expect(later.uptime).toBe("10 minutes");
} finally {
vi.useRealTimers();
}
});
});