fix(dashboard-tests): satisfy FN-8491 plugin-MCP store contract across route suites; align planning create-task envelope
- routes-auth/automation/git/github/tasks: FN-8491 binds the plugin-MCP
provider on every project-context resolution; mock stores now expose
getProjectScopedPluginMcpServers so routes stop 500ing.
- routes-github: stub FN-8442 durable planning-claim plumbing whose internal
getSession bypassed the namespace spy; the scoped-store routing contract
is asserted unchanged.
- api-git: POST /planning/create-task now returns the FN-8442
{task, alreadyCreated} envelope; mock matches.
- TaskDetailModal.tab-persistence: settle-then-requery before clicking the
Session tab (detached-node race under CI load).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1081,7 +1081,15 @@ describe("Planning Mode API", () => {
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
};
|
||||
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, createdTask, 201));
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-23-23:35:
|
||||
FN-8442 (36b318096) changed POST /planning/create-task to respond with a
|
||||
{ task, alreadyCreated } envelope (idempotent replay detection); the client
|
||||
unwraps response.task. Mock the real server envelope, not a bare Task.
|
||||
*/
|
||||
globalThis.fetch = vi.fn().mockReturnValue(
|
||||
mockFetchResponse(true, { task: createdTask, alreadyCreated: false }, 201)
|
||||
);
|
||||
|
||||
const result = await createTaskFromPlanning("plan-123");
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Column, Task, TaskDetail } from "@fusion/core";
|
||||
import {
|
||||
@@ -232,8 +232,16 @@ describe("TaskDetailModal tab persistence", () => {
|
||||
const view = renderDetail({ task });
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Session" })).toBeInTheDocument());
|
||||
/*
|
||||
FNXC:TaskDetailTabs 2026-07-24-00:00:
|
||||
Settle pending CLI-session fetch commits and click a freshly-queried node: clicking
|
||||
right after the tab appears raced the cliSession hydration re-render on loaded CI
|
||||
shards (full-suite run 30070825088), dispatching on a detached node so the tab never
|
||||
activated. Same detached-node class d7752931b fixed for PlanningModeModal Proceed.
|
||||
*/
|
||||
await act(async () => {});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Session" }));
|
||||
expect(screen.getByRole("button", { name: "Session" })).toHaveClass("detail-tab-active");
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Session" })).toHaveClass("detail-tab-active"));
|
||||
|
||||
rerenderDetail(view.rerender, { task: makeTask({ id: "FN-NO-SESSION", column: "in-progress", prompt: "# Full task" }) });
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Plan" })).toHaveClass("detail-tab-active"));
|
||||
|
||||
@@ -237,6 +237,16 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
addPrInfo: vi.fn().mockResolvedValue(undefined),
|
||||
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
|
||||
getRootDir: vi.fn().mockReturnValue("/fake/root"),
|
||||
/*
|
||||
FNXC:PluginMcpServers 2026-07-23-23:30:
|
||||
FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin
|
||||
MCP provider on every getProjectContext call. A store that already exposes
|
||||
getProjectScopedPluginMcpServers is treated as runtime-owned and skips the
|
||||
binder (which would otherwise call getPluginStore()); declare it here so the
|
||||
route contracts under test stay isolated from plugin-loader bootstrapping.
|
||||
Same alignment as remote-access-routes.test.ts (d7752931b).
|
||||
*/
|
||||
getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]),
|
||||
listWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
createWorkflowStep: vi.fn(),
|
||||
getWorkflowStep: vi.fn(),
|
||||
|
||||
@@ -231,6 +231,16 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
|
||||
getRootDir: vi.fn().mockReturnValue("/fake/root"),
|
||||
/*
|
||||
FNXC:PluginMcpServers 2026-07-23-23:40:
|
||||
FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin
|
||||
MCP provider on every getProjectContext call. A store that already exposes
|
||||
getProjectScopedPluginMcpServers is treated as runtime-owned and skips the
|
||||
binder (which would otherwise call getPluginStore()); declare it here so the
|
||||
route contracts under test stay isolated from plugin-loader bootstrapping.
|
||||
Same alignment as remote-access-routes.test.ts (d7752931b).
|
||||
*/
|
||||
getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]),
|
||||
/*
|
||||
FNXC:DashboardTests 2026-07-18-09:45:
|
||||
Backup automation path calls resolveGlobalBackupRoot(store) which requires
|
||||
getGlobalSettingsDir. Full-suite shard 4 failed FN-7537 manual backup parity
|
||||
|
||||
@@ -207,6 +207,16 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
updatePrInfo: vi.fn().mockResolvedValue(undefined),
|
||||
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
|
||||
getRootDir: vi.fn().mockReturnValue("/fake/root"),
|
||||
/*
|
||||
FNXC:PluginMcpServers 2026-07-23-23:40:
|
||||
FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin
|
||||
MCP provider on every getProjectContext call. A store that already exposes
|
||||
getProjectScopedPluginMcpServers is treated as runtime-owned and skips the
|
||||
binder (which would otherwise call getPluginStore()); declare it here so the
|
||||
route contracts under test stay isolated from plugin-loader bootstrapping.
|
||||
Same alignment as remote-access-routes.test.ts (d7752931b).
|
||||
*/
|
||||
getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]),
|
||||
listWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
createWorkflowStep: vi.fn(),
|
||||
getWorkflowStep: vi.fn(),
|
||||
|
||||
@@ -251,6 +251,16 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
removePrInfoByNumber: vi.fn().mockResolvedValue(undefined),
|
||||
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
|
||||
getRootDir: vi.fn().mockReturnValue("/fake/root"),
|
||||
/*
|
||||
FNXC:PluginMcpServers 2026-07-23-23:40:
|
||||
FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin
|
||||
MCP provider on every getProjectContext call. A store that already exposes
|
||||
getProjectScopedPluginMcpServers is treated as runtime-owned and skips the
|
||||
binder (which would otherwise call getPluginStore()); declare it here so the
|
||||
route contracts under test stay isolated from plugin-loader bootstrapping.
|
||||
Same alignment as remote-access-routes.test.ts (d7752931b).
|
||||
*/
|
||||
getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]),
|
||||
listWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
createWorkflowStep: vi.fn(),
|
||||
getWorkflowStep: vi.fn(),
|
||||
@@ -1994,6 +2004,17 @@ describe("projectId store scoping regressions", () => {
|
||||
keyDeliverables: [],
|
||||
});
|
||||
vi.spyOn(planningModule, "cleanupSession").mockImplementation(() => {});
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-23-23:55:
|
||||
FN-8442 (36b318096) routes create-task through durable create-claim plumbing
|
||||
(getDurablePlanningSession + updatePlanningCreateClaim). Those functions call the
|
||||
REAL module-internal getSession (a namespace spy cannot intercept intra-module
|
||||
calls), so the spied session above is invisible to them and they throw
|
||||
SessionNotFoundError. This test's contract is scoped-store routing of createTask,
|
||||
not claim persistence, so stub the claim plumbing to inert no-ops here.
|
||||
*/
|
||||
vi.spyOn(planningModule, "getDurablePlanningSession").mockResolvedValue(undefined);
|
||||
vi.spyOn(planningModule, "updatePlanningCreateClaim").mockResolvedValue(undefined);
|
||||
|
||||
(scopedStore.createTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
...FAKE_TASK_DETAIL,
|
||||
|
||||
@@ -236,6 +236,16 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
recordActivity: vi.fn().mockResolvedValue(undefined),
|
||||
getFusionDir: vi.fn().mockReturnValue("/fake/root/.fusion"),
|
||||
getRootDir: vi.fn().mockReturnValue("/fake/root"),
|
||||
/*
|
||||
FNXC:PluginMcpServers 2026-07-23-23:40:
|
||||
FN-8491 (3cd023fa4) made resolveProjectContext bind a project-scoped plugin
|
||||
MCP provider on every getProjectContext call. A store that already exposes
|
||||
getProjectScopedPluginMcpServers is treated as runtime-owned and skips the
|
||||
binder (which would otherwise call getPluginStore()); declare it here so the
|
||||
route contracts under test stay isolated from plugin-loader bootstrapping.
|
||||
Same alignment as remote-access-routes.test.ts (d7752931b).
|
||||
*/
|
||||
getProjectScopedPluginMcpServers: vi.fn().mockResolvedValue([]),
|
||||
// FNXC:PostgresCutover 2026-07-05-16:20: routes borrow the AsyncDataLayer
|
||||
// from the scoped store (e.g. triggerCommentWakeForAssignedAgent builds a
|
||||
// backend AgentStore). null = legacy mode for this mock.
|
||||
|
||||
Reference in New Issue
Block a user