Files
fusion/packages/engine/src/__tests__/runtime-resolution.test.ts
Fusion a24f50ba02 feat(FN-2617): merge fusion/fn-2617 (auto-resolved)
- test(FN-2617): stabilize workflow remediation timeout under full suite
- test(FN-2617): complete Step 5 — add openclaw runtime resolution coverage
- feat(FN-2617): complete Step 4 — route step sessions through runtime resolution
- fix(FN-2617): thread runtimeHint through merger rebase push flow
- feat(FN-2617): complete Step 3 — wire runtimeHint across engine subsystems
- feat(FN-2617): complete Step 2 — thread runtimeHint in executor paths
- feat(FN-2617): complete Step 1 — add runtimeHint extraction helper
2026-04-26 16:14:35 -07:00

355 lines
12 KiB
TypeScript

/**
* Runtime Resolution Tests
*
* Tests for the runtime resolution system including:
* - Default pi runtime selection
* - Plugin runtime lookup by hint
* - Fallback behavior when configured runtime is unavailable
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type { AgentRuntime, AgentRuntimeOptions, AgentSessionResult } from "../agent-runtime.js";
import {
resolveRuntime,
getDefaultPiRuntime,
type RuntimeResolutionContext,
type ResolvedRuntime,
} from "../runtime-resolution.js";
import type { PluginRunner } from "../plugin-runner.js";
import type { PluginRuntimeRegistration } from "@fusion/core";
// Mock the logger to suppress output during tests
vi.mock("../logger.js", () => ({
createLogger: vi.fn(() => ({
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
})),
}));
// Mock pi.js to avoid actual session creation
vi.mock("../pi.js", () => ({
createFnAgent: vi.fn().mockResolvedValue({
session: {
model: { provider: "anthropic", id: "claude-sonnet-4-5" },
prompt: vi.fn(),
},
sessionFile: undefined,
}),
promptWithFallback: vi.fn().mockResolvedValue(undefined),
describeModel: vi.fn().mockReturnValue("anthropic/claude-sonnet-4-5"),
}));
describe("runtime-resolution", () => {
let mockPluginRunner: {
getPluginRuntimes: ReturnType<typeof vi.fn>;
getRuntimeById: ReturnType<typeof vi.fn>;
createRuntimeContext: ReturnType<typeof vi.fn>;
};
const createMockPluginRuntime = (runtimeId: string, name: string): PluginRuntimeRegistration => ({
metadata: {
runtimeId,
name,
description: `Test runtime ${runtimeId}`,
version: "1.0.0",
},
factory: vi.fn().mockResolvedValue({
id: runtimeId,
name,
createSession: vi.fn().mockResolvedValue({
session: { model: { provider: "test", id: "test-model" } },
}),
promptWithFallback: vi.fn(),
describeModel: vi.fn().mockReturnValue("test/model"),
}),
});
beforeEach(() => {
mockPluginRunner = {
getPluginRuntimes: vi.fn().mockReturnValue([]),
getRuntimeById: vi.fn().mockReturnValue(undefined),
createRuntimeContext: vi.fn().mockResolvedValue({
pluginId: "test-plugin",
taskStore: {},
settings: {},
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
emitEvent: vi.fn(),
}),
};
});
afterEach(() => {
vi.clearAllMocks();
});
describe("getDefaultPiRuntime()", () => {
it("should return the same instance on multiple calls", () => {
const runtime1 = getDefaultPiRuntime();
const runtime2 = getDefaultPiRuntime();
expect(runtime1).toBe(runtime2);
});
it("should have pi as the runtime id", () => {
const runtime = getDefaultPiRuntime();
expect(runtime.id).toBe("pi");
});
it("should have a human-readable name", () => {
const runtime = getDefaultPiRuntime();
expect(runtime.name).toBe("Default PI Runtime");
});
it("should implement createSession", () => {
const runtime = getDefaultPiRuntime();
expect(typeof runtime.createSession).toBe("function");
});
it("should implement promptWithFallback", () => {
const runtime = getDefaultPiRuntime();
expect(typeof runtime.promptWithFallback).toBe("function");
});
it("should implement describeModel", () => {
const runtime = getDefaultPiRuntime();
expect(typeof runtime.describeModel).toBe("function");
});
});
describe("resolveRuntime()", () => {
const createContext = (
purpose: RuntimeResolutionContext["sessionPurpose"] = "executor",
runtimeHint?: string,
): RuntimeResolutionContext => ({
sessionPurpose: purpose,
runtimeHint,
pluginRunner: mockPluginRunner as unknown as PluginRunner,
});
describe("no runtime hint", () => {
it("should return default pi runtime when hint is undefined", async () => {
const context = createContext("executor", undefined);
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
expect(result.runtime.id).toBe("pi");
});
it("should return default pi runtime when hint is empty string", async () => {
const context = createContext("executor", "");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
it("should return default pi runtime when hint is whitespace only", async () => {
const context = createContext("executor", " ");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
it("should work for all session purposes without hint", async () => {
const purposes: RuntimeResolutionContext["sessionPurpose"][] = [
"executor",
"triage",
"reviewer",
"merger",
"heartbeat",
"validation",
];
for (const purpose of purposes) {
const context = createContext(purpose, undefined);
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
}
});
});
describe("explicit pi hint", () => {
it("should return pi runtime when hint is 'pi'", async () => {
const context = createContext("executor", "pi");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(true);
});
it("should return pi runtime when hint is 'default'", async () => {
const context = createContext("executor", "default");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(true);
});
});
describe("plugin runtime lookup", () => {
it("should return plugin runtime when hint matches existing runtime", async () => {
const mockRuntime = createMockPluginRuntime("code-interpreter", "Code Interpreter Runtime");
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "test-plugin",
runtime: mockRuntime,
});
const context = createContext("executor", "code-interpreter");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("code-interpreter");
expect(result.wasConfigured).toBe(true);
expect(result.runtime.id).toBe("code-interpreter");
});
it("should create runtime context when resolving plugin runtime", async () => {
const mockRuntime = createMockPluginRuntime("web-search", "Web Search Runtime");
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "web-plugin",
runtime: mockRuntime,
});
const context = createContext("executor", "web-search");
await resolveRuntime(context);
expect(mockPluginRunner.createRuntimeContext).toHaveBeenCalledWith("web-plugin");
});
it("should call the plugin runtime factory", async () => {
const mockRuntime = createMockPluginRuntime("custom", "Custom Runtime");
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "custom-plugin",
runtime: mockRuntime,
});
const context = createContext("executor", "custom");
await resolveRuntime(context);
expect(mockRuntime.factory).toHaveBeenCalled();
});
it("should find runtime by ID when multiple plugins have runtimes", async () => {
const mockRuntime = createMockPluginRuntime("unique-id", "First Runtime");
// getRuntimeById returns the first match
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "plugin-1",
runtime: mockRuntime,
});
const context = createContext("executor", "unique-id");
const result = await resolveRuntime(context);
// Should return the matching runtime
expect(result.runtime.id).toBe("unique-id");
});
it("should resolve the openclaw runtime when registered", async () => {
const openclawRuntime = createMockPluginRuntime("openclaw", "OpenClaw Runtime");
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "fusion-plugin-openclaw-runtime",
runtime: openclawRuntime,
});
const result = await resolveRuntime(createContext("executor", "openclaw"));
expect(result.runtimeId).toBe("openclaw");
expect(result.wasConfigured).toBe(true);
});
});
describe("fallback behavior", () => {
it("should fall back to pi when runtime hint references non-existent runtime", async () => {
// getRuntimeById returns undefined for non-existent runtime
mockPluginRunner.getRuntimeById.mockReturnValue(undefined);
const context = createContext("executor", "non-existent");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
it("should fall back to pi when openclaw runtime is not registered", async () => {
mockPluginRunner.getRuntimeById.mockReturnValue(undefined);
const result = await resolveRuntime(createContext("executor", "openclaw"));
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
it("should fall back to pi when runtime factory throws", async () => {
const mockRuntime: PluginRuntimeRegistration = {
metadata: {
runtimeId: "broken",
name: "Broken Runtime",
},
factory: vi.fn().mockRejectedValue(new Error("Factory failed")),
};
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "broken-plugin",
runtime: mockRuntime,
});
const context = createContext("executor", "broken");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
it("should fall back to pi when runtime factory returns null", async () => {
const mockRuntime: PluginRuntimeRegistration = {
metadata: {
runtimeId: "null-return",
name: "Null Return Runtime",
},
factory: vi.fn().mockResolvedValue(null),
};
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "null-plugin",
runtime: mockRuntime,
});
const context = createContext("executor", "null-return");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
it("should fall back to pi when createRuntimeContext returns null", async () => {
mockPluginRunner.createRuntimeContext.mockResolvedValue(null);
const mockRuntime = createMockPluginRuntime("orphan", "Orphan Runtime");
mockPluginRunner.getRuntimeById.mockReturnValue({
pluginId: "orphan-plugin",
runtime: mockRuntime,
});
const context = createContext("executor", "orphan");
const result = await resolveRuntime(context);
expect(result.runtimeId).toBe("pi");
expect(result.wasConfigured).toBe(false);
});
});
});
describe("AgentRuntime interface compliance", () => {
it("default pi runtime should implement all required interface methods", () => {
const runtime = getDefaultPiRuntime();
// Check required properties
expect(typeof runtime.id).toBe("string");
expect(typeof runtime.name).toBe("string");
// Check required methods
expect(typeof runtime.createSession).toBe("function");
expect(typeof runtime.promptWithFallback).toBe("function");
expect(typeof runtime.describeModel).toBe("function");
});
});
});