From 35d071b1be951de25a958dafe81d8aba485b14b8 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 19 Feb 2026 15:24:47 +0900 Subject: [PATCH] test(hashline-read-enhancer): add hash consistency and content isolation tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../hashline-read-enhancer/index.test.ts | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/hooks/hashline-read-enhancer/index.test.ts b/src/hooks/hashline-read-enhancer/index.test.ts index 0c640f09..b660d39a 100644 --- a/src/hooks/hashline-read-enhancer/index.test.ts +++ b/src/hooks/hashline-read-enhancer/index.test.ts @@ -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