Prune low-value regression tests

This commit is contained in:
murashit 2026-06-27 12:47:51 +09:00
parent b52fc163dd
commit 0bca5b3477
8 changed files with 23 additions and 488 deletions

View file

@ -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 {

View file

@ -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<string[]> {
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/");
}

View file

@ -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<T>(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",
},
};
}

View file

@ -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 <thread>")).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 <objective>|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 <thread> <name>")).toBe("Rename the selected Codex thread.");
});
it("documents refer history size", () => {
expect(slashCommandHelpValue("/refer <thread> <message>")).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.");

View file

@ -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")]);

View file

@ -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<typeof createToolbarUiActions>[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<typeof createToolbarUiActions>[0]["goals"],
toolbarPanel: {
toggleChatActions: vi.fn(),
toggleHistory: vi.fn(),
toggleStatus: vi.fn(),
startArchive: vi.fn(),
archiveThread: vi.fn().mockResolvedValue(undefined),
} as unknown as Parameters<typeof createToolbarUiActions>[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<typeof createToolbarUiActions>[0]["rename"],
navigation: {
startNewThread: vi.fn().mockResolvedValue(undefined),
selectThreadFromToolbar: vi.fn().mockResolvedValue(undefined),
} as unknown as Parameters<typeof createToolbarUiActions>[0]["navigation"],
};
}

View file

@ -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<ThreadsRenameState, { kind: "generating" }> {
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> = {}): Thread {
return {
id: "thread",

View file

@ -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: [],
});
});
});