test(engine): stub reconcileSupersededGeneratedFixFeatures on mission-validation-trigger-gap mocks

recoverActiveMissions (mission-execution-loop.ts:263) calls
missionStore.reconcileSupersededGeneratedFixFeatures per slice; the 5
MissionExecutionLoop-backed mocks here omitted it, so recovery threw
(TypeError) at the slice loop and aborted before processTaskOutcome /
ensureFeatureAssertionLinked / startValidatorRun ran — 4 tests failed.

Add a no-op stub (matches mission-execution-loop.test.ts reference) with
an FNXC:MissionReconcile note. No-op is correct: supersession is not
exercised by these tests.
This commit is contained in:
gsxdsm
2026-07-07 08:24:35 -07:00
parent 0782184f07
commit f3c50de7e7
9 changed files with 35 additions and 10 deletions

View File

@@ -142,6 +142,8 @@ Configure a GitLab project or group webhook with:
Fusion verifies GitLab's `X-Gitlab-Token` header. GitLab's webhook docs now recommend signing tokens for new webhooks, but this connector intentionally supports the documented secret-token compatibility path required by existing GitLab.com and self-managed GitLab installations. This task introduces no GitLab binary, CLI, download, or checksum-managed artifact.
Broader GitHub-to-GitLab parity (issue import, linked tracking, lifecycle automation, and Command Center analytics) is mapped in [GitLab Parity Inventory](./gitlab-parity-inventory.md). These signal webhooks are the GitLab side of that parity surface.
Supported GitLab events:
- Project and group **Issue Hook** payloads with `object_kind`/`event_type` of `issue`.

View File

@@ -10,8 +10,11 @@ import type { TaskStore } from "@fusion/core";
import { request } from "../test-request.js";
import { createSSE } from "../sse.js";
class MockStore {
constructor(private readonly rootDir: string, private readonly db: Database) {}
// FNXC:DashboardTests 2026-07-07-08:10: createServer now subscribes via store.on("task:moved") (TaskStore extends EventEmitter) to purge task-planner chats on archive (FN-7337); back the mock store with a real EventEmitter so server startup wiring works instead of throwing "store.on is not a function".
class MockStore extends EventEmitter {
constructor(private readonly rootDir: string, private readonly db: Database) {
super();
}
getRootDir(): string { return this.rootDir; }
getFusionDir(): string { return join(this.rootDir, ".fusion"); }

View File

@@ -16,6 +16,8 @@ function createStore(name: string): TaskStore {
getSettings: vi.fn().mockResolvedValue({}),
getGlobalSettingsStore: vi.fn(() => ({ getSettings: vi.fn().mockResolvedValue({}) })),
logEntry: vi.fn().mockResolvedValue(undefined),
// FNXC:DashboardTests 2026-07-07-08:10: createServer subscribes via store.on("task:moved") to purge task-planner chats on archive (FN-7337); provide a no-op EventEmitter "on" so server startup wiring works instead of throwing "store.on is not a function".
on: vi.fn(),
updateTask: vi.fn().mockResolvedValue(undefined),
getDatabase: vi.fn().mockReturnValue({
exec: vi.fn(),

View File

@@ -57,8 +57,14 @@ vi.mock("node:child_process", async (importOriginal) => {
};
});
vi.mock("@fusion/core", () => {
vi.mock("@fusion/core", async (importOriginal) => {
/*
FNXC:DashboardAgentImportTests 2026-07-07-08:05:
Spread the real @fusion/core module and override only the agent-import seams (AgentStore, ChatStore, the company parsers, and the no-op guard/hook stubs). FN-7444 added planning-summary deepening constants (PLANNING_DEEPEN_PROCEED_OPTION_ID etc.) that src/planning.ts imports from core; a fully hand-written mock omitted them and made createServer fail to load with "No export is defined on the @fusion/core mock". Spreading importOriginal keeps every real export (including future additions) resolvable while the explicit keys below retain the focused mock behavior. This also removes the prior duplicate CLI_AGENT_ADAPTER_IDS / sanitizeCliAgentSettings keys (a merge artifact whose second copy silently won).
*/
const actual = await importOriginal() as Record<string, unknown>;
return {
...actual,
AgentStore: class MockAgentStore {
init = mockInit;
listAgents = mockListAgents;
@@ -71,11 +77,7 @@ vi.mock("@fusion/core", () => {
parseCompanyArchive: (...args: unknown[]) => mockParseCompanyArchive(...args),
parseSingleAgentManifest: (...args: unknown[]) => mockParseSingleAgentManifest(...args),
prepareAgentCompaniesImport: (...args: unknown[]) => mockPrepareAgentCompaniesImport(...args),
CLI_AGENT_ADAPTER_IDS: ["claude-code", "codex", "droid", "pi", "generic"],
sanitizeCliAgentSettings: (value: unknown) => value,
AgentCompaniesParseError: MockAgentCompaniesParseError,
CLI_AGENT_ADAPTER_IDS: ["claude-code", "codex", "droid", "pi", "generic"],
sanitizeCliAgentSettings: () => undefined,
isEphemeralAgent: (agent: { metadata?: Record<string, unknown> }) =>
agent?.metadata?.agentKind === "task-worker",
deterministicGuardLocks: new Map(),

View File

@@ -2,6 +2,7 @@
FNXC:DashboardTests 2026-06-14-09:58:
FN-6444 rescues this server route test from the curated skip-list; the fake SQLite statement returns better-sqlite-style mutation metadata so createServer boot sweeps exercise real startup paths.
*/
import { EventEmitter } from "node:events";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { request } from "../test-request.js";
@@ -23,7 +24,8 @@ vi.mock("@fusion/core", async () => {
};
});
class MockStore {
// FNXC:DashboardTests 2026-07-07-08:10: createServer now subscribes via store.on("task:moved") (TaskStore extends EventEmitter) to purge task-planner chats on archive (FN-7337); back the mock store with a real EventEmitter so server startup wiring works instead of throwing "store.on is not a function".
class MockStore extends EventEmitter {
getRunAuditEvents = mockGetRunAuditEvents;
getAgentLogsByTimeRange = vi.fn().mockResolvedValue([]);
getMutationsForRun = vi.fn().mockResolvedValue([]);

View File

@@ -1,3 +1,4 @@
import { EventEmitter } from "node:events";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { request } from "../test-request.js";
@@ -20,7 +21,8 @@ vi.mock("../project-store-resolver.js", () => ({
getOrCreateProjectStore: vi.fn(),
}));
class MockStore {
// FNXC:DashboardTests 2026-07-07-08:10: createServer now subscribes via store.on("task:moved") (TaskStore extends EventEmitter) to purge task-planner chats on archive (FN-7337); back the mock store with a real EventEmitter so server startup wiring works instead of throwing "store.on is not a function".
class MockStore extends EventEmitter {
getRunAuditEvents = mockGetRunAuditEvents;
getAgentLogsByTimeRange = vi.fn().mockResolvedValue([]);
getMutationsForRun = vi.fn().mockResolvedValue([]);

View File

@@ -49,6 +49,8 @@ vi.mock("@fusion/engine", () => ({
resolvedSkillNames: [],
skillSource: "none" as const,
})),
// FNXC:DashboardSessionTests 2026-07-07-08:15: planning/mission-interview sessions now resolve MCP servers via resolveMcpServersForStore before createFnAgent; focused engine mock must export it (returning the real empty-runtime shape) so session generation completes instead of throwing on a missing mock export.
resolveMcpServersForStore: vi.fn(async () => ({ servers: [], errors: [] })),
createFnAgent: mockCreateFnAgent,
}));

View File

@@ -128,7 +128,7 @@ describe("POST /tasks workflowId (U6/R3)", () => {
it.each([
["default coding", "builtin:coding", ["plan-review", "code-review"]],
["legacy coding", "builtin:legacy-coding", ["code-review"]],
["legacy coding", "builtin:legacy-coding", ["plan-review", "code-review"]],
["coding per-step review", "builtin:stepwise-coding", ["plan-review", "code-review"]],
])("%s workflow create/select/resolve works end to end", async (_label, workflowId, defaultSteps) => {
const res = await post("/api/tasks", {

View File

@@ -101,6 +101,8 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
getFeature: vi.fn(() => feature),
transitionLoopState: vi.fn(),
// FNXC:MissionReconcile 2026-07-07-08:21 real MissionStore method (mission-store.ts:3185); recoverActiveMissions calls it per slice and aborts recovery if missing — stub even when supersession isn't exercised.
reconcileSupersededGeneratedFixFeatures: vi.fn(() => ({ supersededCount: 0, featureIds: [] as string[] })),
};
const taskStore = {
getTask: vi.fn(async () => ({ id: "FN-001", column: "done" })),
@@ -132,6 +134,8 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
getFeature: vi.fn(() => feature),
transitionLoopState: vi.fn(),
// FNXC:MissionReconcile 2026-07-07-08:21 real MissionStore method (mission-store.ts:3185); recoverActiveMissions calls it per slice and aborts recovery if missing — stub even when supersession isn't exercised.
reconcileSupersededGeneratedFixFeatures: vi.fn(() => ({ supersededCount: 0, featureIds: [] as string[] })),
};
const taskStore = {
getTask: vi.fn(async () => ({ id: "FN-001", column: "done" })),
@@ -167,6 +171,8 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
listAssertionsForFeature: vi.fn(() => [{ id: "CA-1" }]),
getFeature: vi.fn(() => feature),
transitionLoopState: vi.fn(),
// FNXC:MissionReconcile 2026-07-07-08:21 real MissionStore method (mission-store.ts:3185); recoverActiveMissions calls it per slice and aborts recovery if missing — stub even when supersession isn't exercised.
reconcileSupersededGeneratedFixFeatures: vi.fn(() => ({ supersededCount: 0, featureIds: [] as string[] })),
};
const taskStore = {
getTask: vi.fn(async () => ({ id: "FN-001", column: "done" })),
@@ -222,6 +228,8 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
getMission: vi.fn(() => ({ id: "M-001", status: "active" })),
logMissionEvent: vi.fn(),
transitionLoopState: vi.fn(),
// FNXC:MissionReconcile 2026-07-07-08:21 real MissionStore method (mission-store.ts:3185); recoverActiveMissions calls it per slice and aborts recovery if missing — stub even when supersession isn't exercised.
reconcileSupersededGeneratedFixFeatures: vi.fn(() => ({ supersededCount: 0, featureIds: [] as string[] })),
setFeatureCurrentTaskRunId: vi.fn(),
getFailuresForRun: vi.fn(() => []),
};
@@ -289,6 +297,8 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
getMission: vi.fn(() => ({ id: "M-001", status: "active" })),
logMissionEvent: vi.fn(),
transitionLoopState: vi.fn(),
// FNXC:MissionReconcile 2026-07-07-08:21 real MissionStore method (mission-store.ts:3185); recoverActiveMissions calls it per slice and aborts recovery if missing — stub even when supersession isn't exercised.
reconcileSupersededGeneratedFixFeatures: vi.fn(() => ({ supersededCount: 0, featureIds: [] as string[] })),
setFeatureCurrentTaskRunId: vi.fn(),
getFailuresForRun: vi.fn(() => []),
};