Prune stale test coverage

This commit is contained in:
murashit 2026-05-23 09:15:13 +09:00
parent 8cacc174d5
commit 24f25cd3e6
3 changed files with 119 additions and 179 deletions

View file

@ -6,6 +6,7 @@ import type { AppServerTransport, AppServerTransportHandlers } from "../../src/a
import type { RpcOutboundMessage } from "../../src/app-server/types";
import type { InitializeResponse } from "../../src/generated/app-server/InitializeResponse";
import type { ServerRequest } from "../../src/generated/app-server/ServerRequest";
import manifest from "../../manifest.json";
class FakeTransport implements AppServerTransport {
readonly sent: RpcOutboundMessage[] = [];
@ -57,6 +58,25 @@ async function connectedClient(): Promise<{ client: AppServerClient; transport:
return { client, transport };
}
function latestSent(transport: FakeTransport): RpcOutboundMessage {
const message = transport.sent.at(-1);
if (!message) throw new Error("Expected an outbound app-server message.");
return message;
}
async function expectRequest(
transport: FakeTransport,
request: Promise<unknown>,
expected: Partial<RpcOutboundMessage>,
result: unknown,
): Promise<void> {
const sent = latestSent(transport);
expect(sent).toMatchObject(expected);
if (!("id" in sent) || typeof sent.id !== "number") throw new Error("Expected an app-server request id.");
transport.emitLine({ id: sent.id, result });
await request;
}
describe("AppServerClient", () => {
beforeEach(() => {
vi.stubGlobal("window", {
@ -91,6 +111,11 @@ describe("AppServerClient", () => {
id: 1,
method: "initialize",
params: {
clientInfo: {
name: "obsidian_codex_panel",
title: "Codex Panel",
version: manifest.version,
},
capabilities: {
experimentalApi: true,
requestAttestation: false,
@ -167,7 +192,13 @@ describe("AppServerClient", () => {
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "thread/start",
params: { cwd: "/vault", serviceName: "codex-panel", serviceTier: "fast" },
params: {
cwd: "/vault",
serviceName: "codex-panel",
serviceTier: "fast",
experimentalRawEvents: false,
persistExtendedHistory: false,
},
});
transport.emitLine({ id: 2, result: { thread: { id: "thread-1", title: null }, serviceTier: "fast" } });
await startingThread;
@ -377,61 +408,53 @@ describe("AppServerClient", () => {
await update;
});
it("requests effective config with raw layers for display fallbacks", async () => {
it("sends inventory and diagnostic requests with panel-specific query bounds", async () => {
const { client, transport } = await connectedClient();
const reading = client.readEffectiveConfig("/vault");
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "config/read",
params: { cwd: "/vault", includeLayers: true },
});
transport.emitLine({ id: 2, result: { config: {}, origins: {}, layers: [] } });
await reading;
});
it("sends model list request payloads", async () => {
const { client, transport } = await connectedClient();
const listing = client.listModels();
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "model/list",
params: { includeHidden: false, limit: 100 },
});
transport.emitLine({ id: 2, result: { data: [], nextCursor: null } });
await listing;
});
it("sends diagnostic capability request payloads", async () => {
const { client, transport } = await connectedClient();
const mcpStatus = client.listMcpServerStatus();
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "mcpServerStatus/list",
params: { detail: "toolsAndAuthOnly", limit: 100 },
});
transport.emitLine({ id: 2, result: { data: [], nextCursor: null } });
await mcpStatus;
const collaborationModes = client.listCollaborationModes();
expect(transport.sent[3]).toMatchObject({
id: 3,
method: "collaborationMode/list",
params: {},
});
transport.emitLine({ id: 3, result: { data: [] } });
await collaborationModes;
const capabilities = client.readModelProviderCapabilities();
expect(transport.sent[4]).toMatchObject({
id: 4,
method: "modelProvider/capabilities/read",
params: {},
});
transport.emitLine({ id: 4, result: { namespaceTools: true, imageGeneration: false, webSearch: true } });
await capabilities;
await expectRequest(
transport,
client.readEffectiveConfig("/vault"),
{ method: "config/read", params: { cwd: "/vault", includeLayers: true } },
{ config: {}, origins: {}, layers: [] },
);
await expectRequest(
transport,
client.listModels(),
{ method: "model/list", params: { includeHidden: false, limit: 100 } },
{ data: [], nextCursor: null },
);
await expectRequest(
transport,
client.listThreads("/vault", true),
{ method: "thread/list", params: { cwd: "/vault", limit: 20, archived: true, sortKey: "updated_at", sortDirection: "desc" } },
{ data: [], nextCursor: null },
);
await expectRequest(
transport,
client.listSkills("/vault"),
{ method: "skills/list", params: { cwds: ["/vault"], forceReload: false } },
{ data: [] },
);
expect((latestSent(transport) as { params?: Record<string, unknown> }).params?.["perCwdExtraUserRoots"]).toBeUndefined();
await expectRequest(
transport,
client.listSkills("/vault", true),
{ method: "skills/list", params: { cwds: ["/vault"], forceReload: true } },
{ data: [] },
);
await expectRequest(
transport,
client.listMcpServerStatus(),
{ method: "mcpServerStatus/list", params: { detail: "toolsAndAuthOnly", limit: 100 } },
{ data: [], nextCursor: null },
);
await expectRequest(transport, client.listCollaborationModes(), { method: "collaborationMode/list", params: {} }, { data: [] });
await expectRequest(
transport,
client.readModelProviderCapabilities(),
{ method: "modelProvider/capabilities/read", params: {} },
{ namespaceTools: true, imageGeneration: false, webSearch: true },
);
});
it("preserves app-server RPC error codes", async () => {
@ -448,19 +471,6 @@ describe("AppServerClient", () => {
} satisfies Partial<AppServerRpcError>);
});
it("lists threads for the active cwd and archive view", async () => {
const { client, transport } = await connectedClient();
const listing = client.listThreads("/vault", true);
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "thread/list",
params: { cwd: "/vault", limit: 20, archived: true, sortKey: "updated_at", sortDirection: "desc" },
});
transport.emitLine({ id: 2, result: { data: [], nextCursor: null } });
await listing;
});
it("resumes and forks threads without loading full turn history", async () => {
const { client, transport } = await connectedClient();
@ -483,29 +493,6 @@ describe("AppServerClient", () => {
await forking;
});
it("lists skills from the active cwd and only reloads on request", async () => {
const { client, transport } = await connectedClient();
const skills = client.listSkills("/vault");
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "skills/list",
params: { cwds: ["/vault"], forceReload: false },
});
expect((transport.sent[2] as { params?: Record<string, unknown> }).params?.["perCwdExtraUserRoots"]).toBeUndefined();
transport.emitLine({ id: 2, result: { data: [] } });
await skills;
const reloadedSkills = client.listSkills("/vault", true);
expect(transport.sent[3]).toMatchObject({
id: 3,
method: "skills/list",
params: { cwds: ["/vault"], forceReload: true },
});
transport.emitLine({ id: 3, result: { data: [] } });
await reloadedSkills;
});
it("loads turn history pages in the requested direction with full items", async () => {
const { client, transport } = await connectedClient();
@ -556,6 +543,8 @@ describe("AppServerClient", () => {
sandbox: "read-only",
approvalPolicy: "never",
environments: [],
experimentalRawEvents: false,
persistExtendedHistory: false,
},
});
transport.emitLine({ id: 2, result: { thread: { id: "naming-thread" } } });
@ -600,13 +589,11 @@ describe("AppServerClient", () => {
it("saves the user-confirmed thread title through app-server metadata", async () => {
const { client, transport } = await connectedClient();
const setName = client.setThreadName("thread-1", "Codex Panel自動命名");
expect(transport.sent[2]).toMatchObject({
id: 2,
method: "thread/name/set",
params: { threadId: "thread-1", name: "Codex Panel自動命名" },
});
transport.emitLine({ id: 2, result: {} });
await setName;
await expectRequest(
transport,
client.setThreadName("thread-1", "Codex Panel自動命名"),
{ method: "thread/name/set", params: { threadId: "thread-1", name: "Codex Panel自動命名" } },
{},
);
});
});

View file

@ -30,34 +30,6 @@ function expectPresent<T>(value: T | null | undefined): T {
describe("PanelController", () => {
describe("active turn routing", () => {
it("ignores item notifications for a different active thread", () => {
const state = createPanelState();
state.activeThreadId = "thread-active";
state.activeTurnId = "turn-active";
const controller = controllerForState(state);
controller.handleNotification({
method: "item/agentMessage/delta",
params: { threadId: "thread-other", turnId: "turn-active", itemId: "a1", delta: "wrong" },
} satisfies Extract<ServerNotification, { method: "item/agentMessage/delta" }>);
expect(state.displayItems).toEqual([]);
});
it("ignores item notifications for a different active turn", () => {
const state = createPanelState();
state.activeThreadId = "thread-active";
state.activeTurnId = "turn-active";
const controller = controllerForState(state);
controller.handleNotification({
method: "item/agentMessage/delta",
params: { threadId: "thread-active", turnId: "turn-other", itemId: "a1", delta: "wrong" },
} satisfies Extract<ServerNotification, { method: "item/agentMessage/delta" }>);
expect(state.displayItems).toEqual([]);
});
it("applies matching streaming deltas as assistant markdown", () => {
const state = createPanelState();
state.activeThreadId = "thread-active";
@ -1110,55 +1082,46 @@ describe("PanelController", () => {
expect(state.activeServiceTier).toBeNull();
});
it("shows unsupported goal update notifications as system messages", () => {
const state = createPanelState();
state.activeThreadId = "thread-active";
const controller = controllerForState(state);
controller.handleNotification({
method: "thread/goal/updated",
params: {
threadId: "thread-active",
turnId: null,
goal: {
threadId: "thread-active",
objective: "Finish",
status: "active",
tokenBudget: null,
tokensUsed: 0,
timeUsedSeconds: 0,
createdAt: 1,
updatedAt: 1,
},
},
} satisfies Extract<ServerNotification, { method: "thread/goal/updated" }>);
expect(state.displayItems).toMatchObject([
it("shows unsupported goal notifications as brief system messages", () => {
const cases: { notification: ServerNotification; text: string }[] = [
{
kind: "system",
notification: {
method: "thread/goal/updated",
params: {
threadId: "thread-active",
turnId: null,
goal: {
threadId: "thread-active",
objective: "Finish",
status: "active",
tokenBudget: null,
tokensUsed: 0,
timeUsedSeconds: 0,
createdAt: 1,
updatedAt: 1,
},
},
} satisfies Extract<ServerNotification, { method: "thread/goal/updated" }>,
text: "Thread goal updated: status active. Codex Panel does not support goals.",
},
]);
});
it("shows unsupported goal clear notifications as system messages", () => {
const state = createPanelState();
state.activeThreadId = "thread-active";
const controller = controllerForState(state);
controller.handleNotification({
method: "thread/goal/cleared",
params: {
threadId: "thread-active",
},
} satisfies Extract<ServerNotification, { method: "thread/goal/cleared" }>);
expect(state.displayItems).toMatchObject([
{
kind: "system",
notification: {
method: "thread/goal/cleared",
params: { threadId: "thread-active" },
} satisfies Extract<ServerNotification, { method: "thread/goal/cleared" }>,
text: "Thread goal cleared. Codex Panel does not support goals.",
},
]);
];
for (const { notification, text } of cases) {
const state = createPanelState();
state.activeThreadId = "thread-active";
const controller = controllerForState(state);
controller.handleNotification(notification);
expect(state.displayItems).toMatchObject([{ kind: "system", text }]);
}
});
});

View file

@ -1,10 +0,0 @@
import { describe, expect, it } from "vitest";
import manifest from "../../manifest.json";
import { CLIENT_VERSION } from "../../src/constants";
describe("constants", () => {
it("uses the manifest version for the panel client version", () => {
expect(CLIENT_VERSION).toBe(manifest.version);
});
});