fabro/apps/fabro-web/app/lib/mutations.test.ts
Bryan Helmkamp c6356cbd77
feat: improve run board, thread, and MCP create flows (#347)
## Summary

This branch improves several run-management surfaces that agents and
users rely on: archived runs now stay visible and ordered correctly in
the board view, pair-session messages appear in the stage Thread tab,
and the `fabro_run_create` MCP tool accepts the workflow-string
shorthand it advertises.

## Changes

- Updates the web board cache invalidation and archived-column handling
so archive/unarchive actions refresh both active and archived board
queries and keep archived runs in a predictable column position.
- Adds pair user/system message events to stage activity parsing, Thread
rendering, search, details, and DNA timeline items.
- Aligns `fabro_run_create` MCP runtime deserialization and `tools/list`
schema so each run entry may be either a workflow string or a full
create spec object.

## Test Plan

- `cargo nextest run -p fabro-tool -p fabro-mcp-server`
- `cargo nextest run -p fabro-cli
stdio_server_initializes_and_lists_run_tools
mcp_create_string_shorthand_deserializes_before_auth
mcp_create_validation_errors_happen_before_auth_or_network
mcp_create_and_search_manage_real_runs_with_cli_auth`
- `cargo +nightly-2026-04-14 fmt --check --all`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:39:20 -04:00

57 lines
1.5 KiB
TypeScript

import { describe, expect, mock, test, beforeEach } from "bun:test";
import { queryKeys } from "./query-keys";
const mutateMock = mock((..._args: unknown[]) => Promise.resolve(undefined));
let lastMutationOptions: { onSuccess?: (result: unknown) => void } | null = null;
const useSWRMutationMock = mock((_key: unknown, _fetcher: unknown, options: unknown) => {
lastMutationOptions = options as { onSuccess?: (result: unknown) => void };
return {};
});
mock.module("swr", () => ({
useSWRConfig: () => ({ mutate: mutateMock }),
}));
mock.module("swr/mutation", () => ({
default: useSWRMutationMock,
}));
mock.module("./api-client", () => ({
apiData: mock(),
authApi: {},
humanInTheLoopApi: {},
runsApi: {},
}));
mock.module("./run-actions", () => ({
archiveRun: mock(),
cancelRun: mock(),
isLifecycleActionError: () => false,
unarchiveRun: mock(),
}));
const { useArchiveRun } = await import("./mutations");
beforeEach(() => {
mutateMock.mockClear();
useSWRMutationMock.mockClear();
lastMutationOptions = null;
});
describe("lifecycle mutations", () => {
test("successful archive invalidates both board run caches", () => {
useArchiveRun("run-1");
lastMutationOptions?.onSuccess?.({
intent: "archive",
ok: true,
run: {},
});
const keys = mutateMock.mock.calls.map((call) => call[0]);
expect(keys).toContainEqual(queryKeys.boards.runs(false));
expect(keys).toContainEqual(queryKeys.boards.runs(true));
});
});