From 0bca5b3477b7d6c8bc1140486b07c3b1b77674c0 Mon Sep 17 00:00:00 2001 From: murashit Date: Sat, 27 Jun 2026 12:47:51 +0900 Subject: [PATCH] Prune low-value regression tests --- tests/app-server/catalog.test.ts | 14 ++ .../generated-import-boundary.test.ts | 67 ------- tests/app-server/server-requests.test.ts | 182 ------------------ .../slash-command-execution.test.ts | 16 -- .../application/state/root-reducer.test.ts | 24 +-- .../chat/panel/toolbar-actions.test.ts | 102 +--------- tests/features/threads-view/state.test.ts | 71 +------ tests/settings/settings-lifecycle.test.ts | 35 +--- 8 files changed, 23 insertions(+), 488 deletions(-) delete mode 100644 tests/app-server/generated-import-boundary.test.ts delete mode 100644 tests/app-server/server-requests.test.ts diff --git a/tests/app-server/catalog.test.ts b/tests/app-server/catalog.test.ts index 29d63629..d1583385 100644 --- a/tests/app-server/catalog.test.ts +++ b/tests/app-server/catalog.test.ts @@ -117,6 +117,20 @@ describe("app-server catalog adapters", () => { errors: ['{"message":"err"}'], }); }); + + it("does not fall back to unrelated hook rows", async () => { + const client = { + listHooks: vi.fn().mockResolvedValue({ + data: [{ cwd: "/other", hooks: [{ key: "other" }], warnings: ["skip"], errors: [{ message: "skip" }] }], + }), + } as unknown as AppServerClient; + + await expect(listHookCatalog(client, "/vault")).resolves.toEqual({ + hooks: [], + warnings: [], + errors: [], + }); + }); }); function modelFixture(): Model { diff --git a/tests/app-server/generated-import-boundary.test.ts b/tests/app-server/generated-import-boundary.test.ts deleted file mode 100644 index 38c81d45..00000000 --- a/tests/app-server/generated-import-boundary.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { readdir, readFile } from "node:fs/promises"; -import path from "node:path"; -import { describe, expect, it } from "vitest"; - -const repoRoot = process.cwd(); -const sourceRoot = path.join(repoRoot, "src"); -const testsRoot = path.join(repoRoot, "tests"); -const generatedImportPattern = /(?:from\s+|import\s*\(\s*|require\s*\(\s*)["'][^"']*generated\/app-server\//; -const sourceExtensions = new Set([".ts", ".tsx"]); - -describe("generated app-server import boundary", () => { - it("detects static, import type, and re-export generated app-server references", () => { - expect(generatedImportPattern.test('import type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true); - expect(generatedImportPattern.test('type Thread = import("../../src/generated/app-server/v2/Thread").Thread;')).toBe(true); - expect(generatedImportPattern.test('const thread = require("../../src/generated/app-server/v2/Thread");')).toBe(true); - expect(generatedImportPattern.test('export type { Thread } from "../../src/generated/app-server/v2/Thread";')).toBe(true); - expect(generatedImportPattern.test('export * from "../../src/generated/app-server/v2/Thread";')).toBe(true); - }); - - it("keeps generated app-server types behind explicit app-server adapters", async () => { - const files = await sourceFiles(sourceRoot); - const offenders: string[] = []; - - for (const file of files) { - const relativePath = slashPath(path.relative(repoRoot, file)); - if (relativePath.startsWith("src/generated/")) continue; - if (allowsGeneratedAppServerImport(relativePath)) continue; - if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath); - } - - expect(offenders).toEqual([]); - }); - - it("keeps generated app-server types out of feature tests", async () => { - const files = await sourceFiles(testsRoot); - const offenders: string[] = []; - - for (const file of files) { - const relativePath = slashPath(path.relative(repoRoot, file)); - if (relativePath.startsWith("tests/app-server/")) continue; - if (generatedImportPattern.test(await readFile(file, "utf8"))) offenders.push(relativePath); - } - - expect(offenders).toEqual([]); - }); -}); - -async function sourceFiles(directory: string): Promise { - const entries = await readdir(directory, { withFileTypes: true }); - const files = await Promise.all( - entries.map(async (entry) => { - const fullPath = path.join(directory, entry.name); - if (entry.isDirectory()) return sourceFiles(fullPath); - if (entry.isFile() && sourceExtensions.has(path.extname(entry.name))) return [fullPath]; - return []; - }), - ); - return files.flat(); -} - -function slashPath(value: string): string { - return value.split(path.sep).join("/"); -} - -function allowsGeneratedAppServerImport(relativePath: string): boolean { - return relativePath.startsWith("src/app-server/connection/"); -} diff --git a/tests/app-server/server-requests.test.ts b/tests/app-server/server-requests.test.ts deleted file mode 100644 index c74f1740..00000000 --- a/tests/app-server/server-requests.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import type { ServerRequest } from "../../src/app-server/connection/rpc-messages"; -import { - appServerApprovalRequest, - appServerApprovalResponse, - appServerMcpElicitationRequest, - appServerMcpElicitationResponse, - appServerUserInputRequest, - appServerUserInputResponse, -} from "../../src/app-server/protocol/server-requests"; - -function expectPresent(value: T | null | undefined): T { - if (value === null || value === undefined) throw new Error("Expected value to be present"); - return value; -} - -describe("app-server server request protocol adapters", () => { - it("classifies approval requests and builds approval responses", () => { - const request: ServerRequest = { - id: 2, - method: "item/permissions/requestApproval", - params: { - cwd: "/tmp/project", - threadId: "thread", - turnId: "turn", - itemId: "item", - environmentId: null, - startedAtMs: 1, - reason: "Need network", - permissions: { network: { enabled: true }, fileSystem: null }, - }, - }; - - const approval = expectPresent(appServerApprovalRequest(request)); - - expect(approval.kind).toBe("permission"); - expect(appServerApprovalResponse(approval, "decline")).toEqual({ permissions: {}, scope: "turn" }); - expect(appServerApprovalResponse(approval, "accept")).toEqual({ - permissions: { network: { enabled: true } }, - scope: "turn", - }); - }); - - it("classifies user input requests and builds answer responses", () => { - const request: ServerRequest = { - id: 7, - method: "item/tool/requestUserInput", - params: { - threadId: "thread", - turnId: "turn", - itemId: "item", - questions: [ - { - id: "direction", - header: "Direction", - question: "Which way?", - isOther: true, - isSecret: false, - options: [{ label: "Recommended", description: "Use the default path" }], - }, - ], - autoResolutionMs: null, - }, - }; - - const input = expectPresent(appServerUserInputRequest(request)); - - expect(input.params.questions[0]?.id).toBe("direction"); - expect(appServerUserInputResponse(input.params.questions, { direction: "Left" })).toEqual({ - answers: { direction: { answers: ["Left"] } }, - }); - }); - - it("normalizes MCP form schema fields and response content", () => { - const input = expectPresent(appServerMcpElicitationRequest(formRequest())); - - expect(input).toMatchObject({ - requestId: 42, - params: { - mode: "form", - threadId: "thread", - turnId: null, - serverName: "github", - }, - }); - if (input.params.mode !== "form") throw new Error("Expected form mode"); - expect(input.params.fields).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: "title", type: "string", title: "Title", required: true, defaultValue: "Issue" }), - expect.objectContaining({ id: "notify", type: "boolean", defaultValue: true }), - expect.objectContaining({ id: "count", type: "integer", defaultValue: 2 }), - expect.objectContaining({ - id: "labels", - type: "multi-select", - minItems: 1, - maxItems: 2, - options: [ - { value: "bug", label: "Bug" }, - { value: "docs", label: "Docs" }, - ], - }), - ]), - ); - - expect(appServerMcpElicitationResponse("accept", { title: "Fix bug", labels: ["bug"] })).toEqual({ - action: "accept", - content: { title: "Fix bug", labels: ["bug"] }, - _meta: null, - }); - expect(appServerMcpElicitationResponse("decline", { title: "Fix bug" })).toEqual({ - action: "decline", - content: null, - _meta: null, - }); - }); - - it("normalizes MCP url mode separately from form fields", () => { - expect(appServerMcpElicitationRequest(urlRequest())).toMatchObject({ - requestId: 43, - params: { - mode: "url", - url: "https://example.com/confirm", - elicitationId: "elicit-1", - }, - }); - }); -}); - -function formRequest(): ServerRequest { - return { - id: 42, - method: "mcpServer/elicitation/request", - params: { - threadId: "thread", - turnId: null, - serverName: "github", - mode: "form", - _meta: null, - message: "Provide issue details", - requestedSchema: { - type: "object", - required: ["title"], - properties: { - title: { type: "string", title: "Title", description: "Issue title", default: "Issue" }, - labels: { - type: "array", - title: "Labels", - minItems: 1, - maxItems: 2, - items: { - anyOf: [ - { const: "bug", title: "Bug" }, - { const: "docs", title: "Docs" }, - ], - }, - default: ["bug"], - }, - notify: { type: "boolean", title: "Notify", default: true }, - count: { type: "integer", title: "Count", default: 2 }, - }, - }, - }, - } as unknown as ServerRequest; -} - -function urlRequest(): ServerRequest { - return { - id: 43, - method: "mcpServer/elicitation/request", - params: { - threadId: "thread", - turnId: "turn", - serverName: "github", - mode: "url", - _meta: null, - message: "Confirm in browser", - url: "https://example.com/confirm", - elicitationId: "elicit-1", - }, - }; -} diff --git a/tests/features/chat/application/conversation/slash-command-execution.test.ts b/tests/features/chat/application/conversation/slash-command-execution.test.ts index 9c948c7b..4bb0f384 100644 --- a/tests/features/chat/application/conversation/slash-command-execution.test.ts +++ b/tests/features/chat/application/conversation/slash-command-execution.test.ts @@ -480,10 +480,6 @@ describe("slash commands", () => { expect(ctx.addSystemMessage).toHaveBeenCalledWith("Multiple matching threads: Draft (thread-a), Draft notes (thread-b)"); }); - it("documents archive", () => { - expect(slashCommandHelpValue("/archive ")).toBe("Archive the selected Codex thread."); - }); - it("shows slash command help as a structured system result", async () => { const ctx = context(); @@ -582,10 +578,6 @@ describe("slash commands", () => { expect(ctx.addSystemMessage).toHaveBeenCalledWith("/doctor does not take arguments. Usage: /doctor"); }); - it("documents that /plan can take a message", () => { - expect(slashCommandHelpValue("/plan [message]")).toBe("Toggle Plan mode, optionally with a message."); - }); - it("expands goal subcommands in help", () => { expect(slashCommandHelpValue("/goal [set |edit|pause|resume|clear]")).toBeUndefined(); expect(slashCommandHelpValue("/goal")).toBe("Show or manage the current thread goal."); @@ -601,14 +593,6 @@ describe("slash commands", () => { expect(slashCommandHelpValue("/rollback")).toBe("Roll back the latest turn and restore its prompt."); }); - it("documents rename", () => { - expect(slashCommandHelpValue("/rename ")).toBe("Rename the selected Codex thread."); - }); - - it("documents refer history size", () => { - expect(slashCommandHelpValue("/refer ")).toBe("Send a message with recent turns from another non-archived thread."); - }); - it("documents status and doctor as separate commands", () => { expect(slashCommandHelpValue("/status")).toBe("Show current thread, context, and usage limits."); expect(slashCommandHelpValue("/doctor")).toBe("Show Codex CLI and Codex App Server diagnostics."); diff --git a/tests/features/chat/application/state/root-reducer.test.ts b/tests/features/chat/application/state/root-reducer.test.ts index fb99dc59..00d4c980 100644 --- a/tests/features/chat/application/state/root-reducer.test.ts +++ b/tests/features/chat/application/state/root-reducer.test.ts @@ -1,12 +1,7 @@ import { describe, expect, it } from "vitest"; import type { ThreadGoal } from "../../../../../src/domain/threads/goal"; import type { Thread } from "../../../../../src/domain/threads/model"; -import { - activeTurnId, - chatTurnBusy, - pendingTurnStart, - transitionChatTurnLifecycleState, -} from "../../../../../src/features/chat/application/conversation/turn-state"; +import { activeTurnId, chatTurnBusy, pendingTurnStart } from "../../../../../src/features/chat/application/conversation/turn-state"; import { messageStreamItems } from "../../../../../src/features/chat/application/state/message-stream"; import { type ChatState, chatReducer } from "../../../../../src/features/chat/application/state/root-reducer"; import { createChatStateStore } from "../../../../../src/features/chat/application/state/store"; @@ -249,23 +244,6 @@ describe("chatReducer", () => { expect(pendingTurnStart(completed)).toBeNull(); }); - it("keeps turn lifecycle transition rules explicit", () => { - const pending = { anchorItemId: "local-user", promptSubmitHookItemIds: ["hook"] }; - const optimistic = transitionChatTurnLifecycleState({ kind: "idle" }, { type: "optimistic-started", pendingTurnStart: pending }); - const running = transitionChatTurnLifecycleState(optimistic, { type: "start-acknowledged", turnId: "turn" }); - - expect(optimistic).toEqual({ kind: "starting", pendingTurnStart: pending }); - expect(running).toEqual({ kind: "running", turnId: "turn" }); - expect(transitionChatTurnLifecycleState(running, { type: "completed", turnId: "stale-turn" })).toEqual({ - kind: "running", - turnId: "turn", - }); - expect(transitionChatTurnLifecycleState({ kind: "idle" }, { type: "start-acknowledged", turnId: "turn" })).toEqual({ - kind: "idle", - }); - expect(transitionChatTurnLifecycleState(running, { type: "completed", turnId: "turn" })).toEqual({ kind: "idle" }); - }); - it("appends streaming assistant deltas after stable history", () => { let state = chatStateFixture(); state = withChatStateMessageStreamItems(state, [message("history")]); diff --git a/tests/features/chat/panel/toolbar-actions.test.ts b/tests/features/chat/panel/toolbar-actions.test.ts index 5f798139..170b9f54 100644 --- a/tests/features/chat/panel/toolbar-actions.test.ts +++ b/tests/features/chat/panel/toolbar-actions.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest"; import { createChatState } from "../../../../src/features/chat/application/state/root-reducer"; import { createChatStateStore } from "../../../../src/features/chat/application/state/store"; import type { ThreadManagementActions } from "../../../../src/features/chat/application/threads/thread-management-actions"; -import { createToolbarPanelActions, createToolbarUiActions } from "../../../../src/features/chat/panel/toolbar-actions"; +import { createToolbarPanelActions } from "../../../../src/features/chat/panel/toolbar-actions"; describe("createToolbarPanelActions", () => { it("tracks archive confirmation and delegates archive actions", async () => { @@ -42,103 +42,3 @@ describe("createToolbarPanelActions", () => { expect(stateStore.getState().ui.toolbarPanel).toBeNull(); }); }); - -describe("createToolbarUiActions", () => { - it("maps primary toolbar controls to panel actions", () => { - const deps = toolbarActionDeps(); - const actions = createToolbarUiActions(deps); - - actions.primary.toggleHistory(); - actions.primary.toggleChatActions(); - actions.primary.toggleStatusPanel(); - - expect(vi.mocked(deps.toolbarPanel.toggleHistory)).toHaveBeenCalledOnce(); - expect(vi.mocked(deps.toolbarPanel.toggleChatActions)).toHaveBeenCalledOnce(); - expect(vi.mocked(deps.toolbarPanel.toggleStatus)).toHaveBeenCalledOnce(); - }); - - it("starts a new thread through thread navigation", () => { - const deps = toolbarActionDeps(); - const actions = createToolbarUiActions(deps); - - actions.chat.startNewThread(); - - expect(vi.mocked(deps.navigation.startNewThread)).toHaveBeenCalledOnce(); - }); - - it("delegates compacting the active thread", () => { - const deps = toolbarActionDeps(); - const actions = createToolbarUiActions(deps); - - actions.chat.compactConversation(); - - expect(vi.mocked(deps.threadActions.compactActiveThread)).toHaveBeenCalledOnce(); - }); - - it("starts the goal editor from the active goal", () => { - const deps = toolbarActionDeps(); - const actions = createToolbarUiActions(deps); - - actions.chat.setGoal(); - - expect(vi.mocked(deps.goals.startEditingCurrent)).toHaveBeenCalledOnce(); - }); - - it("maps thread list actions to navigation, archive, and rename actions", () => { - const deps = toolbarActionDeps(); - const actions = createToolbarUiActions(deps); - - actions.threads.resume("thread"); - actions.threads.archive.start("thread"); - actions.threads.archive.confirm("thread", true); - actions.threads.rename.start("thread"); - actions.threads.rename.updateDraft("thread", "Draft"); - actions.threads.rename.save("thread", "Saved"); - actions.threads.rename.cancel("thread"); - actions.threads.rename.autoName("thread"); - - expect(vi.mocked(deps.navigation.selectThreadFromToolbar)).toHaveBeenCalledWith("thread"); - expect(vi.mocked(deps.toolbarPanel.startArchive)).toHaveBeenCalledWith("thread"); - expect(vi.mocked(deps.toolbarPanel.archiveThread)).toHaveBeenCalledWith("thread", true); - expect(vi.mocked(deps.rename.start)).toHaveBeenCalledWith("thread"); - expect(vi.mocked(deps.rename.updateDraft)).toHaveBeenCalledWith("thread", "Draft"); - expect(vi.mocked(deps.rename.save)).toHaveBeenCalledWith("thread", "Saved"); - expect(vi.mocked(deps.rename.cancel)).toHaveBeenCalledWith("thread"); - expect(vi.mocked(deps.rename.autoNameDraft)).toHaveBeenCalledWith("thread"); - }); -}); - -function toolbarActionDeps(): Parameters[0] { - return { - connectionController: { refreshStatusPanel: vi.fn() } as unknown as Parameters< - typeof createToolbarUiActions - >[0]["connectionController"], - reconnectPanel: vi.fn(), - threadActions: { - archiveThread: vi.fn().mockResolvedValue(undefined), - compactActiveThread: vi.fn().mockResolvedValue(undefined), - compactThread: vi.fn().mockResolvedValue(undefined), - } as unknown as ThreadManagementActions, - goals: { - startEditingCurrent: vi.fn(), - } as unknown as Parameters[0]["goals"], - toolbarPanel: { - toggleChatActions: vi.fn(), - toggleHistory: vi.fn(), - toggleStatus: vi.fn(), - startArchive: vi.fn(), - archiveThread: vi.fn().mockResolvedValue(undefined), - } as unknown as Parameters[0]["toolbarPanel"], - rename: { - start: vi.fn(), - updateDraft: vi.fn(), - save: vi.fn().mockResolvedValue(undefined), - cancel: vi.fn(), - autoNameDraft: vi.fn().mockResolvedValue(undefined), - } as unknown as Parameters[0]["rename"], - navigation: { - startNewThread: vi.fn().mockResolvedValue(undefined), - selectThreadFromToolbar: vi.fn().mockResolvedValue(undefined), - } as unknown as Parameters[0]["navigation"], - }; -} diff --git a/tests/features/threads-view/state.test.ts b/tests/features/threads-view/state.test.ts index deb278f5..1bbb19e2 100644 --- a/tests/features/threads-view/state.test.ts +++ b/tests/features/threads-view/state.test.ts @@ -1,35 +1,14 @@ import { describe, expect, it } from "vitest"; import type { Thread } from "../../../src/domain/threads/model"; -import { type ThreadsRenameState, threadRows, transitionThreadsRenameState } from "../../../src/features/threads-view/state"; +import { threadRows, transitionThreadsRenameState } from "../../../src/features/threads-view/state"; import type { OpenCodexPanelSnapshot } from "../../../src/workspace/panel-coordinator"; describe("threads view rename state", () => { - it("keeps a late auto-name result from reviving a cancelled rename", () => { - const generating = generatingRenameState("Original draft", 1); - - expect( - transitionThreadsRenameState(undefined, { type: "auto-name-generated", generatingState: generating, title: "Late title" }), - ).toBeUndefined(); - expect(transitionThreadsRenameState(undefined, { type: "auto-name-finished", generatingState: generating })).toBeUndefined(); - }); - - it("keeps a manually edited draft when auto-name finishes later", () => { - const generating = generatingRenameState("Original draft", 1); - - const manuallyEdited = transitionThreadsRenameState(generating, { type: "draft-updated", draft: "Manual draft" }); - - expect( - transitionThreadsRenameState(manuallyEdited, { type: "auto-name-generated", generatingState: generating, title: "Late title" }), - ).toBe(manuallyEdited); - expect(transitionThreadsRenameState(manuallyEdited, { type: "auto-name-finished", generatingState: generating })).toEqual({ - kind: "editing", - draft: "Manual draft", - }); - }); - - it("applies generated titles only to the active unchanged generation", () => { - const generating = generatingRenameState("Original draft", 1); + it("maps threads view auto-name events through the shared rename lifecycle", () => { + const editing = transitionThreadsRenameState(undefined, { type: "started", draft: "Original draft" }); + const generating = transitionThreadsRenameState(editing, { type: "auto-name-started", generationToken: 1 }); + if (generating?.kind !== "generating") throw new Error("Expected generating state"); const generated = transitionThreadsRenameState(generating, { type: "auto-name-generated", @@ -47,30 +26,8 @@ describe("threads view rename state", () => { kind: "editing", draft: "Generated title", }); - }); - - it("keeps ignored rename lifecycle transitions as no-ops", () => { - const editing = editingRenameState("Original draft"); - const generating = generatingRenameState("Original draft", 1); - + expect(transitionThreadsRenameState(generated, { type: "cancelled" })).toBeUndefined(); expect(transitionThreadsRenameState(undefined, { type: "draft-updated", draft: "Stray" })).toBeUndefined(); - expect(transitionThreadsRenameState(undefined, { type: "auto-name-started", generationToken: 1 })).toBeUndefined(); - expect(transitionThreadsRenameState(editing, { type: "auto-name-generated", generatingState: generating, title: "Late" })).toBe( - editing, - ); - expect(transitionThreadsRenameState(generating, { type: "auto-name-started", generationToken: 2 })).toBe(generating); - }); - - it("keeps stale auto-name completion from finishing a newer generation", () => { - const oldGenerating = generatingRenameState("Original draft", 1); - const currentGenerating = generatingRenameState("Original draft", 2); - - expect( - transitionThreadsRenameState(currentGenerating, { type: "auto-name-generated", generatingState: oldGenerating, title: "Old" }), - ).toBe(currentGenerating); - expect(transitionThreadsRenameState(currentGenerating, { type: "auto-name-finished", generatingState: oldGenerating })).toBe( - currentGenerating, - ); }); it("initializes rename drafts from normalized explicit thread names", () => { @@ -96,22 +53,6 @@ describe("threads view rename state", () => { }); }); -function editingRenameState(draft: string): ThreadsRenameState { - return expectRenameState(transitionThreadsRenameState(undefined, { type: "started", draft })); -} - -function generatingRenameState(draft: string, generationToken: number): Extract { - const editing = editingRenameState(draft); - const generating = transitionThreadsRenameState(editing, { type: "auto-name-started", generationToken }); - if (generating?.kind !== "generating") throw new Error("Expected generating state"); - return generating; -} - -function expectRenameState(state: ThreadsRenameState | undefined): ThreadsRenameState { - if (state) return state; - throw new Error("Expected rename state"); -} - function thread(overrides: Partial = {}): Thread { return { id: "thread", diff --git a/tests/settings/settings-lifecycle.test.ts b/tests/settings/settings-lifecycle.test.ts index d3bb291a..9930d6e9 100644 --- a/tests/settings/settings-lifecycle.test.ts +++ b/tests/settings/settings-lifecycle.test.ts @@ -1,6 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; -import { listHookCatalog } from "../../src/app-server/catalog"; -import type { AppServerClient } from "../../src/app-server/connection/client"; +import { describe, expect, it } from "vitest"; import { createSettingsDynamicSectionLifecycle, transitionSettingsDynamicSectionLifecycle } from "../../src/settings/lifecycle"; describe("settings lifecycle", () => { @@ -35,35 +33,4 @@ describe("settings lifecycle", () => { expect(transitionSettingsDynamicSectionLifecycle(failed, { type: "reset" })).toEqual(idle); }); - - it("uses only hook rows for the requested cwd", async () => { - const client = { - listHooks: vi.fn().mockResolvedValue({ - data: [ - { cwd: "/other", hooks: [{ key: "other" }], warnings: ["skip"], errors: [{ message: "skip" }] }, - { cwd: "/vault", hooks: [{ key: "vault" }], warnings: ["warn"], errors: [{ message: "err" }] }, - ], - }), - } as unknown as AppServerClient; - - await expect(listHookCatalog(client, "/vault")).resolves.toEqual({ - hooks: [{ key: "vault" }], - warnings: ["warn"], - errors: ['{"message":"err"}'], - }); - }); - - it("does not fall back to unrelated hook rows", async () => { - const client = { - listHooks: vi.fn().mockResolvedValue({ - data: [{ cwd: "/other", hooks: [{ key: "other" }], warnings: ["skip"], errors: [{ message: "skip" }] }], - }), - } as unknown as AppServerClient; - - await expect(listHookCatalog(client, "/vault")).resolves.toEqual({ - hooks: [], - warnings: [], - errors: [], - }); - }); });