feat(FN-3573): harden plugin setup sync and test-isolation handling

- Add plugin setup API coverage for migration and sync edge cases
- Expand plugin route tests and implementation safeguards for setup state handling
- Update legacy API glue to align plugin setup responses with route behavior
- Keep test-isolation runtime ignore handling compatible with live app activity

Fusion-Task-Id: FN-3573
This commit is contained in:
Fusion
2026-05-06 14:16:59 -07:00
committed by gsxdsm
parent 846a9900f5
commit 149e1eabfb
5 changed files with 278 additions and 4 deletions

View File

@@ -196,10 +196,6 @@ export function createPluginRouter(
): Router {
const router = Router();
// ── Error Handler ───────────────────────────────────────────────
router.use(catchHandler);
// ── Management Routes ───────────────────────────────────────────
/**
@@ -366,6 +362,88 @@ export function createPluginRouter(
res.json(updatedPlugin);
}));
/**
* GET /plugins/:id/setup-status
* Check plugin setup status.
*/
router.get("/:id/setup-status", catchHandler(async (req: Request, res: Response) => {
const id = req.params.id as string;
let plugin: import("@fusion/core").PluginInstallation;
try {
plugin = await pluginStore.getPlugin(id);
} catch (err: unknown) {
if (
(err as NodeJS.ErrnoException).code === "ENOENT"
|| (err instanceof Error && err.message.includes("not found"))
) {
throw notFound(`Plugin "${id}" not found`);
}
throw internalError(err instanceof Error ? err.message : "Unknown error");
}
if (!pluginRunner?.checkPluginSetup || !pluginRunner.getPluginSetupInfo) {
throw internalError("Plugin runner not available");
}
const setupInfo = pluginRunner.getPluginSetupInfo();
const hasSetup = setupInfo.some((entry) => entry.pluginId === id);
if (!hasSetup) {
res.json({ hasSetup: false });
return;
}
if (plugin.state !== "started") {
res.json({
hasSetup: false,
status: { status: "error", error: "Plugin not loaded" },
});
return;
}
const status = await pluginRunner.checkPluginSetup(id);
res.json({ hasSetup: true, ...status });
}));
/**
* POST /plugins/:id/setup/install
* Trigger plugin setup install hook.
*/
router.post("/:id/setup/install", catchHandler(async (req: Request, res: Response) => {
const id = req.params.id as string;
let plugin: import("@fusion/core").PluginInstallation;
try {
plugin = await pluginStore.getPlugin(id);
} catch (err: unknown) {
if (
(err as NodeJS.ErrnoException).code === "ENOENT"
|| (err instanceof Error && err.message.includes("not found"))
) {
throw notFound(`Plugin "${id}" not found`);
}
throw internalError(err instanceof Error ? err.message : "Unknown error");
}
if (!plugin.enabled) {
throw badRequest("Plugin must be enabled before setup install");
}
if (!pluginRunner?.installPluginSetup || !pluginRunner.getPluginSetupInfo) {
throw internalError("Plugin runner not available");
}
const setupInfo = pluginRunner.getPluginSetupInfo();
const setup = setupInfo.find((entry) => entry.pluginId === id);
if (!setup?.hooks.install) {
throw badRequest("Plugin has no install hook");
}
const result = await pluginRunner.installPluginSetup(id);
res.json(result ?? { success: true });
}));
/**
* DELETE /plugins/:id
* Uninstall a plugin.