Files
oh-my-pi/packages/coding-agent/test/tools/read-pdf-line-range.test.ts
T
can1357 68c454c0fc feat(edit/read): enabled canonical snapshot keys across read/write paths
- Canonicalized snapshot key resolution with realpath, parent fallback, and raw fallback.
- Updated snapshot, hashline, read, and write flows to use canonical snapshot keys consistently.
- Fixed converted-content line-range reads to apply selectors via in-memory range/text builders.
- Updated read tool docs to clarify one-line and multi-range selector context bounds.
2026-06-08 02:04:59 +02:00

104 lines
3.8 KiB
TypeScript

/**
* Regression test for cluster 51: line-range selectors on PDF/document
* reads silently returned the head of the converted document. The fix
* routes the converted markdown through the same in-memory builders that
* notebook reads use.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read";
import * as markit from "@oh-my-pi/pi-coding-agent/utils/markit";
import { Snowflake } from "@oh-my-pi/pi-utils";
function makeSession(testDir: string): ToolSession {
const sessionFile = path.join(testDir, "session.jsonl");
const artifactsDir = sessionFile.slice(0, -6);
let nextArtifactId = 0;
return {
cwd: testDir,
hasUI: false,
getSessionFile: () => sessionFile,
getArtifactsDir: () => artifactsDir,
getSessionSpawns: () => null,
allocateOutputArtifact: async toolType => {
const id = String(nextArtifactId++);
return { id, path: path.join(artifactsDir, `${id}.${toolType}.log`) };
},
settings: Settings.isolated(),
};
}
describe("read PDF with a line-range selector", () => {
let testDir: string;
let pdfPath: string;
beforeEach(() => {
testDir = path.join(os.tmpdir(), `read-pdf-${Snowflake.next()}`);
fs.mkdirSync(testDir, { recursive: true });
pdfPath = path.join(testDir, "doc.pdf");
fs.writeFileSync(pdfPath, "%PDF-stub");
});
afterEach(() => {
vi.restoreAllMocks();
fs.rmSync(testDir, { recursive: true, force: true });
});
it("honours `:N-M` against the converted markdown body", async () => {
const converted = Array.from({ length: 200 }, (_, i) => `pdf line ${i + 1}`).join("\n");
vi.spyOn(markit, "convertFileWithMarkit").mockResolvedValue({ ok: true, content: converted });
const session = makeSession(testDir);
const tool = new ReadTool(session);
const result = await tool.execute("call", { path: `${pdfPath}:120-122` });
const text = result.content
.filter(c => c.type === "text")
.map(c => c.text)
.join("\n");
// The requested window must surface. Pre-fix the read silently returned
// the head of the document (lines 1-200 head-truncated) instead.
expect(text).toContain("pdf line 120");
expect(text).toContain("pdf line 122");
expect(text).not.toContain("pdf line 1\n");
expect(text).not.toContain("pdf line 5");
});
it("honours `:A-B,C-D` multi-range against the converted markdown body", async () => {
const converted = Array.from({ length: 200 }, (_, i) => `pdf line ${i + 1}`).join("\n");
vi.spyOn(markit, "convertFileWithMarkit").mockResolvedValue({ ok: true, content: converted });
const session = makeSession(testDir);
const tool = new ReadTool(session);
const result = await tool.execute("call", { path: `${pdfPath}:50-52,160-162` });
const text = result.content
.filter(c => c.type === "text")
.map(c => c.text)
.join("\n");
expect(text).toContain("pdf line 50");
expect(text).toContain("pdf line 52");
expect(text).toContain("pdf line 160");
expect(text).toContain("pdf line 162");
expect(text).not.toContain("pdf line 100");
});
it("falls back to the full converted body when no selector is provided", async () => {
const converted = "pdf line 1\npdf line 2\npdf line 3\n";
vi.spyOn(markit, "convertFileWithMarkit").mockResolvedValue({ ok: true, content: converted });
const session = makeSession(testDir);
const tool = new ReadTool(session);
const result = await tool.execute("call", { path: pdfPath });
const text = result.content
.filter(c => c.type === "text")
.map(c => c.text)
.join("\n");
expect(text).toContain("pdf line 1");
expect(text).toContain("pdf line 3");
});
});