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:
@@ -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`.
|
||||
|
||||
@@ -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"); }
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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(() => []),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user