Spaces:
Running
Running
File size: 4,622 Bytes
6c3af4e e759542 ccc76d6 6c3af4e ccc76d6 e759542 af13576 6c3af4e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | 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();
}
});
});
|