feat(FN-3637): establish canonical roadmap plugin ID and compatibility rout
Merged commits stabilize the Fusion roadmap plugin's identity and routing surface, establishing a canonical plugin ID and compatibility routes so the roadmap plugin integrates cleanly with the dashboard's plugin system. Added new roadmap-routes and roadmap-suggestions modules in the dashboard packag Fusion-Task-Id: FN-3637
This commit is contained in:
@@ -1193,24 +1193,24 @@ describe("plugin-defined route dispatch", () => {
|
||||
it("registers PATCH routes from plugins", () => {
|
||||
const pluginRunner = {
|
||||
getPluginRoutes: vi.fn().mockReturnValue([
|
||||
{ pluginId: "roadmap-planner", route: { method: "PATCH", path: "/roadmaps/x", handler: vi.fn() } },
|
||||
{ pluginId: "fusion-plugin-roadmap", route: { method: "PATCH", path: "/roadmaps/x", handler: vi.fn() } },
|
||||
]),
|
||||
};
|
||||
|
||||
const pluginStore = createMockPluginStore();
|
||||
const router = createPluginRouter(pluginStore, createMockPluginLoader({
|
||||
createRouteContext: vi.fn().mockResolvedValue({
|
||||
pluginId: "roadmap-planner",
|
||||
pluginId: "fusion-plugin-roadmap",
|
||||
taskStore: createMockTaskStore(),
|
||||
settings: {},
|
||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
||||
emitEvent: vi.fn(),
|
||||
}),
|
||||
getPlugin: vi.fn().mockReturnValue({ manifest: { id: "roadmap-planner" } }),
|
||||
getPlugin: vi.fn().mockReturnValue({ manifest: { id: "fusion-plugin-roadmap" } }),
|
||||
} as any), pluginRunner as any, createMockTaskStore());
|
||||
|
||||
const stack = (router as any).stack as Array<{ route?: { path: string; methods: Record<string, boolean> } }>;
|
||||
const patchRoute = stack.find((layer) => layer.route?.path === "/roadmap-planner/roadmaps/x");
|
||||
const patchRoute = stack.find((layer) => layer.route?.path === "/fusion-plugin-roadmap/roadmaps/x");
|
||||
expect(patchRoute?.route?.methods.patch).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1218,14 +1218,14 @@ describe("plugin-defined route dispatch", () => {
|
||||
const routeHandler = vi.fn().mockResolvedValue({ ok: true });
|
||||
const pluginRunner = {
|
||||
getPluginRoutes: vi.fn().mockReturnValue([
|
||||
{ pluginId: "roadmap-planner", route: { method: "POST", path: "/ctx-check", handler: routeHandler } },
|
||||
{ pluginId: "fusion-plugin-roadmap", route: { method: "POST", path: "/ctx-check", handler: routeHandler } },
|
||||
]),
|
||||
};
|
||||
const scopedPluginStore = createMockPluginStore();
|
||||
const scopedTaskStore = createMockTaskStore({ getPluginStore: vi.fn().mockReturnValue(scopedPluginStore) });
|
||||
mockGetOrCreateProjectStore.mockResolvedValue(scopedTaskStore);
|
||||
const createRouteContext = vi.fn().mockImplementation(async (_pluginId: string, overrides: any) => ({
|
||||
pluginId: "roadmap-planner",
|
||||
pluginId: "fusion-plugin-roadmap",
|
||||
taskStore: overrides.taskStore,
|
||||
settings: overrides.settings,
|
||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
||||
@@ -1235,7 +1235,7 @@ describe("plugin-defined route dispatch", () => {
|
||||
}));
|
||||
const pluginLoader = createMockPluginLoader({
|
||||
createRouteContext,
|
||||
getPlugin: vi.fn().mockReturnValue({ manifest: { id: "roadmap-planner" } }),
|
||||
getPlugin: vi.fn().mockReturnValue({ manifest: { id: "fusion-plugin-roadmap" } }),
|
||||
} as any);
|
||||
const pluginStore = createMockPluginStore();
|
||||
|
||||
@@ -1243,9 +1243,9 @@ describe("plugin-defined route dispatch", () => {
|
||||
app.use(express.json());
|
||||
app.use("/api/plugins", createPluginRouter(pluginStore, pluginLoader, pluginRunner as any, createMockTaskStore()));
|
||||
|
||||
const res = await REQUEST(app, "POST", "/api/plugins/roadmap-planner/ctx-check", { projectId: "proj_123" });
|
||||
const res = await REQUEST(app, "POST", "/api/plugins/fusion-plugin-roadmap/ctx-check", { projectId: "proj_123" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(createRouteContext).toHaveBeenCalledWith("roadmap-planner", expect.objectContaining({
|
||||
expect(createRouteContext).toHaveBeenCalledWith("fusion-plugin-roadmap", expect.objectContaining({
|
||||
taskStore: scopedTaskStore,
|
||||
resolveProjectTaskStore: projectStoreResolver.getOrCreateProjectStore,
|
||||
}));
|
||||
|
||||
@@ -1028,7 +1028,7 @@ describe("GET /api/plugins/dashboard-views", () => {
|
||||
it("keeps dashboard-views payload separate from ui-slots payload", async () => {
|
||||
(pluginLoader.getPluginDashboardViews as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{
|
||||
pluginId: "roadmap-planner",
|
||||
pluginId: "fusion-plugin-roadmap",
|
||||
view: {
|
||||
viewId: "roadmaps",
|
||||
label: "Roadmaps",
|
||||
@@ -1038,7 +1038,7 @@ describe("GET /api/plugins/dashboard-views", () => {
|
||||
]);
|
||||
(pluginLoader.getPluginUiSlots as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{
|
||||
pluginId: "roadmap-planner",
|
||||
pluginId: "fusion-plugin-roadmap",
|
||||
slot: {
|
||||
slotId: "task-detail-tab",
|
||||
label: "Roadmap Details",
|
||||
@@ -1696,7 +1696,8 @@ describe("createPluginRouter plugin-defined route responses", () => {
|
||||
const res = await performGet(app, "/plugins/demo/html");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers["content-type"]).toContain("text/html");
|
||||
expect(res.text).toContain("<html><body>Hello</body></html>");
|
||||
const html = typeof res.text === "string" ? res.text : String(res.body ?? "");
|
||||
expect(html).toContain("<html><body>Hello</body></html>");
|
||||
});
|
||||
|
||||
it("propagates custom response headers", async () => {
|
||||
|
||||
@@ -4,18 +4,21 @@ import { describe, it, expect } from "vitest";
|
||||
import express from "express";
|
||||
import { registerIntegratedRouters } from "../routes/register-integrated-routers.js";
|
||||
|
||||
describe("integrated roadmap routes removed", () => {
|
||||
it("does not register a legacy /roadmaps mount", () => {
|
||||
describe("integrated roadmap routes compatibility", () => {
|
||||
it("registers a legacy /roadmaps mount that delegates to plugin routes", () => {
|
||||
const router = express.Router();
|
||||
registerIntegratedRouters({
|
||||
router,
|
||||
store: {} as never,
|
||||
});
|
||||
|
||||
const mountedPaths = (router as unknown as { stack?: Array<{ regexp?: { source?: string } }> }).stack
|
||||
?.map((layer) => layer.regexp?.source ?? "")
|
||||
?? [];
|
||||
const stack = (router as unknown as { stack?: Array<{ regexp?: { source?: string }; handle?: { stack?: Array<{ route?: { path?: string } }> } }> }).stack ?? [];
|
||||
|
||||
expect(mountedPaths.some((path) => path.includes("roadmaps"))).toBe(false);
|
||||
const hasRoadmapMount = stack.some((layer) => {
|
||||
if (layer.regexp?.source?.includes("roadmaps")) return true;
|
||||
return layer.handle?.stack?.some((nested) => typeof nested.route?.path === "string" && nested.route.path.startsWith("/roadmaps")) ?? false;
|
||||
});
|
||||
|
||||
expect(hasRoadmapMount).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { SUGGESTION_TIMEOUT_MS } from "../roadmap-suggestions.js";
|
||||
|
||||
describe("roadmap suggestions compatibility exports", () => {
|
||||
it("re-exports suggestion timeout from roadmap plugin", () => {
|
||||
expect(typeof SUGGESTION_TIMEOUT_MS).toBe("number");
|
||||
expect(SUGGESTION_TIMEOUT_MS).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user