test: dedupe plugin runtime utility suites

This commit is contained in:
Peter Steinberger
2026-03-28 04:40:04 +00:00
parent 12318d25ae
commit 0c729b6d30
5 changed files with 103 additions and 61 deletions

View File

@@ -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,
});
});

View File

@@ -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);
});
});

View File

@@ -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,

View File

@@ -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();
});

View File

@@ -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();
}