murashit_codex-panel/tests/features/chat/host/turn-bundle.test.ts

116 lines
4 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createServerDiagnostics, diagnosticsWithToolInventory } from "../../../../src/domain/server/diagnostics";
import type { ToolInventorySnapshot } from "../../../../src/domain/server/tool-inventory";
import { createChatStateStore } from "../../../../src/features/chat/application/state/store";
import { createTurnBundle } from "../../../../src/features/chat/host/bundles/turn-bundle";
describe("createTurnBundle", () => {
it("uses cached tool inventory for /tools without refreshing diagnostics", async () => {
const stateStore = createChatStateStore();
stateStore.dispatch({
type: "connection/metadata-applied",
serverDiagnostics: diagnosticsWithToolInventory(createServerDiagnostics(), toolInventory()),
});
const fixture = turnBundleFixture({ stateStore });
await fixture.bundle.turnActions.composerSubmit.submit();
expect(fixture.refreshDiagnostics).not.toHaveBeenCalled();
expect(fixture.runtimeProjection.toolInventoryDetails).toHaveBeenCalledOnce();
expect(fixture.status.addStructuredSystemMessage).toHaveBeenCalledWith("Codex capabilities", [
{ title: "Tool providers", auditFacts: [{ key: "codex_apps", value: "github, gmail" }] },
]);
});
it("refreshes diagnostics for /tools when tool inventory is not loaded", async () => {
const fixture = turnBundleFixture();
await fixture.bundle.turnActions.composerSubmit.submit();
expect(fixture.refreshDiagnostics).toHaveBeenCalledOnce();
expect(fixture.runtimeProjection.toolInventoryDetails).toHaveBeenCalledOnce();
});
});
function turnBundleFixture(options: { stateStore?: ReturnType<typeof createChatStateStore> } = {}) {
const stateStore = options.stateStore ?? createChatStateStore();
const status = {
set: vi.fn(),
addSystemMessage: vi.fn(),
addStructuredSystemMessage: vi.fn(),
};
const runtimeProjection = {
connectionDiagnosticDetails: vi.fn(() => []),
modelStatusDetails: vi.fn(() => []),
effortStatusDetails: vi.fn(() => []),
statusDetails: vi.fn(() => []),
permissionDetails: vi.fn(() => []),
toolInventoryDetails: vi.fn(() => [{ title: "Tool providers", auditFacts: [{ key: "codex_apps", value: "github, gmail" }] }]),
};
const refreshDiagnostics = vi.fn().mockResolvedValue(undefined);
const bundle = createTurnBundle(
{
stateStore,
threadStreamScrollBinding: {
showLatest: vi.fn(),
},
} as never,
{
localItemIds: { next: vi.fn(() => "local-id") },
appServer: {
connectionAvailable: vi.fn(() => true),
threadReferences: vi.fn(() => ({ referThread: vi.fn() })),
turn: { ensureConnected: vi.fn().mockResolvedValue(true) },
},
status,
inboundHandler: {},
threadLifecycle: {
restoration: { ensureLoaded: vi.fn().mockResolvedValue(true) },
resume: { resumeThread: vi.fn() },
},
threadActions: {},
navigation: {
startNewThread: vi.fn(),
selectThread: vi.fn(),
},
composerController: {
get draft() {
return "/tools";
},
get trimmedDraft() {
return "/tools";
},
setDraft: vi.fn(),
preparedInput: vi.fn(),
captureInputSnapshot: vi.fn(() => ({ sourcePath: "snapshot.md" })),
hasFocus: vi.fn(() => false),
focusComposer: vi.fn(),
},
runtimeSettings: {},
threadStart: {},
goals: {},
autoTitleCoordinator: { resetThreadTurnPresence: vi.fn() },
reconnect: vi.fn(),
runtimeProjection,
refreshDiagnostics,
refreshLiveState: vi.fn(),
notifyActiveThreadIdentityChanged: vi.fn(),
} as never,
);
return { bundle, refreshDiagnostics, runtimeProjection, status };
}
function toolInventory(): ToolInventorySnapshot {
return {
checkedAt: 1,
plugins: [],
pluginMarketplaceErrors: [],
pluginsError: null,
mcpServers: [],
mcpDiagnostics: [],
mcpError: null,
skills: [],
skillsError: null,
};
}