mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 01:52:04 +00:00
test: dedupe plugin runtime utility suites
This commit is contained in:
@@ -18,6 +18,19 @@ function expectValidHostCheck(currentVersion: string, minHostVersion?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function expectHostCheckResult(params: {
|
||||
currentVersion: string;
|
||||
minHostVersion?: string | number;
|
||||
expected: unknown;
|
||||
}) {
|
||||
expect(
|
||||
checkMinHostVersion({
|
||||
currentVersion: params.currentVersion,
|
||||
minHostVersion: params.minHostVersion,
|
||||
}),
|
||||
).toEqual(params.expected);
|
||||
}
|
||||
|
||||
describe("min-host-version", () => {
|
||||
it("accepts empty metadata", () => {
|
||||
expect(validateMinHostVersion(undefined)).toBeNull();
|
||||
@@ -39,32 +52,43 @@ describe("min-host-version", () => {
|
||||
it.each(["2026.3.22", 123] as const)(
|
||||
"reports invalid floor syntax when checking host compatibility: %p",
|
||||
(minHostVersion) => {
|
||||
expect(checkMinHostVersion({ currentVersion: "2026.3.22", minHostVersion })).toEqual({
|
||||
ok: false,
|
||||
kind: "invalid",
|
||||
error: MIN_HOST_VERSION_FORMAT,
|
||||
expectHostCheckResult({
|
||||
currentVersion: "2026.3.22",
|
||||
minHostVersion,
|
||||
expected: {
|
||||
ok: false,
|
||||
kind: "invalid",
|
||||
error: MIN_HOST_VERSION_FORMAT,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("reports unknown host versions distinctly", () => {
|
||||
expect(
|
||||
checkMinHostVersion({ currentVersion: "unknown", minHostVersion: ">=2026.3.22" }),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
kind: "unknown_host_version",
|
||||
requirement: MIN_HOST_REQUIREMENT,
|
||||
});
|
||||
});
|
||||
|
||||
it("reports incompatible hosts", () => {
|
||||
expect(
|
||||
checkMinHostVersion({ currentVersion: "2026.3.21", minHostVersion: ">=2026.3.22" }),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
kind: "incompatible",
|
||||
it.each([
|
||||
{
|
||||
name: "reports unknown host versions distinctly",
|
||||
currentVersion: "unknown",
|
||||
expected: {
|
||||
ok: false,
|
||||
kind: "unknown_host_version",
|
||||
requirement: MIN_HOST_REQUIREMENT,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reports incompatible hosts",
|
||||
currentVersion: "2026.3.21",
|
||||
requirement: MIN_HOST_REQUIREMENT,
|
||||
expected: {
|
||||
ok: false,
|
||||
kind: "incompatible",
|
||||
currentVersion: "2026.3.21",
|
||||
requirement: MIN_HOST_REQUIREMENT,
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ currentVersion, expected }) => {
|
||||
expectHostCheckResult({
|
||||
currentVersion,
|
||||
minHostVersion: ">=2026.3.22",
|
||||
expected,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -50,6 +50,13 @@ function expectGatewaySubagentRunFailure(
|
||||
);
|
||||
}
|
||||
|
||||
function expectRuntimeValue<T>(
|
||||
readValue: (runtime: ReturnType<typeof createPluginRuntime>) => T,
|
||||
expected: T,
|
||||
) {
|
||||
expect(readValue(createPluginRuntime())).toBe(expected);
|
||||
}
|
||||
|
||||
function expectFunctionKeys(value: Record<string, unknown>, keys: readonly string[]) {
|
||||
keys.forEach((key) => {
|
||||
expect(typeof value[key]).toBe("function");
|
||||
@@ -101,10 +108,31 @@ describe("plugin runtime command execution", () => {
|
||||
expect(runCommandWithTimeoutMock).toHaveBeenCalledWith(["echo", "hello"], { timeoutMs: 1000 });
|
||||
});
|
||||
|
||||
it("exposes runtime.events listener registration helpers", () => {
|
||||
const runtime = createPluginRuntime();
|
||||
expect(runtime.events.onAgentEvent).toBe(onAgentEvent);
|
||||
expect(runtime.events.onSessionTranscriptUpdate).toBe(onSessionTranscriptUpdate);
|
||||
it.each([
|
||||
{
|
||||
name: "exposes runtime.events.onAgentEvent",
|
||||
readValue: (runtime: ReturnType<typeof createPluginRuntime>) => runtime.events.onAgentEvent,
|
||||
expected: onAgentEvent,
|
||||
},
|
||||
{
|
||||
name: "exposes runtime.events.onSessionTranscriptUpdate",
|
||||
readValue: (runtime: ReturnType<typeof createPluginRuntime>) =>
|
||||
runtime.events.onSessionTranscriptUpdate,
|
||||
expected: onSessionTranscriptUpdate,
|
||||
},
|
||||
{
|
||||
name: "exposes runtime.system.requestHeartbeatNow",
|
||||
readValue: (runtime: ReturnType<typeof createPluginRuntime>) =>
|
||||
runtime.system.requestHeartbeatNow,
|
||||
expected: requestHeartbeatNow,
|
||||
},
|
||||
{
|
||||
name: "exposes runtime.version from the shared VERSION constant",
|
||||
readValue: (runtime: ReturnType<typeof createPluginRuntime>) => runtime.version,
|
||||
expected: VERSION,
|
||||
},
|
||||
] as const)("$name", ({ readValue, expected }) => {
|
||||
expectRuntimeValue(readValue, expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -170,11 +198,6 @@ describe("plugin runtime command execution", () => {
|
||||
expectRuntimeShape(assert);
|
||||
});
|
||||
|
||||
it("exposes runtime.system.requestHeartbeatNow", () => {
|
||||
const runtime = createPluginRuntime();
|
||||
expect(runtime.system.requestHeartbeatNow).toBe(requestHeartbeatNow);
|
||||
});
|
||||
|
||||
it("modelAuth wrappers strip agentDir and store to prevent credential steering", async () => {
|
||||
// The wrappers should not forward agentDir or store from plugin callers.
|
||||
// We verify this by checking the wrapper functions exist and are not the
|
||||
@@ -206,9 +229,4 @@ describe("plugin runtime command execution", () => {
|
||||
});
|
||||
expect(run).toHaveBeenCalledWith({ sessionKey: "s-2", message: "hello" });
|
||||
});
|
||||
|
||||
it("exposes runtime.version from the shared VERSION constant", () => {
|
||||
const runtime = createPluginRuntime();
|
||||
expect(runtime.version).toBe(VERSION);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type CreateDiscordTypingLeaseParams,
|
||||
} from "./runtime-discord-typing.js";
|
||||
import {
|
||||
createPulseWithBackgroundFailure,
|
||||
expectBackgroundTypingPulseFailuresAreSwallowed,
|
||||
expectIndependentTypingLeases,
|
||||
} from "./typing-lease.test-support.js";
|
||||
@@ -33,10 +34,10 @@ describe("createDiscordTypingLease", () => {
|
||||
});
|
||||
|
||||
it("swallows background pulse failures", async () => {
|
||||
const pulse = vi
|
||||
.fn<(params: { channelId: string; accountId?: string; cfg?: unknown }) => Promise<void>>()
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockRejectedValueOnce(new Error("boom"));
|
||||
const pulse =
|
||||
createPulseWithBackgroundFailure<
|
||||
(params: { channelId: string; accountId?: string; cfg?: unknown }) => Promise<void>
|
||||
>();
|
||||
|
||||
await expectBackgroundTypingPulseFailuresAreSwallowed({
|
||||
createLease: createDiscordTypingLease,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, it, vi } from "vitest";
|
||||
import { createTelegramTypingLease } from "./runtime-telegram-typing.js";
|
||||
import {
|
||||
createPulseWithBackgroundFailure,
|
||||
expectBackgroundTypingPulseFailuresAreSwallowed,
|
||||
expectIndependentTypingLeases,
|
||||
expectTypingPulseCount,
|
||||
} from "./typing-lease.test-support.js";
|
||||
|
||||
const TELEGRAM_TYPING_INTERVAL_MS = 2_000;
|
||||
@@ -23,13 +25,6 @@ function buildTelegramTypingParams(
|
||||
};
|
||||
}
|
||||
|
||||
function expectTelegramPulseCount(
|
||||
pulse: ReturnType<typeof vi.fn<() => Promise<unknown>>>,
|
||||
expected: number,
|
||||
) {
|
||||
expect(pulse).toHaveBeenCalledTimes(expected);
|
||||
}
|
||||
|
||||
describe("createTelegramTypingLease", () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
@@ -43,17 +38,15 @@ describe("createTelegramTypingLease", () => {
|
||||
});
|
||||
|
||||
it("swallows background pulse failures", async () => {
|
||||
const pulse = vi
|
||||
.fn<
|
||||
const pulse =
|
||||
createPulseWithBackgroundFailure<
|
||||
(params: {
|
||||
to: string;
|
||||
accountId?: string;
|
||||
cfg?: unknown;
|
||||
messageThreadId?: number;
|
||||
}) => Promise<unknown>
|
||||
>()
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockRejectedValueOnce(new Error("boom"));
|
||||
>();
|
||||
|
||||
await expectBackgroundTypingPulseFailuresAreSwallowed({
|
||||
createLease: createTelegramTypingLease,
|
||||
@@ -72,11 +65,11 @@ describe("createTelegramTypingLease", () => {
|
||||
pulse,
|
||||
});
|
||||
|
||||
expectTelegramPulseCount(pulse, 1);
|
||||
expectTypingPulseCount(pulse, 1);
|
||||
await vi.advanceTimersByTimeAsync(TELEGRAM_TYPING_DEFAULT_INTERVAL_MS - 1);
|
||||
expectTelegramPulseCount(pulse, 1);
|
||||
expectTypingPulseCount(pulse, 1);
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expectTelegramPulseCount(pulse, 2);
|
||||
expectTypingPulseCount(pulse, 2);
|
||||
|
||||
lease.stop();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { expect, vi } from "vitest";
|
||||
|
||||
function expectPulseCount(pulse: { mock: { calls: unknown[] } }, expected: number) {
|
||||
export function expectTypingPulseCount(pulse: { mock: { calls: unknown[] } }, expected: number) {
|
||||
expect(pulse.mock.calls).toHaveLength(expected);
|
||||
}
|
||||
|
||||
export function createPulseWithBackgroundFailure<
|
||||
TPulse extends (...args: unknown[]) => Promise<unknown>,
|
||||
>() {
|
||||
return vi.fn<TPulse>().mockResolvedValueOnce(undefined).mockRejectedValueOnce(new Error("boom"));
|
||||
}
|
||||
|
||||
export async function expectIndependentTypingLeases<
|
||||
TParams extends { intervalMs?: number; pulse: (...args: never[]) => Promise<unknown> },
|
||||
TLease extends { refresh: () => Promise<void>; stop: () => void },
|
||||
@@ -17,17 +23,17 @@ export async function expectIndependentTypingLeases<
|
||||
const leaseA = await params.createLease(params.buildParams(pulse));
|
||||
const leaseB = await params.createLease(params.buildParams(pulse));
|
||||
|
||||
expectPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 2);
|
||||
expectTypingPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 2);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(2_000);
|
||||
expectPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 4);
|
||||
expectTypingPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 4);
|
||||
|
||||
leaseA.stop();
|
||||
await vi.advanceTimersByTimeAsync(2_000);
|
||||
expectPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 5);
|
||||
expectTypingPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 5);
|
||||
|
||||
await leaseB.refresh();
|
||||
expectPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 6);
|
||||
expectTypingPulseCount(pulse as unknown as { mock: { calls: unknown[] } }, 6);
|
||||
|
||||
leaseB.stop();
|
||||
}
|
||||
@@ -45,7 +51,7 @@ export async function expectBackgroundTypingPulseFailuresAreSwallowed<
|
||||
const lease = await params.createLease(params.buildParams(params.pulse));
|
||||
|
||||
await expect(vi.advanceTimersByTimeAsync(2_000)).resolves.toBe(vi);
|
||||
expectPulseCount(params.pulse as unknown as { mock: { calls: unknown[] } }, 2);
|
||||
expectTypingPulseCount(params.pulse as unknown as { mock: { calls: unknown[] } }, 2);
|
||||
|
||||
lease.stop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user