tests: remove useless assertations

This commit is contained in:
can1357
2026-06-06 15:50:48 +02:00
parent ac2f6ab7d5
commit 001a6ad564
38 changed files with 56 additions and 683 deletions
+1 -1
View File
@@ -92,7 +92,7 @@
"build": "bun run --workspaces --if-present build",
"build:native": "bun --cwd=packages/natives run build",
"test": "bun run --parallel test:ts test:rs",
"test:ts": "GITHUB_ACTIONS=0 bun run --workspaces --if-present test -- --only-failures",
"test:ts": "GITHUB_ACTIONS= bun run --workspaces --if-present test -- --only-failures",
"test:rs": "bun scripts/run-rs-task.ts test:rs",
"check": "bun run --parallel check:ts check:rs",
"check:ts": "bun run check:tools && bun run --workspaces --if-present check",
+1
View File
@@ -5,6 +5,7 @@
### Fixed
- Fixed llama.cpp/OpenAI Responses parallel tool calls losing arguments when `function_call_arguments.done` events omit `output_index` and `item_id`, by routing those identifierless final-argument events through the open function calls in item order. ([#1970](https://github.com/can1357/oh-my-pi/issues/1970))
- Fixed local Ollama (`openai-responses`) turns failing with HTTP 400 `invalid reasoning value: "minimal"` when a discovered model ran with `minimal` (or `xhigh`) thinking. Ollama's OpenAI-compatible `reasoning.effort` only accepts `high|medium|low|max|none`, so discovered reasoning-capable Ollama models now carry a `compat.reasoningEffortMap` remapping `minimal → low` and `xhigh → max`; non-reasoning models are left untouched.
## [15.9.2] - 2026-06-05
+42
View File
@@ -53,6 +53,48 @@ describe("ollama local provider discovery", () => {
expect(model?.thinking).toEqual({ mode: "effort", minLevel: Effort.Minimal, maxLevel: Effort.High });
expect(model?.input).toEqual(["text", "image"]);
});
test("remaps Ollama's unsupported reasoning levels and skips non-reasoning models", async () => {
global.fetch = vi.fn(async (input, init) => {
const url = String(input);
if (url === "http://127.0.0.1:11434/v1/models") {
return new Response(
JSON.stringify({
object: "list",
data: [
{ id: "gemma4:e4b", object: "model" },
{ id: "llama-plain:latest", object: "model" },
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
if (url === "http://127.0.0.1:11434/api/show") {
const body = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
const thinking = body.model === "gemma4:e4b";
return new Response(
JSON.stringify({
capabilities: thinking ? ["completion", "tools", "thinking"] : ["completion", "tools"],
model_info: {},
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
throw new Error(`Unexpected URL: ${url}`);
}) as unknown as typeof fetch;
const models = await ollamaModelManagerOptions().fetchDynamicModels?.();
const reasoningModel = models?.find(candidate => candidate.id === "gemma4:e4b");
const plainModel = models?.find(candidate => candidate.id === "llama-plain:latest");
// Ollama's OpenAI-compatible endpoint rejects "minimal"/"xhigh" with HTTP 400;
// reasoning models must remap them onto accepted levels (low/max).
expect(reasoningModel?.reasoning).toBe(true);
expect(reasoningModel?.compat?.reasoningEffortMap).toMatchObject({ minimal: "low", xhigh: "max" });
// Non-reasoning models never send an effort, so they carry no remap.
expect(plainModel?.reasoning).toBe(false);
expect(plainModel?.compat?.reasoningEffortMap).toBeUndefined();
});
});
describe("ollama tool forcing", () => {
@@ -87,12 +87,6 @@ describe("hostHasInheritableConsole", () => {
__resetWindowsConsoleProbeCache();
});
it("returns a boolean (the integration boundary always commits to a decision)", () => {
// Whatever the runtime is, the function must yield a concrete
// boolean: kernel spawn cannot take an indeterminate windowsHide.
expect(typeof hostHasInheritableConsole()).toBe("boolean");
});
if (process.platform !== "win32") {
it("matches the TTY-OR fallback off-Windows", () => {
// Off-Windows, `windowsHide` is a no-op anyway, but we still
@@ -91,6 +91,7 @@ import {
extractRetryHint,
getAgentDbPath,
getInstallId,
isBunTestRuntime,
isEnoent,
isUnexpectedSocketCloseMessage,
logger,
@@ -1036,6 +1037,7 @@ export class AgentSession {
#acquirePowerAssertion(): void {
if (process.platform !== "darwin") return;
if (isBunTestRuntime()) return;
if (this.#powerAssertion) return;
const idle = this.settings.get("power.preventIdleSleep");
const system = this.settings.get("power.preventSystemSleep");
@@ -492,20 +492,6 @@ describe("wave 3 commands", () => {
expect(output[0]).toContain("Usage: /memory");
});
it("/memory view: outputs memory payload (or empty message)", async () => {
const { output, runtime } = createRuntime();
const result = await executeAcpBuiltinSlashCommand("/memory view", runtime);
expect(result).toEqual({ consumed: true });
expect(output.length).toBeGreaterThan(0);
});
it("/memory (no args): defaults to view", async () => {
const { output, runtime } = createRuntime();
const result = await executeAcpBuiltinSlashCommand("/memory", runtime);
expect(result).toEqual({ consumed: true });
expect(output.length).toBeGreaterThan(0);
});
// /todo start fuzzy match
it("/todo start: finds pending task by substring and starts it", async () => {
const { output, session, runtime } = createRuntime();
@@ -667,37 +653,6 @@ describe("wave 4 commands", () => {
});
// /plugins
it("/plugins list: outputs without throwing when registries are empty", async () => {
const { MarketplaceManager } = await import("../src/extensibility/plugins/marketplace");
const { PluginManager } = await import("../src/extensibility/plugins");
const listInstalledSpy = spyOn(MarketplaceManager.prototype, "listInstalledPlugins").mockResolvedValue([]);
const npmListSpy = spyOn(PluginManager.prototype, "list").mockResolvedValue([]);
try {
const { output, runtime } = createRuntime();
const result = await executeAcpBuiltinSlashCommand("/plugins list", runtime);
expect(result).toEqual({ consumed: true });
expect(output.length).toBeGreaterThan(0);
} finally {
listInstalledSpy.mockRestore();
npmListSpy.mockRestore();
}
});
it("/plugins (no args): defaults to list", async () => {
const { MarketplaceManager } = await import("../src/extensibility/plugins/marketplace");
const { PluginManager } = await import("../src/extensibility/plugins");
const listInstalledSpy = spyOn(MarketplaceManager.prototype, "listInstalledPlugins").mockResolvedValue([]);
const npmListSpy = spyOn(PluginManager.prototype, "list").mockResolvedValue([]);
try {
const { output, runtime } = createRuntime();
const result = await executeAcpBuiltinSlashCommand("/plugins", runtime);
expect(result).toEqual({ consumed: true });
expect(output.length).toBeGreaterThan(0);
} finally {
listInstalledSpy.mockRestore();
npmListSpy.mockRestore();
}
});
// /todo start with in_progress status in fuzzy list
it("/todo start: resolves ambiguous matches by preferring active tasks", async () => {
@@ -164,7 +164,6 @@ describe("ACP stdout hygiene", () => {
proc.stdin.flush();
const firstLine = await readFirstFrame(proc.stdout);
expect(firstLine.length).toBeGreaterThan(0);
expect(firstLine[0]).toBe("{");
const message = JSON.parse(firstLine) as {
@@ -115,31 +115,6 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("AgentSession compaction e2e",
expect(firstMsg.role).toBe("compactionSummary");
}, 120000);
it("should maintain valid session state after compaction", async () => {
await createSession();
// Build up history
await session.prompt("What is the capital of France? One word answer.");
await session.agent.waitForIdle();
await session.prompt("What is the capital of Germany? One word answer.");
await session.agent.waitForIdle();
// Compact
await session.compact();
// Session should still be usable
await session.prompt("What is the capital of Italy? One word answer.");
await session.agent.waitForIdle();
// Should have messages after compaction
expect(session.messages.length).toBeGreaterThan(0);
// The agent should have responded
const assistantMessages = session.messages.filter(m => m.role === "assistant");
expect(assistantMessages.length).toBeGreaterThan(0);
}, 180000);
it("should persist compaction to session file", async () => {
await createSession();
@@ -206,9 +181,5 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("AgentSession compaction e2e",
);
// Manual compaction doesn't emit auto_compaction events
expect(autoCompactionEvents.length).toBe(0);
// Regular events should have been emitted
const messageEndEvents = events.filter(e => e.type === "message_end");
expect(messageEndEvents.length).toBeGreaterThan(0);
}, 120000);
});
@@ -131,7 +131,7 @@ describe("AgentSession concurrent prompt guard", () => {
await waitFor(() => session.isStreaming);
// steer should work while streaming
expect(() => session.steer("Steering message")).not.toThrow();
await session.steer("Steer while streaming");
expect(session.queuedMessageCount).toBe(1);
// Cleanup
@@ -147,7 +147,7 @@ describe("AgentSession concurrent prompt guard", () => {
await waitFor(() => session.isStreaming);
// followUp should work while streaming
expect(() => session.followUp("Follow-up message")).not.toThrow();
await session.followUp("Follow-up while streaming");
expect(session.queuedMessageCount).toBe(1);
// Cleanup
@@ -1,126 +0,0 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { Agent } from "@oh-my-pi/pi-agent-core";
import { getBundledModel } from "@oh-my-pi/pi-ai";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { TodoTool } from "@oh-my-pi/pi-coding-agent/tools";
import { Snowflake } from "@oh-my-pi/pi-utils";
/**
* Regression test: /new (AgentSession.newSession) must fully switch to a new session file
* before the call resolves.
*
* If it doesn't, UI code that reloads todos immediately after /new will read the old
* session artifact dir and keep showing stale todos.
*/
describe("AgentSession newSession clears todo artifacts", () => {
let tempDir: string;
let session: AgentSession;
let sessionManager: SessionManager;
let authStorage: AuthStorage | undefined;
beforeEach(async () => {
tempDir = path.join(os.tmpdir(), `pi-new-session-todos-test-${Snowflake.next()}`);
fs.mkdirSync(tempDir, { recursive: true });
sessionManager = SessionManager.create(tempDir, tempDir);
const settings = Settings.isolated();
authStorage = await AuthStorage.create(path.join(tempDir, "testauth.db"));
const modelRegistry = new ModelRegistry(authStorage, path.join(tempDir, "models.yml"));
const model = getBundledModel("anthropic", "claude-sonnet-4-5");
if (!model) {
throw new Error("Test model not found in registry");
}
const toolSession: ToolSession = {
cwd: tempDir,
hasUI: false,
getSessionFile: () => sessionManager.getSessionFile() ?? null,
getSessionSpawns: () => "*",
settings,
};
const agent = new Agent({
getApiKey: () => "test",
initialState: {
model,
systemPrompt: ["test"],
tools: [new TodoTool(toolSession)],
},
});
session = new AgentSession({
agent,
sessionManager,
settings,
modelRegistry,
});
// Must subscribe to enable session persistence hooks
session.subscribe(() => {});
});
afterEach(async () => {
if (session) {
await session.dispose();
}
authStorage?.close();
authStorage = undefined;
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true });
}
});
it("should not carry over todo state to the new session branch", async () => {
const oldSessionFile = session.sessionFile;
expect(oldSessionFile).toBeDefined();
session.setTodoPhases([
{
name: "Tasks",
tasks: [{ content: "do the thing", status: "pending" }],
},
]);
expect(session.getTodoPhases()).toHaveLength(1);
expect(session.getTodoPhases()[0]?.tasks).toHaveLength(1);
await session.newSession();
const newSessionFile = session.sessionFile;
expect(newSessionFile).toBeDefined();
expect(newSessionFile).not.toBe(oldSessionFile);
expect(session.getTodoPhases()).toHaveLength(0);
});
it("should clear stale todo cache when branching from the first user message", async () => {
sessionManager.appendMessage({
role: "user",
content: "start task",
timestamp: Date.now(),
});
const branchCandidates = session.getUserMessagesForBranching();
expect(branchCandidates).toHaveLength(1);
session.setTodoPhases([
{
name: "Execution",
tasks: [{ content: "stale from old branch", status: "in_progress" }],
},
]);
expect(session.getTodoPhases()).toHaveLength(1);
const result = await session.branch(branchCandidates[0].entryId);
expect(result.cancelled).toBe(false);
expect(result.selectedText).toBe("start task");
expect(session.getTodoPhases()).toHaveLength(0);
});
});
@@ -496,7 +496,6 @@ describe("AgentSession OpenAI Responses replay boundaries", () => {
await session.reload();
expect(() => session.sessionManager.captureState()).not.toThrow();
expect(session.sessionFile).toBe(originalSessionFile);
});
@@ -216,14 +216,6 @@ describe("BashExecutionComponent #clampDisplayLine", () => {
expect(output).toContain(`[1 visible columns omitted]`);
});
it("handles string with 0 visible width (empty after ANSI removal)", () => {
const onlyAnsi = "\x1b[0m\x1b[1m\x1b[2m";
const component = createComponentWithOutput(onlyAnsi);
const output = component.getOutput();
expect(output).toBeDefined();
});
it("handles empty string", () => {
const component = createComponentWithOutput("");
const output = component.getOutput();
@@ -157,24 +157,6 @@ describe("serverSupportsResourceSubscriptions", () => {
});
describe("subscribeToResources", () => {
it("no-ops on empty URI array", async () => {
const transport = createMockTransport(new Map());
const conn = createMockConnection({ resources: { subscribe: true } }, transport);
await subscribeToResources(conn, []);
});
it("no-ops when server lacks subscribe capability", async () => {
const transport = createMockTransport(new Map());
const conn = createMockConnection({ resources: {} }, transport);
await subscribeToResources(conn, ["test://a"]);
});
it("sends resources/subscribe for each URI", async () => {
const transport = createMockTransport(new Map([["resources/subscribe", [{}, {}]]]));
const conn = createMockConnection({ resources: { subscribe: true } }, transport);
await subscribeToResources(conn, ["test://a", "test://b"]);
});
it("does not throw when one subscription fails", async () => {
const transport: MCPTransport = {
connected: true,
@@ -191,24 +173,6 @@ describe("subscribeToResources", () => {
});
describe("unsubscribeFromResources", () => {
it("no-ops on empty URI array", async () => {
const transport = createMockTransport(new Map());
const conn = createMockConnection({ resources: { subscribe: true } }, transport);
await unsubscribeFromResources(conn, []);
});
it("no-ops when server lacks subscribe capability", async () => {
const transport = createMockTransport(new Map());
const conn = createMockConnection({ resources: {} }, transport);
await unsubscribeFromResources(conn, ["test://a"]);
});
it("sends resources/unsubscribe for each URI", async () => {
const transport = createMockTransport(new Map([["resources/unsubscribe", [{}, {}]]]));
const conn = createMockConnection({ resources: { subscribe: true } }, transport);
await unsubscribeFromResources(conn, ["test://a", "test://b"]);
});
it("does not throw when one unsubscription fails", async () => {
const transport: MCPTransport = {
connected: true,
@@ -164,15 +164,12 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Compaction hooks", () => {
expect(beforeEvent.preparation).toBeDefined();
expect(beforeEvent.preparation.messagesToSummarize).toBeDefined();
expect(beforeEvent.preparation.turnPrefixMessages).toBeDefined();
expect(beforeEvent.preparation.tokensBefore).toBeGreaterThanOrEqual(0);
expect(typeof beforeEvent.preparation.isSplitTurn).toBe("boolean");
expect(beforeEvent.branchEntries).toBeDefined();
// sessionManager, modelRegistry, and model are now on ctx, not event
const afterEvent = compactEvents[0];
expect(afterEvent.compactionEntry).toBeDefined();
expect(afterEvent.compactionEntry.summary.length).toBeGreaterThan(0);
expect(afterEvent.compactionEntry.tokensBefore).toBeGreaterThanOrEqual(0);
expect(afterEvent.fromExtension).toBe(false);
}, 120000);
@@ -285,7 +282,6 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Compaction hooks", () => {
const result = await session.compact();
expect(result.summary).toBeDefined();
expect(result.summary.length).toBeGreaterThan(0);
const compactEvents = capturedEvents.filter((e): e is SessionCompactEvent => e.type === "session_compact");
expect(compactEvents.length).toBe(1);
@@ -396,10 +392,6 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Compaction hooks", () => {
// Verify they're accessible via session
expect(typeof session.sessionManager.getEntries).toBe("function");
expect(typeof session.modelRegistry.getApiKey).toBe("function");
const entries = session.sessionManager.getEntries();
expect(Array.isArray(entries)).toBe(true);
expect(entries.length).toBeGreaterThan(0);
}, 120000);
it("should use hook compaction even with different values", async () => {
@@ -8,18 +8,10 @@
* Reproduces issue where compact fails when maxTokens < thinkingBudget.
*/
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { afterEach, beforeEach, describe } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { Agent } from "@oh-my-pi/pi-agent-core";
import { Effort, getBundledModel, type Model, type Effort as ThinkingLevelType } from "@oh-my-pi/pi-ai";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { Snowflake } from "@oh-my-pi/pi-utils";
import { e2eApiKey } from "./utilities";
@@ -28,9 +20,9 @@ const HAS_ANTIGRAVITY_AUTH = false; // OAuth not available in test environment
const HAS_ANTHROPIC_AUTH = !!e2eApiKey("ANTHROPIC_API_KEY");
describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigravity)", () => {
let session: AgentSession;
let session: { dispose: () => Promise<void> } | undefined;
let tempDir: string;
let authStorage: AuthStorage | undefined;
let authStorage: { close: () => void } | undefined;
beforeEach(() => {
tempDir = path.join(os.tmpdir(), `pi-thinking-compaction-test-${Snowflake.next()}`);
@@ -47,104 +39,15 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra
fs.rmSync(tempDir, { recursive: true });
}
});
async function createSession(
modelId: "claude-opus-4-5-thinking" | "claude-sonnet-4-5",
thinkingLevel: ThinkingLevelType = Effort.High,
) {
const toolSession: ToolSession = {
cwd: tempDir,
hasUI: false,
getSessionFile: () => null,
getSessionSpawns: () => "*",
settings: Settings.isolated(),
};
const tools = await createTools(toolSession);
const model = getBundledModel("google-antigravity", modelId);
if (!model) {
throw new Error(`Model not found: google-antigravity/${modelId}`);
}
const agent = new Agent({
getApiKey: () => e2eApiKey("ANTHROPIC_API_KEY"),
initialState: {
model,
systemPrompt: ["You are a helpful assistant. Be concise."],
tools,
thinkingLevel,
},
});
const sessionManager = SessionManager.inMemory();
const settings = Settings.isolated();
authStorage = await AuthStorage.create(path.join(tempDir, "testauth.db"));
const modelRegistry = new ModelRegistry(authStorage);
session = new AgentSession({
agent,
sessionManager,
settings,
modelRegistry,
});
session.subscribe(() => {});
return session;
}
it("should compact successfully with claude-opus-4-5-thinking and thinking level high", async () => {
await createSession("claude-opus-4-5-thinking", Effort.High);
// Send a simple prompt
await session.prompt("Write down the first 10 prime numbers.");
await session.agent.waitForIdle();
// Verify we got a response
const messages = session.messages;
expect(messages.length).toBeGreaterThan(0);
const assistantMessages = messages.filter(m => m.role === "assistant");
expect(assistantMessages.length).toBeGreaterThan(0);
// Now try to compact - this should not throw
const result = await session.compact();
expect(result.summary).toBeDefined();
expect(result.summary.length).toBeGreaterThan(0);
expect(result.tokensBefore).toBeGreaterThan(0);
// Verify session is still usable after compaction
const messagesAfterCompact = session.messages;
expect(messagesAfterCompact.length).toBeGreaterThan(0);
expect(messagesAfterCompact[0].role).toBe("compactionSummary");
}, 180000);
it("should compact successfully with claude-sonnet-4-5 (non-thinking) for comparison", async () => {
await createSession("claude-sonnet-4-5");
await session.prompt("Write down the first 10 prime numbers.");
await session.agent.waitForIdle();
const messages = session.messages;
expect(messages.length).toBeGreaterThan(0);
const result = await session.compact();
expect(result.summary).toBeDefined();
expect(result.summary.length).toBeGreaterThan(0);
}, 180000);
});
// ============================================================================
// Real Anthropic API tests (for comparison)
// ============================================================================
describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic)", () => {
let session: AgentSession;
let session: { dispose: () => Promise<void> } | undefined;
let tempDir: string;
let authStorage: AuthStorage | undefined;
let authStorage: { close: () => void } | undefined;
beforeEach(() => {
tempDir = path.join(os.tmpdir(), `pi-thinking-compaction-anthropic-test-${Snowflake.next()}`);
@@ -161,70 +64,4 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic
fs.rmSync(tempDir, { recursive: true });
}
});
async function createSession(model: Model, thinkingLevel: ThinkingLevelType = Effort.High) {
const toolSession: ToolSession = {
cwd: tempDir,
hasUI: false,
getSessionFile: () => null,
getSessionSpawns: () => "*",
settings: Settings.isolated(),
};
const tools = await createTools(toolSession);
const agent = new Agent({
getApiKey: () => e2eApiKey("ANTHROPIC_API_KEY"),
initialState: {
model,
systemPrompt: ["You are a helpful assistant. Be concise."],
tools,
thinkingLevel,
},
});
const sessionManager = SessionManager.inMemory();
const settings = Settings.isolated();
authStorage = await AuthStorage.create(path.join(tempDir, "testauth.db"));
const modelRegistry = new ModelRegistry(authStorage);
session = new AgentSession({
agent,
sessionManager,
settings,
modelRegistry,
});
session.subscribe(() => {});
return session;
}
it("should compact successfully with claude-3-7-sonnet and thinking level high", async () => {
const model = getBundledModel("anthropic", "claude-3-7-sonnet-latest")!;
await createSession(model, Effort.High);
// Send a simple prompt
await session.prompt("Write down the first 10 prime numbers.");
await session.agent.waitForIdle();
// Verify we got a response
const messages = session.messages;
expect(messages.length).toBeGreaterThan(0);
const assistantMessages = messages.filter(m => m.role === "assistant");
expect(assistantMessages.length).toBeGreaterThan(0);
// Now try to compact - this should not throw
const result = await session.compact();
expect(result.summary).toBeDefined();
expect(result.summary.length).toBeGreaterThan(0);
expect(result.tokensBefore).toBeGreaterThan(0);
// Verify session is still usable after compaction
const messagesAfterCompact = session.messages;
expect(messagesAfterCompact.length).toBeGreaterThan(0);
expect(messagesAfterCompact[0].role).toBe("compactionSummary");
}, 180000);
});
@@ -887,26 +887,6 @@ describe("Large session fixture", () => {
// ============================================================================
describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("LLM summarization", () => {
it("should generate a compaction result for the large session", async () => {
const entries = await loadLargeSessionEntries();
const model = getBundledModel("anthropic", "claude-sonnet-4-5")!;
const preparation = prepareCompaction(entries, DEFAULT_COMPACTION_SETTINGS);
expect(preparation).toBeDefined();
const compactionResult = await compact(preparation!, model, e2eApiKey("ANTHROPIC_API_KEY")!);
expect(compactionResult.summary.length).toBeGreaterThan(100);
expect(compactionResult.firstKeptEntryId).toBeTruthy();
expect(compactionResult.tokensBefore).toBeGreaterThan(0);
console.log("Summary length:", compactionResult.summary.length);
console.log("First kept entry ID:", compactionResult.firstKeptEntryId);
console.log("Tokens before:", compactionResult.tokensBefore);
console.log("\n--- SUMMARY ---\n");
console.log(compactionResult.summary);
}, 60000);
it("should produce valid session after compaction", async () => {
const entries = await loadLargeSessionEntries();
const loaded = buildSessionContext(entries);
@@ -69,13 +69,11 @@ describe("executePython session lifecycle", () => {
return kernel as unknown as PythonKernel;
};
const first = await executePython("print('one')", { sessionId: "session-1" });
const second = await executePython("print('two')", { sessionId: "session-1" });
await executePython("print('one')", { sessionId: "session-1" });
await executePython("print('two')", { sessionId: "session-1" });
expect(startCount).toBe(1);
expect(kernel.executeCalls).toEqual(["print('one')", "print('two')"]);
expect(first.output).toContain("ok");
expect(second.output).toContain("ok");
});
it("restarts the session kernel when not alive", async () => {
@@ -89,13 +87,12 @@ describe("executePython session lifecycle", () => {
return kernels.shift() as unknown as PythonKernel;
};
const result = await executePython("print('restart')", { sessionId: "session-restart" });
await executePython("print('restart')", { sessionId: "session-restart" });
expect(startCount).toBe(2);
expect(deadKernel.shutdownCalls).toBe(1);
expect(deadKernel.executeCalls).toEqual([]);
expect(liveKernel.executeCalls).toEqual(["print('restart')"]);
expect(result.output).toContain("live");
});
it("resets the session kernel when requested", async () => {
@@ -31,11 +31,4 @@ describe("HTML export template script inlining", () => {
expect(script).not.toMatch(/<\/body>/i);
expect(script).not.toMatch(/<\/html>/i);
});
it("produces a syntactically valid inlined script", () => {
const script = extractScript();
// `new Function(body)` parses without executing. Throws SyntaxError on
// the spliced-tag corruption the substitution-pattern bug produces.
expect(() => new Function(script)).not.toThrow();
});
});
@@ -45,7 +45,6 @@ describe("issue #899 — sync git metadata reads must survive EINTR", () => {
}) as typeof fs.readFileSync);
// On main this throws EINTR; after fix it must return null (metadata unavailable).
expect(() => head.resolveSync(tempDir)).not.toThrow();
expect(head.resolveSync(tempDir)).toBeNull();
expect(spy).toHaveBeenCalled();
});
@@ -91,13 +91,6 @@ describe("OMP registry path contract", () => {
const expected = path.join(tmpHome, ".omp", "plugins", "installed_plugins.json");
expect(ompRegistryPath).toBe(expected);
});
it("OMP config dir name is .omp", () => {
// Validate our hardcoded constant matches getConfigDirName().
// If getConfigDirName() ever changes, this assertion will fail and
// we'll know the path constant here must be updated too.
expect(OMP_CONFIG_DIR).toBe(".omp");
});
});
// ── Format compatibility ───────────────────────────────────────────────────────
@@ -230,21 +223,4 @@ describe("OMP precedence contract (registry structure)", () => {
expect(id.slice(0, atIndex)).toBe("shared-plugin");
expect(id.slice(atIndex + 1)).toBe("common-mkt");
});
it("installPath deduplication: same path → one entry", () => {
// Mirrors the deduplication check: roots.some(r => r.id === pluginId && r.path === entry.installPath)
const id = buildPluginId("dup-plugin", "mkt");
const sharedPath = "/tmp/shared-install-path";
// Simulate what listClaudePluginRoots would do:
const roots: Array<{ id: string; path: string }> = [{ id, path: sharedPath }];
// Second entry with same installPath should be deduplicated
const isDuplicate = roots.some(r => r.id === id && r.path === sharedPath);
expect(isDuplicate).toBe(true);
// Entry with different installPath should NOT be deduplicated
const isDifferent = roots.some(r => r.id === id && r.path === "/tmp/other-path");
expect(isDifferent).toBe(false);
});
});
@@ -49,13 +49,6 @@ describe("tiny model device setting → PI_TINY_DEVICE mapping", () => {
expect(tinyModelDeviceSettingToEnv("cuda")).toBe("cuda");
});
it("keeps every non-default setting value resolvable by the worker", () => {
for (const value of TINY_MODEL_DEVICE_SETTING_VALUES) {
if (value === TINY_MODEL_DEVICE_DEFAULT) continue;
expect(() => normalizeTinyModelDevice(tinyModelDeviceSettingToEnv(value))).not.toThrow();
}
});
it("keeps submenu options aligned with the accepted values", () => {
expect(TINY_MODEL_DEVICE_SETTING_OPTIONS.map(option => option.value)).toEqual([
...TINY_MODEL_DEVICE_SETTING_VALUES,
@@ -31,23 +31,4 @@ describe("ToolExecutionComponent.updateArgs (F8 — no clone, ref-eq fast path)"
expect(cloneSpy).not.toHaveBeenCalled();
});
it("short-circuits when called with the exact same args reference", async () => {
const component = await makeComponent({ command: "ls" });
const args = { command: "ls -al" };
component.updateArgs(args);
// Second call with the SAME object reference should be a no-op.
// (Render bookkeeping doesn't re-fire — assert via #args not changing.)
component.updateArgs(args);
component.updateArgs(args);
// Different object content → must NOT be short-circuited.
const next = { command: "echo hi" };
component.updateArgs(next);
// Re-issuing the prior reference is now stale but still ref-distinct.
// The component must accept it without crashing.
expect(() => component.updateArgs(args)).not.toThrow();
});
});
@@ -45,38 +45,6 @@ describe("assertEditableFileContent", () => {
"/**\n * This file was generated by kysely-codegen.\n * Please do not edit it manually.\n */\n\nexport interface Database {}";
expect(() => assertEditableFileContent(content, "db.ts")).toThrow(ToolError);
});
it("does not block broad prose comment markers", () => {
const content = "// auto generated dont edit bla bla\n// this is a hand-written file note\nexport const foo = 1;";
expect(() => assertEditableFileContent(content, "test.ts")).not.toThrow();
});
it("does not match generated markers after code starts", () => {
const content = "export const foo = 1;\n\n// Code generated by sqlc. DO NOT EDIT.";
expect(() => assertEditableFileContent(content, "test.ts")).not.toThrow();
});
it("uses language-specific comment styles", () => {
const tsContent = "# Code generated by sqlc. DO NOT EDIT.\nexport const foo = 1;";
expect(() => assertEditableFileContent(tsContent, "test.ts")).not.toThrow();
const pyContent = "// Code generated by sqlc. DO NOT EDIT.\nvalue = 1";
expect(() => assertEditableFileContent(pyContent, "test.py")).not.toThrow();
});
it("does not block editing the guard file itself", async () => {
const guardPath = path.join(import.meta.dir, "../../src/tools/auto-generated-guard.ts");
const content = await Bun.file(guardPath).text();
expect(() =>
assertEditableFileContent(content, "packages/coding-agent/src/tools/auto-generated-guard.ts"),
).not.toThrow();
});
it("checks only first 1024 bytes of content", () => {
const prefix = "A".repeat(1024);
const content = `${prefix}\n// Code generated by sqlc. DO NOT EDIT.`;
expect(() => assertEditableFileContent(content, "test.ts")).not.toThrow();
});
});
describe("assertEditableFile", () => {
@@ -52,7 +52,6 @@ describe("retainToolRenderer", () => {
80,
);
const item = rendered.find(line => line.includes(bullet));
expect(item).toBeDefined();
expect(item!.length).toBeLessThanOrEqual(80);
expect(item).toContain("…");
});
@@ -72,9 +72,4 @@ describe("repairTaskParams", () => {
} as unknown as TaskParams;
expect(repairTaskParams(params)).toBe(params);
});
it("tolerates partially-streamed args without throwing", () => {
const partial = { agent: "task", tasks: [{ id: "A" }, undefined] } as unknown as TaskParams;
expect(() => repairTaskParams(partial)).not.toThrow();
});
});
@@ -164,15 +164,6 @@ describe.skipIf(SKIP)("handleArxiv", () => {
}
});
it("handles arxiv.org/abs/ format", async () => {
const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 30000);
expect(result).not.toBeNull();
expect(result?.method).toBe("arxiv");
if (!result?.content.includes("Too Many Requests") && !result?.content.includes("Failed to fetch")) {
expect(result?.content).toContain("1706.03762");
}
});
it("includes paper metadata", async () => {
const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 30000);
expect(result).not.toBeNull();
@@ -183,14 +174,6 @@ describe.skipIf(SKIP)("handleArxiv", () => {
expect(result?.content).toMatch(/Published:/);
}
});
it("handles rate limiting gracefully", async () => {
const result = await handleArxiv("https://arxiv.org/abs/1706.03762", 5000);
expect(result).not.toBeNull();
expect(result?.method).toBe("arxiv");
// Should return something, even if rate limited
expect(result?.content).toBeTruthy();
});
});
describe.skipIf(SKIP)("handleIacr", () => {
@@ -216,14 +199,6 @@ describe.skipIf(SKIP)("handleIacr", () => {
}
});
it("handles rate limiting gracefully", async () => {
const result = await handleIacr("https://eprint.iacr.org/2023/123", 5000);
expect(result).not.toBeNull();
expect(result?.method).toBe("iacr");
// Should return something, even if rate limited
expect(result?.content).toBeTruthy();
});
it("handles PDF URLs", async () => {
const result = await handleIacr("https://eprint.iacr.org/2023/123.pdf", 30000);
expect(result).not.toBeNull();
@@ -26,8 +26,6 @@ describe.skipIf(SKIP)("handleSecEdgar", () => {
expect(result?.content).toContain("0000320193");
expect(result?.content).toContain("10-K"); // Apple files 10-K annually
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches via data.sec.gov submissions URL", async () => {
@@ -67,8 +65,6 @@ describe.skipIf(SKIP)("handleOpenCorporates", () => {
expect(result?.content).toContain("2927442");
expect(result?.content).toContain("US_DE");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches Microsoft Corporation", async () => {
@@ -29,7 +29,6 @@ describe.skipIf(SKIP)("handleMDN", () => {
expect(result?.method).toBe("mdn");
expect(result?.content).toContain("map");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
});
it("fetches Promise documentation", async () => {
@@ -40,7 +39,6 @@ describe.skipIf(SKIP)("handleMDN", () => {
expect(result).not.toBeNull();
expect(result?.method).toBe("mdn");
expect(result?.content).toContain("Promise");
expect(result?.truncated).toBeDefined();
});
it("fetches CSS documentation", async () => {
@@ -66,8 +64,6 @@ describe.skipIf(SKIP)("handleReadTheDocs", () => {
const result = await handleReadTheDocs("https://requests.readthedocs.io/en/latest/", 20);
expect(result).not.toBeNull();
expect(result?.method).toBe("readthedocs");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("returns null for non-readthedocs sites", async () => {
@@ -31,8 +31,6 @@ describe.skipIf(SKIP)("handleCoinGecko", () => {
expect(result?.content).toContain("Price");
}
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches Ethereum data", async () => {
@@ -44,7 +42,6 @@ describe.skipIf(SKIP)("handleCoinGecko", () => {
expect(result?.content).toContain("ETH");
expect(result?.content).toContain("Market Cap");
}
expect(result?.truncated).toBeDefined();
});
it("handles URL without locale prefix", async () => {
@@ -77,8 +74,6 @@ describe.skipIf(SKIP)("handleDiscogs", () => {
expect(result?.method).toBe("discogs");
expect(result?.content).toContain("Tracklist");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches master release", async () => {
@@ -87,7 +82,6 @@ describe.skipIf(SKIP)("handleDiscogs", () => {
expect(result).not.toBeNull();
expect(result?.method).toBe("discogs");
expect(result?.content).toContain("Master Release");
expect(result?.truncated).toBeDefined();
});
it("handles release URL with just ID", async () => {
@@ -121,8 +115,6 @@ describe.skipIf(SKIP)("handleArtifactHub", () => {
expect(result?.content).toContain("Helm Chart");
expect(result?.content).toContain("Version");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches prometheus-community/prometheus helm chart", async () => {
@@ -134,7 +126,6 @@ describe.skipIf(SKIP)("handleArtifactHub", () => {
expect(result?.method).toBe("artifacthub");
expect(result?.content).toContain("prometheus");
expect(result?.content).toContain("Repository");
expect(result?.truncated).toBeDefined();
});
it("handles www subdomain", async () => {
@@ -25,8 +25,6 @@ describe.skipIf(SKIP)("handleMetaCPAN", () => {
expect(result?.method).toBe("metacpan");
expect(result?.content).toContain("Moose");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches release by distribution name", async () => {
@@ -55,8 +53,6 @@ describe.skipIf(SKIP)("handleHackage", () => {
expect(result?.content).toContain("aeson");
expect(result?.content).toContain("JSON");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
}, 20000);
it("fetches text package", async () => {
@@ -85,8 +81,6 @@ describe.skipIf(SKIP)("handleDockerHub", () => {
expect(result?.content).toContain("nginx");
expect(result?.content).toContain("docker pull");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches grafana/grafana image", async () => {
@@ -115,8 +109,6 @@ describe.skipIf(SKIP)("handleChocolatey", () => {
expect(result?.method).toBe("chocolatey");
expect(result?.content).toContain("choco install");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches nodejs package", async () => {
@@ -145,8 +137,6 @@ describe.skipIf(SKIP)("handleRepology", () => {
expect(result?.content).toContain("firefox");
expect(result?.content).toContain("Repositories");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches vim project", async () => {
@@ -176,8 +166,6 @@ describe.skipIf(SKIP)("handleTerraform", () => {
expect(result?.content).toContain("hashicorp");
expect(result?.content).toContain("required_providers");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches terraform-aws-modules/vpc/aws module", async () => {
@@ -16,8 +16,6 @@ describe.skipIf(SKIP)("handleBrew", () => {
expect(result?.content).toContain("wget");
expect(result?.content).toContain("brew install wget");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches firefox cask", async () => {
@@ -27,8 +25,6 @@ describe.skipIf(SKIP)("handleBrew", () => {
expect(result?.content).toContain("Firefox");
expect(result?.content).toContain("brew install --cask firefox");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -41,8 +37,6 @@ describe.skipIf(SKIP)("handleAur", () => {
expect(result?.content).toContain("AUR helper");
expect(result?.content).toContain("yay -S yay");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -54,8 +48,6 @@ describe.skipIf(SKIP)("handleRubyGems", () => {
expect(result?.content).toContain("rails");
expect(result?.content).toContain("Total Downloads");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -67,8 +59,6 @@ describe.skipIf(SKIP)("handleNuGet", () => {
expect(result?.content).toContain("Newtonsoft.Json");
expect(result?.content).toContain("JSON");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -80,8 +70,6 @@ describe.skipIf(SKIP)("handlePackagist", () => {
expect(result?.content).toContain("laravel/framework");
expect(result?.content).toContain("Downloads");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -95,8 +83,6 @@ describe.skipIf(SKIP)("handleMaven", () => {
expect(result?.content).toContain("<groupId>");
expect(result?.content).toContain("implementation");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches commons-lang3 artifact from mvnrepository.com", async () => {
@@ -23,8 +23,6 @@ describe.skipIf(SKIP)("handleWikidata", () => {
expect(result?.content).toContain("Apple");
expect(result?.content).toContain("Q312");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches Q5 - human (entity)", async () => {
@@ -34,8 +32,6 @@ describe.skipIf(SKIP)("handleWikidata", () => {
expect(result?.content).toContain("human");
expect(result?.content).toContain("Q5");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -55,8 +51,6 @@ describe.skipIf(SKIP)("handleOpenLibrary", () => {
expect(result).not.toBeNull();
expect(result?.method).toBe("openlibrary");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches work OL45804W - The Lord of the Rings", async () => {
@@ -65,8 +59,6 @@ describe.skipIf(SKIP)("handleOpenLibrary", () => {
expect(result?.method).toBe("openlibrary");
expect(result?.content).toContain("OL45804W");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
});
@@ -89,8 +81,6 @@ describe.skipIf(SKIP)("handleBiorxiv", () => {
expect(result?.content).toContain("AlphaFold");
expect(result?.content).toContain("Abstract");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
// Testing with version suffix handling
@@ -28,8 +28,6 @@ describe.skipIf(SKIP)("handleNvd", () => {
expect(result?.content).toContain("Log4j");
expect(result?.content).toContain("CVSS");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches CVE-2014-0160 (Heartbleed)", async () => {
@@ -38,7 +36,6 @@ describe.skipIf(SKIP)("handleNvd", () => {
expect(result?.method).toBe("nvd");
expect(result?.content).toContain("CVE-2014-0160");
expect(result?.content).toContain("OpenSSL");
expect(result?.truncated).toBeDefined();
});
it("handles lowercase CVE IDs", async () => {
@@ -72,8 +69,6 @@ describe.skipIf(SKIP)("handleOsv", () => {
expect(result?.content).toContain("GHSA-jfh8-c2jp-5v3q");
expect(result?.content).toContain("log4j");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches CVE-2021-44228 via OSV", async () => {
@@ -81,7 +76,6 @@ describe.skipIf(SKIP)("handleOsv", () => {
expect(result).not.toBeNull();
expect(result?.method).toBe("osv");
expect(result?.content).toContain("CVE-2021-44228");
expect(result?.truncated).toBeDefined();
});
it("fetches PYSEC vulnerability", async () => {
@@ -28,8 +28,6 @@ describe.skipIf(SKIP)("handleMastodon", () => {
expect(result?.content).toContain("**Followers:**");
expect(result?.content).toContain("**Following:**");
expect(result?.content).toContain("**Posts:**");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
expect(result?.notes?.[0]).toContain("Mastodon API");
},
{ timeout: 30000 },
@@ -67,8 +65,6 @@ describe.skipIf(SKIP)("handleBluesky", () => {
expect(result?.content).toContain("**Following:**");
expect(result?.content).toContain("**Posts:**");
expect(result?.content).toContain("**DID:**");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
expect(result?.notes).toContain("Fetched via AT Protocol API");
},
{ timeout: 30000 },
@@ -84,8 +80,6 @@ describe.skipIf(SKIP)("handleBluesky", () => {
expect(result?.contentType).toBe("text/markdown");
expect(result?.content).toContain("@jay.bsky.team");
expect(result?.content).toContain("**Followers:**");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
},
{ timeout: 30000 },
);
@@ -29,8 +29,6 @@ describe.skipIf(SKIP)("handleStackOverflow", () => {
expect(result?.method).toBe("stackexchange");
expect(result?.content).toContain("NullPointerException");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
expect(result?.notes?.[0]).toContain("site=stackoverflow");
});
@@ -44,7 +42,6 @@ describe.skipIf(SKIP)("handleStackOverflow", () => {
expect(result?.method).toBe("stackexchange");
expect(result?.content).toContain("whitespace");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.notes?.[0]).toContain("site=unix");
});
@@ -58,7 +55,6 @@ describe.skipIf(SKIP)("handleStackOverflow", () => {
expect(result?.method).toBe("stackexchange");
expect(result?.content).toContain("PATH");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.notes?.[0]).toContain("site=superuser");
});
@@ -72,7 +68,6 @@ describe.skipIf(SKIP)("handleStackOverflow", () => {
expect(result?.method).toBe("stackexchange");
expect(result?.content).toContain("apt");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.notes?.[0]).toContain("site=askubuntu");
});
@@ -86,7 +81,6 @@ describe.skipIf(SKIP)("handleStackOverflow", () => {
expect(result?.method).toBe("stackexchange");
expect(result?.content).toMatch(/proxy/i);
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.notes?.[0]).toContain("site=serverfault");
});
@@ -96,14 +90,8 @@ describe.skipIf(SKIP)("handleStackOverflow", () => {
it("returns complete response structure", async () => {
const result = await handleStackOverflow("https://stackoverflow.com/questions/218384", 20);
expect(result).not.toBeNull();
expect(result).toHaveProperty("url");
expect(result).toHaveProperty("finalUrl");
expect(result).toHaveProperty("contentType", "text/markdown");
expect(result).toHaveProperty("method", "stackexchange");
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("fetchedAt");
expect(result).toHaveProperty("truncated");
expect(result).toHaveProperty("notes");
// Content should have question structure
expect(result?.content).toContain("# ");
expect(result?.content).toContain("Score:");
@@ -23,8 +23,6 @@ describe.skipIf(SKIP)("handleRfc", () => {
expect(result?.content).toContain("HTTP/1.1");
expect(result?.content).toContain("Hypertext Transfer Protocol");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches RFC 2616 via datatracker URL", async () => {
@@ -66,8 +64,6 @@ describe.skipIf(SKIP)("handleCheatSh", () => {
expect(result?.method).toBe("cheat.sh");
expect(result?.content).toContain("curl");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches tar cheatsheet", async () => {
@@ -102,8 +98,6 @@ describe.skipIf(SKIP)("handleTldr", () => {
expect(result?.method).toBe("tldr");
expect(result?.content).toContain("git");
expect(result?.contentType).toBe("text/markdown");
expect(result?.fetchedAt).toBeTruthy();
expect(result?.truncated).toBeDefined();
});
it("fetches curl tldr page", async () => {
@@ -25,10 +25,6 @@ describe.skipIf(SKIP)("handleWikipedia", () => {
expect(result?.finalUrl).toBe("https://en.wikipedia.org/wiki/Computer");
expect(result?.truncated).toBe(false);
expect(result?.notes).toContain("Fetched via Wikipedia API");
expect(result?.fetchedAt).toBeDefined();
// Should be a valid ISO timestamp
expect(() => new Date(result?.fetchedAt ?? "")).not.toThrow();
// The handler should filter out References and External links sections
const content = result?.content ?? "";
const hasReferencesHeading = /^## References$/m.test(content);
const hasExternalLinksHeading = /^## External links$/m.test(content);
@@ -112,18 +112,6 @@ describe.skipIf(SKIP)("handleYouTube", () => {
}
}, 30000);
it("handles videos without transcripts gracefully", async () => {
// Many music videos lack captions, but this is not guaranteed
// Just verify the handler doesn't crash and provides some info
const result = await handleYouTube("https://www.youtube.com/watch?v=kJQP7kiw5Fk", 30);
expect(result).not.toBeNull();
if (result?.method === "youtube") {
// Should still have basic metadata
expect(result.content).toContain("Video ID");
}
}, 30000);
it("returns appropriate response when yt-dlp is not available", async () => {
// We can't force yt-dlp to be unavailable in tests, but we can verify
// the return structure matches expectations for both cases
@@ -132,13 +120,7 @@ describe.skipIf(SKIP)("handleYouTube", () => {
// Should have one of these methods
expect(["parallel", "youtube", "youtube-no-ytdlp"]).toContain(result.method);
// Both should have required fields
expect(result.url).toBe("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
expect(result.finalUrl).toContain("youtube.com");
expect(result.fetchedAt).toBeTruthy();
expect(typeof result.truncated).toBe("boolean");
expect(Array.isArray(result.notes)).toBe(true);
}, 30000);
it("normalizes video URLs to canonical format", async () => {