mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
test: move core coverage to owner boundaries (#124184)
* test: move core coverage to owner boundaries * ci: lower plugin SDK surface budget
This commit is contained in:
committed by
GitHub
parent
9663cc7edc
commit
b365f7ec89
@@ -284,7 +284,8 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env
|
||||
// +6: load-only bridges for published pre-split plugin artifacts
|
||||
// (voice-call/matrix runtime-doctor repair names, WhatsApp ack policy,
|
||||
// Slack progress-draft render) so installed plugins survive upgrade (#124041 class).
|
||||
4330,
|
||||
// -1: remove the orphan diagnostic traceparent propagation export.
|
||||
4329,
|
||||
env,
|
||||
),
|
||||
publicFunctionExports: readPluginSdkSurfaceBudgetEnv(
|
||||
@@ -357,7 +358,8 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env
|
||||
// +3: load-only bridges for published pre-split plugin artifacts
|
||||
// (voice-call/matrix runtime-doctor repair names, WhatsApp ack policy,
|
||||
// Slack progress-draft render) so installed plugins survive upgrade (#124041 class).
|
||||
2577,
|
||||
// -1: remove the orphan diagnostic traceparent propagation export.
|
||||
2576,
|
||||
env,
|
||||
),
|
||||
publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv(
|
||||
|
||||
@@ -360,10 +360,18 @@ describe("agentCliCommand", () => {
|
||||
expect(zeroTimeoutGatewayRequestMs).toBe(2_147_000_000);
|
||||
});
|
||||
|
||||
it("clamps oversized gateway timeout seconds", () => {
|
||||
expect(agentViaGatewayTesting.resolveGatewayAgentTimeoutMs(Number.MAX_SAFE_INTEGER)).toBe(
|
||||
MAX_TIMER_TIMEOUT_MS,
|
||||
);
|
||||
it("clamps oversized gateway timeout seconds at the command boundary", async () => {
|
||||
await withTempStore(async () => {
|
||||
mockGatewaySuccessReply();
|
||||
|
||||
await agentCliCommand(
|
||||
{ message: "hi", to: "+1555", timeout: String(Number.MAX_SAFE_INTEGER) },
|
||||
runtime,
|
||||
);
|
||||
|
||||
const request = requireFirstCallArg(callGateway, "gateway") as { timeoutMs?: number };
|
||||
expect(request.timeoutMs).toBe(MAX_TIMER_TIMEOUT_MS);
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects partial gateway timeout values", async () => {
|
||||
|
||||
@@ -357,7 +357,6 @@ export const agentViaGatewayTesting = {
|
||||
agentSessionModuleCache.clear();
|
||||
agentSessionModuleLoader = loader;
|
||||
},
|
||||
resolveGatewayAgentTimeoutMs,
|
||||
setGatewayAbortRetryDelaysMsForTests(delays?: readonly number[]): void {
|
||||
gatewayAbortRetryDelaysMsForTests = delays;
|
||||
},
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { probeDockerGatewayHealth, resolveDockerHealthcheckPort } from "./docker-healthcheck.js";
|
||||
import { probeDockerGatewayHealth } from "./docker-healthcheck.js";
|
||||
|
||||
describe("Docker healthcheck", () => {
|
||||
it("prefers the active Gateway lock port used by --port", async () => {
|
||||
it("probes the active Gateway lock port used by --port", async () => {
|
||||
const getRuntimeConfig = vi.fn(() => ({ gateway: { port: 19002 } }));
|
||||
const resolveGatewayPort = vi.fn(() => 19003);
|
||||
const fetch = vi.fn(async () => ({ ok: true }) as Response);
|
||||
|
||||
await expect(
|
||||
resolveDockerHealthcheckPort({
|
||||
probeDockerGatewayHealth({
|
||||
env: { OPENCLAW_GATEWAY_PORT: "19001" },
|
||||
fetch,
|
||||
getRuntimeConfig,
|
||||
readActiveGatewayLockPort: vi.fn(async () => 19000),
|
||||
resolveGatewayPort,
|
||||
}),
|
||||
).resolves.toBe(19000);
|
||||
).resolves.toBe(true);
|
||||
expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:19000/healthz");
|
||||
expect(getRuntimeConfig).not.toHaveBeenCalled();
|
||||
expect(resolveGatewayPort).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -31,30 +34,24 @@ describe("Docker healthcheck", () => {
|
||||
config: { gateway: { port: 19002 } },
|
||||
expected: 19002,
|
||||
},
|
||||
])("falls back to the canonical $name port", async ({ env, config, expected }) => {
|
||||
await expect(
|
||||
resolveDockerHealthcheckPort({
|
||||
env,
|
||||
getRuntimeConfig: () => config,
|
||||
readActiveGatewayLockPort: vi.fn(async () => undefined),
|
||||
}),
|
||||
).resolves.toBe(expected);
|
||||
});
|
||||
])(
|
||||
"probes the canonical $name port when no active lock exists",
|
||||
async ({ env, config, expected }) => {
|
||||
const fetch = vi.fn(async () => ({ ok: true }) as Response);
|
||||
|
||||
it("falls back to config when the active lock cannot be read", async () => {
|
||||
await expect(
|
||||
resolveDockerHealthcheckPort({
|
||||
env: {},
|
||||
getRuntimeConfig: () => ({ gateway: { port: 19002 } }),
|
||||
readActiveGatewayLockPort: vi.fn(async () => {
|
||||
throw new Error("lock unavailable");
|
||||
await expect(
|
||||
probeDockerGatewayHealth({
|
||||
env,
|
||||
fetch,
|
||||
getRuntimeConfig: () => config,
|
||||
readActiveGatewayLockPort: vi.fn(async () => undefined),
|
||||
}),
|
||||
resolveGatewayPort: (config) => config.gateway?.port ?? 18789,
|
||||
}),
|
||||
).resolves.toBe(19002);
|
||||
});
|
||||
).resolves.toBe(true);
|
||||
expect(fetch).toHaveBeenCalledWith(`http://127.0.0.1:${expected}/healthz`);
|
||||
},
|
||||
);
|
||||
|
||||
it("probes the unauthenticated liveness endpoint on the resolved port", async () => {
|
||||
it("probes the configured port when the active lock cannot be read", async () => {
|
||||
const fetch = vi.fn(async () => ({ ok: true }) as Response);
|
||||
|
||||
await expect(
|
||||
@@ -62,11 +59,13 @@ describe("Docker healthcheck", () => {
|
||||
env: {},
|
||||
fetch,
|
||||
getRuntimeConfig: () => ({ gateway: { port: 19002 } }),
|
||||
readActiveGatewayLockPort: vi.fn(async () => 19000),
|
||||
resolveGatewayPort: vi.fn(() => 19002),
|
||||
readActiveGatewayLockPort: vi.fn(async () => {
|
||||
throw new Error("lock unavailable");
|
||||
}),
|
||||
resolveGatewayPort: (config) => config.gateway?.port ?? 18789,
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:19000/healthz");
|
||||
expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:19002/healthz");
|
||||
});
|
||||
|
||||
it("reports an unsuccessful or unreachable liveness endpoint as unhealthy", async () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ type DockerHealthcheckDeps = Partial<DockerHealthcheckPortDeps> & {
|
||||
fetch?: typeof globalThis.fetch;
|
||||
};
|
||||
|
||||
export async function resolveDockerHealthcheckPort(
|
||||
async function resolveDockerHealthcheckPort(
|
||||
deps: Partial<DockerHealthcheckPortDeps> = {},
|
||||
): Promise<number> {
|
||||
const env = deps.env ?? process.env;
|
||||
|
||||
@@ -95,8 +95,8 @@ describe("gateway usage helpers", () => {
|
||||
endDate: string,
|
||||
) {
|
||||
const range = expectDateRange(result);
|
||||
expect(range.startMs).toBe(testApi.parseDateToMs(startDate));
|
||||
expect(range.endMs).toBe(testApi.parseDateToMs(endDate)! + dayMs - 1);
|
||||
expect(range.startMs).toBe(Date.parse(`${startDate}T00:00:00.000Z`));
|
||||
expect(range.endMs).toBe(Date.parse(`${endDate}T00:00:00.000Z`) + dayMs - 1);
|
||||
}
|
||||
|
||||
function expectDateRange(result: ReturnType<typeof testApi.resolveDateRange>) {
|
||||
@@ -128,26 +128,6 @@ describe("gateway usage helpers", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("parseDateToMs accepts YYYY-MM-DD and rejects invalid input", () => {
|
||||
expect(testApi.parseDateToMs("2026-02-05")).toBe(Date.UTC(2026, 1, 5));
|
||||
expect(testApi.parseDateToMs(" 2026-02-05 ")).toBe(Date.UTC(2026, 1, 5));
|
||||
expect(testApi.parseDateToMs("2026-2-5")).toBeUndefined();
|
||||
expect(testApi.parseDateToMs("nope")).toBeUndefined();
|
||||
expect(testApi.parseDateToMs(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parseDateToMs rejects out-of-range calendar dates instead of rolling them over", () => {
|
||||
// Impossible dates that still match the YYYY-MM-DD shape must not silently shift to a real day.
|
||||
expect(testApi.parseDateToMs("2026-02-30")).toBeUndefined(); // would roll to Mar 2
|
||||
expect(testApi.parseDateToMs("2026-04-31")).toBeUndefined(); // would roll to May 1
|
||||
expect(testApi.parseDateToMs("2025-02-29")).toBeUndefined(); // non-leap Feb 29
|
||||
expect(testApi.parseDateToMs("2026-13-01")).toBeUndefined(); // month too large
|
||||
expect(testApi.parseDateToMs("2026-00-10")).toBeUndefined(); // month zero
|
||||
expect(testApi.parseDateToMs("2026-01-00")).toBeUndefined(); // day zero
|
||||
// Real leap day must stay valid (guard against over-rejection).
|
||||
expect(testApi.parseDateToMs("2024-02-29")).toBe(Date.UTC(2024, 1, 29));
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ startDate: "2026-02-30" }, "invalid startDate"],
|
||||
[{ endDate: "2026-2-5" }, "invalid endDate"],
|
||||
@@ -275,34 +255,6 @@ describe("gateway usage helpers", () => {
|
||||
expect(vi.mocked(discoverAllSessions)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("parseUtcOffsetToMinutes supports whole-hour and half-hour offsets", () => {
|
||||
expect(testApi.parseUtcOffsetToMinutes("UTC-4")).toBe(-240);
|
||||
expect(testApi.parseUtcOffsetToMinutes("UTC+5:30")).toBe(330);
|
||||
expect(testApi.parseUtcOffsetToMinutes(" UTC+14 ")).toBe(14 * 60);
|
||||
});
|
||||
|
||||
it("parseUtcOffsetToMinutes rejects invalid offsets", () => {
|
||||
expect(testApi.parseUtcOffsetToMinutes("UTC+14:30")).toBeUndefined();
|
||||
expect(testApi.parseUtcOffsetToMinutes("UTC+5:99")).toBeUndefined();
|
||||
expect(testApi.parseUtcOffsetToMinutes("UTC+25")).toBeUndefined();
|
||||
expect(testApi.parseUtcOffsetToMinutes("GMT+5")).toBeUndefined();
|
||||
expect(testApi.parseUtcOffsetToMinutes(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parseDays coerces strings/numbers to integers", () => {
|
||||
expect(testApi.parseDays(7.9)).toBe(7);
|
||||
expect(testApi.parseDays("30")).toBe(30);
|
||||
expect(testApi.parseDays("")).toBeUndefined();
|
||||
expect(testApi.parseDays("nope")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parseDays caps day counts before Date arithmetic can overflow", () => {
|
||||
expect(testApi.parseDays(1e300)).toBe(36600);
|
||||
expect(testApi.parseDays("1e300")).toBe(36600);
|
||||
expect(testApi.parseDays(Number.MAX_SAFE_INTEGER)).toBe(36600);
|
||||
expect(testApi.parseDays(366 * 100)).toBe(36600);
|
||||
});
|
||||
|
||||
it("resolveDateRange uses explicit start/end as UTC when mode is missing (backward compatible)", () => {
|
||||
const result = testApi.resolveDateRange({
|
||||
startDate: "2026-02-01",
|
||||
@@ -311,13 +263,13 @@ describe("gateway usage helpers", () => {
|
||||
expectUtcDateRange(result, "2026-02-01", "2026-02-02");
|
||||
});
|
||||
|
||||
it("resolveDateRange uses explicit UTC mode", () => {
|
||||
it("resolveDateRange accepts a leap day in explicit UTC mode", () => {
|
||||
const result = testApi.resolveDateRange({
|
||||
startDate: "2026-02-01",
|
||||
endDate: "2026-02-02",
|
||||
startDate: "2024-02-29",
|
||||
endDate: "2024-03-01",
|
||||
mode: "utc",
|
||||
});
|
||||
expectUtcDateRange(result, "2026-02-01", "2026-02-02");
|
||||
expectUtcDateRange(result, "2024-02-29", "2024-03-01");
|
||||
});
|
||||
|
||||
it("resolveDateRange uses specific UTC offset for explicit dates", () => {
|
||||
@@ -389,7 +341,7 @@ describe("gateway usage helpers", () => {
|
||||
startDate: "2026-02-01",
|
||||
endDate: "2026-02-02",
|
||||
mode: "specific",
|
||||
utcOffset: "bad-value",
|
||||
utcOffset: "UTC+14:30",
|
||||
}),
|
||||
);
|
||||
expect(missingOffset.startMs).toBe(Date.UTC(2026, 1, 1));
|
||||
|
||||
@@ -522,21 +522,6 @@ const getDateParts = (date: Date, interpretation: DateInterpretation): DateParts
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a date string (YYYY-MM-DD) to start-of-day timestamp based on interpretation mode.
|
||||
* Returns undefined if invalid.
|
||||
*/
|
||||
const parseDateToMs = (
|
||||
raw: unknown,
|
||||
interpretation: DateInterpretation = { mode: "utc" },
|
||||
): number | undefined => {
|
||||
const parts = parseDateParts(raw);
|
||||
if (!parts) {
|
||||
return undefined;
|
||||
}
|
||||
return datePartsToStartMs(parts, interpretation);
|
||||
};
|
||||
|
||||
const formatDateLabel = (ms: number, interpretation: DateInterpretation): string => {
|
||||
const parts = getDateParts(new Date(ms), interpretation);
|
||||
return formatDateParts(parts.year, parts.monthIndex, parts.day);
|
||||
@@ -1141,9 +1126,6 @@ function mergeUsageCacheStatus(
|
||||
|
||||
// Exposed for unit tests (kept as a single export to avoid widening the public API surface).
|
||||
export const testApi = {
|
||||
parseUtcOffsetToMinutes,
|
||||
parseDateToMs,
|
||||
parseDays,
|
||||
resolveDateRange,
|
||||
loadCostUsageSummaryCached,
|
||||
costUsageCache,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
emitTrustedDiagnosticEventWithPrivateData,
|
||||
emitTrustedSkillUsedDiagnosticEvent,
|
||||
emitTrustedSecurityEvent,
|
||||
formatDiagnosticTraceparentForPropagation,
|
||||
hasPendingInternalDiagnosticEvent,
|
||||
isInternalDiagnosticEventMetadata,
|
||||
isDiagnosticsEnabled,
|
||||
@@ -246,38 +245,6 @@ describe("diagnostic-events", () => {
|
||||
expect(isInternalDiagnosticEventMetadata({ trusted: false })).toBe(false);
|
||||
});
|
||||
|
||||
it("formats traceparent for propagation only from dispatcher-trusted metadata", () => {
|
||||
const trace = createDiagnosticTraceContext({
|
||||
traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
|
||||
spanId: "00f067aa0ba902b7",
|
||||
traceFlags: "01",
|
||||
});
|
||||
const traceparents: Array<string | undefined> = [];
|
||||
onInternalDiagnosticEvent((event, metadata) => {
|
||||
traceparents.push(formatDiagnosticTraceparentForPropagation(event, metadata));
|
||||
});
|
||||
|
||||
emitDiagnosticEvent({
|
||||
type: "message.queued",
|
||||
source: "plugin",
|
||||
trace,
|
||||
});
|
||||
emitTrustedDiagnosticEvent({
|
||||
type: "model.usage",
|
||||
usage: { total: 1 },
|
||||
trace,
|
||||
});
|
||||
|
||||
expect(traceparents).toEqual([undefined, `00-${trace.traceId}-${trace.spanId}-01`]);
|
||||
expect(formatDiagnosticTraceparentForPropagation({ trace }, { trusted: true })).toBeUndefined();
|
||||
expect(
|
||||
formatDiagnosticTraceparentForPropagation(
|
||||
{ trace },
|
||||
{ trusted: false, trustedTraceContext: true },
|
||||
),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("prepares trusted events synchronously without cloning private data", async () => {
|
||||
const diagnosticTrace = createDiagnosticTraceContext({
|
||||
traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
type DiagnosticTraceContext,
|
||||
} from "./diagnostic-trace-context.js";
|
||||
import {
|
||||
formatPropagatedDiagnosticTraceparent,
|
||||
prepareDiagnosticTracePropagation,
|
||||
resetDiagnosticTracePropagationForTest,
|
||||
shouldPrepareDiagnosticTracePropagation,
|
||||
@@ -959,7 +958,6 @@ type DiagnosticEventsGlobalState = {
|
||||
const MAX_ASYNC_DIAGNOSTIC_EVENTS = 10_000;
|
||||
const MAX_ASYNC_DIAGNOSTIC_EVENTS_PER_TURN = 100;
|
||||
const DIAGNOSTIC_EVENTS_STATE_KEY = Symbol.for("openclaw.diagnosticEvents.state.v1");
|
||||
const dispatchedTrustedDiagnosticMetadata = new WeakSet<object>();
|
||||
const ASYNC_DIAGNOSTIC_EVENT_TYPES = new Set<DiagnosticEventPayload["type"]>([
|
||||
"tool.execution.started",
|
||||
"tool.execution.completed",
|
||||
@@ -1145,11 +1143,7 @@ function dispatchDiagnosticEvent(
|
||||
function createDiagnosticMetadataForListener(
|
||||
metadata: DiagnosticEventMetadata,
|
||||
): DiagnosticEventMetadata {
|
||||
const listenerMetadata = Object.freeze({ ...metadata });
|
||||
if (listenerMetadata.trusted) {
|
||||
dispatchedTrustedDiagnosticMetadata.add(listenerMetadata);
|
||||
}
|
||||
return listenerMetadata;
|
||||
return Object.freeze({ ...metadata });
|
||||
}
|
||||
|
||||
function cloneDiagnosticEventForListener(event: DiagnosticEventPayload): DiagnosticEventPayload {
|
||||
@@ -1585,17 +1579,6 @@ export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => voi
|
||||
});
|
||||
}
|
||||
|
||||
/** Formats traceparent only for trusted metadata created by the diagnostic dispatcher. */
|
||||
export function formatDiagnosticTraceparentForPropagation(
|
||||
event: { trace?: DiagnosticTraceContext },
|
||||
metadata: DiagnosticEventMetadata,
|
||||
): string | undefined {
|
||||
if (!metadata.trusted || !dispatchedTrustedDiagnosticMetadata.has(metadata)) {
|
||||
return undefined;
|
||||
}
|
||||
return formatPropagatedDiagnosticTraceparent(event.trace);
|
||||
}
|
||||
|
||||
/** Returns whether listener metadata marks dispatcher-internal provenance. */
|
||||
export function isInternalDiagnosticEventMetadata(metadata: DiagnosticEventMetadata): boolean {
|
||||
return metadata.internal === true;
|
||||
|
||||
Reference in New Issue
Block a user