## Summary Restores green **Full Suite (non-blocking)** runs on `main`. Recent main merges left i18n key parity, schema baseline bookkeeping (0011→0012), heartbeat tool inventory (FN-8058 `fn_task_logs_read`), and merger whitespace-classification mocks (execFile `git diff -p -w :2: :3:`) out of date, so all four test shards failed. ## Root causes observed on main - **Shard 4 / `@fusion/i18n`**: missing `skipConfirmationDialogs*` + `reviewBudgetExhausted` in non-en locales; orphan `awaitingApprovalPlanReviewReplanCap` - **Shard 3 / `@fusion/core`**: `SCHEMA_BASELINE_VERSION` advanced to `0012` while tests still equated it with `OWNER_PROJECT_ID_SPLIT_VERSION` (`0011`) and omitted `0012` from applied-migration lists - **Shards 1–2 / `@fusion/engine`**: tool count/snapshot drift for `fn_task_logs_read`; merger tests still mocked `git diff-tree` for trivial classification after the execFile `:2:`/`:3:` cutover; mock provider `updateTask` arity drift ## Changes - Locale catalogs: add missing keys, drop orphan key - Schema applier tests: immutable 0011 identity + baseline 0012 lists - Heartbeat + gating snapshots: include `fn_task_logs_read` - Merger unit mocks: recognize `git diff -p -w :2:path :3:path` - Mock provider: accept optional third `updateTask` arg ## Test plan - [x] `pnpm --filter @fusion/i18n exec vitest run` — 23/23 - [x] `pnpm --filter @fusion/core exec vitest run src/__tests__/postgres/schema-applier.test.ts` (immutable + automation upgrade) — pass - [x] `pnpm --filter @fusion/core exec vitest run` project-identity + satellite-fusiondir — pass - [x] Engine suites from failed CI shards (file-scoped, hermes/openclaw/paperclip/grok, reliability post-finalize/mission, heartbeat, gating, merger recovery/prompt, mock-provider, etc.) — pass - [ ] Full Suite workflow green on merge to main <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved project data isolation across backend operations. - Added safer optional toast handling when UI components render outside the full application shell. - Added support for reading task logs during agent heartbeat sessions. - **Bug Fixes** - Prevented runtime probes from hanging and avoided scanning large binary files. - Improved path handling for workspaces with missing descendants. - Corrected task retry state resets and GitHub import/issue-close behavior. - **Style** - Improved chat, terminal, and settings spacing. - Added clearer accessibility labeling for the auto-merge control. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
170 lines
6.3 KiB
TypeScript
170 lines
6.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const taskStoreCtorMock = vi.hoisted(() => vi.fn());
|
|
const runGhJsonAsyncMock = vi.hoisted(() => vi.fn());
|
|
const resolveProjectMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@fusion/core", async (importActual) => {
|
|
const actual = await importActual<typeof import("@fusion/core")>();
|
|
return {
|
|
...actual,
|
|
createTaskStoreForBackend: vi.fn(async () => null),
|
|
TaskStore: taskStoreCtorMock,
|
|
};
|
|
});
|
|
|
|
vi.mock("@fusion/core/gh-cli", () => ({
|
|
isGhAvailable: vi.fn(() => true),
|
|
isGhAuthenticated: vi.fn(() => true),
|
|
runGhJsonAsync: runGhJsonAsyncMock,
|
|
getGhErrorMessage: vi.fn((error: unknown) => (error instanceof Error ? error.message : String(error))),
|
|
}));
|
|
|
|
// FNXC:CliBoardMutation 2026-07-09-00:00: task.ts imports closeProjectStore +
|
|
// asLocalProjectContext from project-context; stub both so the whole-module
|
|
// mock stays accurate as task.ts's project-context surface grows.
|
|
vi.mock("../project-context.js", () => ({
|
|
resolveProject: resolveProjectMock,
|
|
closeProjectStore: vi.fn(async (context: { store: { close?: () => unknown } }) => {
|
|
try {
|
|
await context.store.close?.();
|
|
} catch {
|
|
// best-effort, mirrors production closeProjectStore
|
|
}
|
|
}),
|
|
createLocalStore: vi.fn(async () => new (taskStoreCtorMock as unknown as new () => unknown)()),
|
|
asLocalProjectContext: vi.fn((store: unknown) => ({
|
|
projectId: process.cwd(),
|
|
projectPath: process.cwd(),
|
|
projectName: "current-project",
|
|
isRegistered: false,
|
|
store,
|
|
})),
|
|
createLocalStore: vi.fn(async () => new (taskStoreCtorMock as any)()),
|
|
}));
|
|
|
|
vi.mock("@fusion/dashboard", () => ({
|
|
registerGithubTrackingHook: vi.fn(),
|
|
// FNXC:CliTests 2026-07-13-09:40: Missing dashboard barrel exports added for mock completeness (scripts/check-mock-completeness.mjs gate).
|
|
GitLabClient: vi.fn(),
|
|
resolveGitlabAuth: vi.fn(() => ({})),
|
|
buildGitLabTaskProvenance: vi.fn(() => ({})),
|
|
isGitLabAlreadyImported: vi.fn(),
|
|
buildGitLabTaskDescription: vi.fn(),
|
|
buildGitHubIssueSource: vi.fn((owner: string, repo: string, issue: { number: number; html_url: string }) => ({
|
|
sourceIssue: { provider: "github", repository: `${owner}/${repo}`, externalIssueId: String(issue.number), issueNumber: issue.number, url: issue.html_url },
|
|
sourceMetadata: { issueUrl: issue.html_url, issueNumber: issue.number },
|
|
})),
|
|
isGitHubIssueAlreadyImported: vi.fn(() => false),
|
|
}));
|
|
|
|
vi.mock("@fusion/engine", () => ({
|
|
installBaselineArchiveWorktreeDisposer: vi.fn(),
|
|
createFnAgent: vi.fn(),
|
|
runAiMerge: vi.fn(),
|
|
landWorkspaceTask: vi.fn(),
|
|
// FNXC:TestInfrastructure 2026-07-13-10:25: extension.ts named-imports this from @fusion/engine.
|
|
isInReviewMissingWorktreeSessionStartFailure: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@fusion/dashboard/planning", () => ({
|
|
createSession: vi.fn(),
|
|
submitResponse: vi.fn(),
|
|
RateLimitError: class RateLimitError extends Error {},
|
|
SessionNotFoundError: class SessionNotFoundError extends Error {},
|
|
InvalidSessionStateError: class InvalidSessionStateError extends Error {},
|
|
}));
|
|
|
|
import { runTaskImportFromGitHub } from "../commands/task.js";
|
|
|
|
describe("fn task import GitHub tracking defaults", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
resolveProjectMock.mockRejectedValue(new Error("No project context"));
|
|
runGhJsonAsyncMock.mockResolvedValue([
|
|
{
|
|
number: 1,
|
|
title: "Imported Issue",
|
|
body: "Imported issue body",
|
|
html_url: "https://github.com/owner/repo/issues/1",
|
|
labels: [],
|
|
},
|
|
]);
|
|
});
|
|
|
|
function mockStore(options: { projectSettings?: Record<string, unknown>; globalSettings?: Record<string, unknown> } = {}) {
|
|
const createTask = vi.fn().mockImplementation((input) => Promise.resolve({
|
|
id: "FN-001",
|
|
title: input.title,
|
|
description: input.description,
|
|
column: "triage",
|
|
}));
|
|
taskStoreCtorMock.mockImplementation(function () {
|
|
return {
|
|
init: vi.fn().mockResolvedValue(undefined),
|
|
listTasks: vi.fn().mockResolvedValue([]),
|
|
createTask,
|
|
getSettings: vi.fn().mockResolvedValue(options.projectSettings ?? {}),
|
|
getGlobalSettingsStore: vi.fn().mockReturnValue({
|
|
getSettings: vi.fn().mockResolvedValue(options.globalSettings ?? {}),
|
|
}),
|
|
};
|
|
});
|
|
return { createTask };
|
|
}
|
|
|
|
it("sets githubTracking.enabled for fn task import when project tracking defaults are on", async () => {
|
|
const { createTask } = mockStore({ projectSettings: { githubTrackingEnabledByDefault: true } });
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
githubTracking: { enabled: true },
|
|
sourceIssue: expect.objectContaining({
|
|
provider: "github",
|
|
repository: "owner/repo",
|
|
issueNumber: 1,
|
|
}),
|
|
}));
|
|
});
|
|
|
|
it("sets githubTracking.enabled for fn task import when global tracking defaults are on", async () => {
|
|
const { createTask } = mockStore({ globalSettings: { githubTrackingDefaultEnabledForNewTasks: true } });
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
githubTracking: { enabled: true },
|
|
sourceIssue: expect.objectContaining({ issueNumber: 1 }),
|
|
}));
|
|
});
|
|
|
|
it("sets githubTracking.enabled for fn task import when import linking is on and new-task defaults are off", async () => {
|
|
const { createTask } = mockStore({
|
|
projectSettings: {
|
|
githubTrackingEnabledByDefault: false,
|
|
githubLinkImportedIssuesToTracking: true,
|
|
},
|
|
});
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
githubTracking: { enabled: true },
|
|
sourceIssue: expect.objectContaining({ provider: "github", repository: "owner/repo", issueNumber: 1 }),
|
|
}));
|
|
});
|
|
|
|
it("does not force githubTracking for fn task import when tracking defaults are off", async () => {
|
|
const { createTask } = mockStore();
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.not.objectContaining({
|
|
githubTracking: expect.anything(),
|
|
}));
|
|
});
|
|
});
|