mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 23:00:22 +00:00
feat(gateway): add channel-backed readiness probes (#38285)
* Changelog: add channel-backed readiness probe entry * Gateway: add channel-backed readiness probes * Docs: describe readiness probe behavior * Gateway: add readiness probe regression tests * Changelog: dedupe gateway probe entries * Docs: fix readiness startup grace description * Changelog: remove stale readiness entry * Gateway: cover readiness hardening * Gateway: harden readiness probes
This commit is contained in:
155
src/gateway/server-http.probe.test.ts
Normal file
155
src/gateway/server-http.probe.test.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
AUTH_TOKEN,
|
||||
AUTH_NONE,
|
||||
createRequest,
|
||||
createResponse,
|
||||
dispatchRequest,
|
||||
withGatewayServer,
|
||||
} from "./server-http.test-harness.js";
|
||||
import type { ReadinessChecker } from "./server/readiness.js";
|
||||
|
||||
describe("gateway probe endpoints", () => {
|
||||
it("returns detailed readiness payload for local /ready requests", async () => {
|
||||
const getReadiness: ReadinessChecker = () => ({
|
||||
ready: true,
|
||||
failing: [],
|
||||
uptimeMs: 45_000,
|
||||
});
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "probe-ready",
|
||||
resolvedAuth: AUTH_NONE,
|
||||
overrides: { getReadiness },
|
||||
run: async (server) => {
|
||||
const req = createRequest({ path: "/ready" });
|
||||
const { res, getBody } = createResponse();
|
||||
await dispatchRequest(server, req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(JSON.parse(getBody())).toEqual({ ready: true, failing: [], uptimeMs: 45_000 });
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns only readiness state for unauthenticated remote /ready requests", async () => {
|
||||
const getReadiness: ReadinessChecker = () => ({
|
||||
ready: false,
|
||||
failing: ["discord", "telegram"],
|
||||
uptimeMs: 8_000,
|
||||
});
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "probe-not-ready",
|
||||
resolvedAuth: AUTH_NONE,
|
||||
overrides: { getReadiness },
|
||||
run: async (server) => {
|
||||
const req = createRequest({
|
||||
path: "/ready",
|
||||
remoteAddress: "10.0.0.8",
|
||||
host: "gateway.test",
|
||||
});
|
||||
const { res, getBody } = createResponse();
|
||||
await dispatchRequest(server, req, res);
|
||||
|
||||
expect(res.statusCode).toBe(503);
|
||||
expect(JSON.parse(getBody())).toEqual({ ready: false });
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns detailed readiness payload for authenticated remote /ready requests", async () => {
|
||||
const getReadiness: ReadinessChecker = () => ({
|
||||
ready: false,
|
||||
failing: ["discord", "telegram"],
|
||||
uptimeMs: 8_000,
|
||||
});
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "probe-remote-authenticated",
|
||||
resolvedAuth: AUTH_TOKEN,
|
||||
overrides: { getReadiness },
|
||||
run: async (server) => {
|
||||
const req = createRequest({
|
||||
path: "/ready",
|
||||
remoteAddress: "10.0.0.8",
|
||||
host: "gateway.test",
|
||||
authorization: "Bearer test-token",
|
||||
});
|
||||
const { res, getBody } = createResponse();
|
||||
await dispatchRequest(server, req, res);
|
||||
|
||||
expect(res.statusCode).toBe(503);
|
||||
expect(JSON.parse(getBody())).toEqual({
|
||||
ready: false,
|
||||
failing: ["discord", "telegram"],
|
||||
uptimeMs: 8_000,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns typed internal error payload when readiness evaluation throws", async () => {
|
||||
const getReadiness: ReadinessChecker = () => {
|
||||
throw new Error("boom");
|
||||
};
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "probe-throws",
|
||||
resolvedAuth: AUTH_NONE,
|
||||
overrides: { getReadiness },
|
||||
run: async (server) => {
|
||||
const req = createRequest({ path: "/ready" });
|
||||
const { res, getBody } = createResponse();
|
||||
await dispatchRequest(server, req, res);
|
||||
|
||||
expect(res.statusCode).toBe(503);
|
||||
expect(JSON.parse(getBody())).toEqual({ ready: false, failing: ["internal"], uptimeMs: 0 });
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps /healthz shallow even when readiness checker reports failing channels", async () => {
|
||||
const getReadiness: ReadinessChecker = () => ({
|
||||
ready: false,
|
||||
failing: ["discord"],
|
||||
uptimeMs: 999,
|
||||
});
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "probe-healthz-unaffected",
|
||||
resolvedAuth: AUTH_NONE,
|
||||
overrides: { getReadiness },
|
||||
run: async (server) => {
|
||||
const req = createRequest({ path: "/healthz" });
|
||||
const { res, getBody } = createResponse();
|
||||
await dispatchRequest(server, req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(getBody()).toBe(JSON.stringify({ ok: true, status: "live" }));
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reflects readiness status on HEAD /readyz without a response body", async () => {
|
||||
const getReadiness: ReadinessChecker = () => ({
|
||||
ready: false,
|
||||
failing: ["discord"],
|
||||
uptimeMs: 5_000,
|
||||
});
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "probe-readyz-head",
|
||||
resolvedAuth: AUTH_NONE,
|
||||
overrides: { getReadiness },
|
||||
run: async (server) => {
|
||||
const req = createRequest({ path: "/readyz", method: "HEAD" });
|
||||
const { res, getBody } = createResponse();
|
||||
await dispatchRequest(server, req, res);
|
||||
|
||||
expect(res.statusCode).toBe(503);
|
||||
expect(getBody()).toBe("");
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user