test(hashline-read-enhancer): add hash consistency and content isolation tests

Add comprehensive test coverage for:
- Hash consistency validation between Read tool output and Edit tool validateLineRef
- Injected content isolation to prevent hashifying non-file-content lines
- Footer messages and system reminders that should pass through unchanged

Tests ensure Read hook properly handles content boundaries and maintains
hash validity for Edit tool operations.

🤖 Generated with assistance of oh-my-opencode
This commit is contained in:
YeonGyu-Kim 2026-02-19 15:24:47 +09:00
parent 64b2d69036
commit 35d071b1be

View File

@ -1,5 +1,7 @@
import { describe, it, expect, beforeEach } from "bun:test"
import { createHashlineReadEnhancerHook } from "./hook"
import { computeLineHash } from "../../tools/hashline-edit/hash-computation"
import { validateLineRef } from "../../tools/hashline-edit/validation"
import type { PluginInput } from "@opencode-ai/plugin"
//#given - Test setup helpers
@ -201,6 +203,117 @@ describe("createHashlineReadEnhancerHook", () => {
})
})
describe("hash consistency with Edit tool validation", () => {
it("hash in Read output matches what validateLineRef expects for the same file content", async () => {
//#given
const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true))
const input = { tool: "read", sessionID, callID: "call-1" }
const fileLines = ["import { foo } from './bar'", " const x = 1", "", "export default x"]
const readOutput = fileLines.map((line, i) => `${i + 1}: ${line}`).join("
")
const output = { title: "Read", output: readOutput, metadata: {} }
//#when
await hook["tool.execute.after"](input, output)
//#then - each hash in Read output must satisfy validateLineRef
const transformedLines = output.output.split("
")
for (let i = 0; i < fileLines.length; i++) {
const lineNum = i + 1
const expectedHash = computeLineHash(lineNum, fileLines[i])
expect(transformedLines[i]).toBe(`${lineNum}:${expectedHash}|${fileLines[i]}`)
// Must not throw when used with validateLineRef
expect(() => validateLineRef({ line: lineNum, hash: expectedHash }, fileLines)).not.toThrow()
}
})
})
describe("injected content isolation", () => {
it("should NOT hashify lines appended by other hooks that happen to match the numbered pattern", async () => {
//#given - file content followed by injected README with numbered items
const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true))
const input = { tool: "read", sessionID, callID: "call-1" }
const output = {
title: "Read",
output: "1: const x = 1
2: const y = 2
[Project README]
1: First item
2: Second item",
metadata: {},
}
//#when
await hook["tool.execute.after"](input, output)
//#then - only original file content gets hashified
const lines = output.output.split("
")
expect(lines[0]).toMatch(/^1:[a-f0-9]{2}\|const x = 1$/)
expect(lines[1]).toMatch(/^2:[a-f0-9]{2}\|const y = 2$/)
expect(lines[2]).toBe("[Project README]")
expect(lines[3]).toBe("1: First item")
expect(lines[4]).toBe("2: Second item")
})
it("should NOT hashify Read tool footer messages after file content", async () => {
//#given
const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true))
const input = { tool: "read", sessionID, callID: "call-1" }
const output = {
title: "Read",
output: "1: const x = 1
2: const y = 2
(File has more lines. Use 'offset' parameter to read beyond line 2)",
metadata: {},
}
//#when
await hook["tool.execute.after"](input, output)
//#then - footer passes through unchanged
const lines = output.output.split("
")
expect(lines[0]).toMatch(/^1:[a-f0-9]{2}\|const x = 1$/)
expect(lines[1]).toMatch(/^2:[a-f0-9]{2}\|const y = 2$/)
expect(lines[2]).toBe("")
expect(lines[3]).toBe("(File has more lines. Use 'offset' parameter to read beyond line 2)")
})
it("should NOT hashify system reminder content appended after file content", async () => {
//#given - other hooks append system reminders to output
const hook = createHashlineReadEnhancerHook(mockCtx, createMockConfig(true))
const input = { tool: "read", sessionID, callID: "call-1" }
const output = {
title: "Read",
output: "1: export function hello() {
2: return 42
3: }
[System Reminder]
1: Do not forget X
2: Always do Y",
metadata: {},
}
//#when
await hook["tool.execute.after"](input, output)
//#then
const lines = output.output.split("
")
expect(lines[0]).toMatch(/^1:[a-f0-9]{2}\|export function hello\(\) \{$/)
expect(lines[1]).toMatch(/^2:[a-f0-9]{2}\| return 42$/)
expect(lines[2]).toMatch(/^3:[a-f0-9]{2}\|}$/)
expect(lines[3]).toBe("")
expect(lines[4]).toBe("[System Reminder]")
expect(lines[5]).toBe("1: Do not forget X")
expect(lines[6]).toBe("2: Always do Y")
})
})
describe("edge cases", () => {
it("should handle non-string output gracefully", async () => {
//#given