fix(FN-2117): log swallowed engine runtime errors

- Add structured warning logs in silent catch paths for IPC worker shutdown, plugin unregistration cleanup, child runtime metrics polling, and merger build rollback reset
- Improve merger rollback warning context to make build-verification reset and retry failures easier to diagnose
- Add regression tests covering each new warning path to ensure errors are surfaced without changing existing control flow
- Add StepSessionExecutor test coverage for cherry-pick abort logging when conflict cleanup fails
This commit is contained in:
Fusion
2026-04-19 02:55:04 -07:00
committed by gsxdsm
parent 12f1e7fa32
commit f7bd321d42
9 changed files with 196 additions and 11 deletions

View File

@@ -9,14 +9,15 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { PluginRunner, type PluginRunnerOptions } from "./plugin-runner.js";
import type { PluginLoader, PluginStore, PluginInstallation } from "@fusion/core";
import type { FusionPlugin, PluginToolDefinition } from "@fusion/core";
import { createLogger } from "./logger.js";
// Mock the logger to suppress output during tests
vi.mock("./logger.js", () => ({
createLogger: () => ({
createLogger: vi.fn(() => ({
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}),
})),
executorLog: {
log: vi.fn(),
warn: vi.fn(),
@@ -63,6 +64,18 @@ describe("PluginRunner", () => {
...overrides,
});
const getPluginRunnerLogger = () => {
const logger = vi.mocked(createLogger).mock.results.at(-1)?.value as {
log: ReturnType<typeof vi.fn>;
warn: ReturnType<typeof vi.fn>;
error: ReturnType<typeof vi.fn>;
} | undefined;
if (!logger) {
throw new Error("Expected plugin-runner logger to be initialized");
}
return logger;
};
beforeEach(() => {
// Create fresh mocks for each test
mockPluginLoader = {
@@ -655,6 +668,37 @@ describe("PluginRunner", () => {
expect(true).toBe(true); // Handler exists and doesn't throw
});
it("logs warning when stopPlugin fails during plugin:unregistered handler", async () => {
mockPluginLoader.stopPlugin.mockRejectedValue(new Error("stop failed"));
await pluginRunner.init();
const unregisteredHandler = mockPluginStore.on.mock.calls.find(
call => call[0] === "plugin:unregistered"
)?.[1];
const logger = getPluginRunnerLogger();
logger.warn.mockClear();
expect(unregisteredHandler).toBeTypeOf("function");
const plugin = {
id: "broken-plugin",
name: "Broken Plugin",
version: "1.0.0",
path: "/test/path",
enabled: false,
state: "stopped" as const,
settings: {},
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
};
await expect(unregisteredHandler?.(plugin)).resolves.toBeUndefined();
expect(mockPluginLoader.stopPlugin).toHaveBeenCalledWith("broken-plugin");
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining("Failed to stop unregistered plugin broken-plugin: stop failed"),
);
});
it("should handle plugin:stateChanged event", async () => {
await pluginRunner.init();