open-code-review/internal/diff/resolver_test.go
2026-05-20 22:03:52 +08:00

234 lines
6.6 KiB
Go

package diff
import (
"testing"
"github.com/open-code-review/open-code-review/internal/model"
)
const testDiff = `diff --git a/pkg/example/handler.go b/pkg/example/handler.go
--- a/pkg/example/handler.go
+++ b/pkg/example/handler.go
@@ -10,7 +10,7 @@ func HandleRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
- log.Print("handling request")
+ log.Printf("handling request: %s", r.URL.Path)
err := process(ctx)`
func TestResolveLineNumbers_SingleLineHunkMatch(t *testing.T) {
diffs := []model.Diff{
{NewPath: "pkg/example/handler.go", Diff: testDiff},
}
comments := []model.LlmComment{
{Path: "pkg/example/handler.go", ExistingCode: ` log.Print("handling request")`},
}
result := ResolveLineNumbers(comments, diffs)
if len(result) != 1 {
t.Fatalf("expected 1 comment, got %d", len(result))
}
cm := result[0]
if cm.StartLine == 0 || cm.EndLine == 0 {
t.Errorf("expected non-zero line numbers, got StartLine=%d EndLine=%d", cm.StartLine, cm.EndLine)
}
// The existing code is at old-file line 11.
// Diff: @@ -10,7 → context "ctx := r.Context()" is old line 10, offset becomes 1.
// Then deleted line "log.Print..." matches → OldStart(10) + offset-before-match...
// Actually offset increments AFTER each FROM-side check, so it's still 0 when we hit line 0 (context).
// After context line, offset=1. Deleted line at index 1 tries match with offset=1 → startLine=11.
// Wait — need to trace carefully: ctx line is HunkContext, offset++ makes it 1 before next iteration.
// So deleted line sees offset=1, startLine = 10+1 = 11 ✓
if cm.StartLine != 11 || cm.EndLine != 11 {
t.Errorf("expected 11..11, got %d..%d", cm.StartLine, cm.EndLine)
}
}
func TestResolveLineNumbers_WhitespaceTolerant(t *testing.T) {
diffs := []model.Diff{
{NewPath: "pkg/example/handler.go", Diff: testDiff},
}
// LLM may return indented or differently formatted code
comments := []model.LlmComment{
{Path: "pkg/example/handler.go", ExistingCode: `log.Print("handling request")`},
}
result := ResolveLineNumbers(comments, diffs)
cm := result[0]
if cm.StartLine != 11 || cm.EndLine != 11 {
t.Errorf("whitespace-tolerant match: expected 11..11, got %d..%d", cm.StartLine, cm.EndLine)
}
}
func TestResolveLineNumbers_MultiLineHunkMatch(t *testing.T) {
rawMulti := `diff --git a/test.go b/test.go
--- a/test.go
+++ b/test.go
@@ -5,4 +5,4 @@ import "fmt"
func foo() {
- x := 1
- y := 2
+ x := 10
+ y := 20
}`
diffs := []model.Diff{
{NewPath: "test.go", Diff: rawMulti},
}
comments := []model.LlmComment{
{Path: "test.go", ExistingCode: ` x := 1
y := 2`},
}
result := ResolveLineNumbers(comments, diffs)
cm := result[0]
if cm.StartLine == 0 || cm.EndLine == 0 {
t.Errorf("multiline hunk match: expected non-zero lines, got StartLine=%d EndLine=%d", cm.StartLine, cm.EndLine)
}
if cm.StartLine != 6 || cm.EndLine != 7 {
t.Errorf("expected 6..7, got %d..%d", cm.StartLine, cm.EndLine)
}
}
func TestResolveLineNumbers_FallbackToFileContent(t *testing.T) {
// Code that doesn't appear in diff hunks but exists in file content
raw := `diff --git a/test.go b/test.go
--- a/test.go
+++ b/test.go
@@ -1,3 +1,4 @@
package main
+import "fmt"
func foo() {}`
diffs := []model.Diff{
{NewPath: "test.go", Diff: raw, NewFileContent: `package main
import "fmt"
func foo() {}`},
}
comments := []model.LlmComment{
{Path: "test.go", ExistingCode: `package main
import "fmt"`},
}
result := ResolveLineNumbers(comments, diffs)
cm := result[0]
// Fallback should find these consecutive lines starting at line 1
if cm.StartLine != 1 || cm.EndLine != 2 {
t.Errorf("fallback: expected 1..2, got %d..%d", cm.StartLine, cm.EndLine)
}
}
func TestResolveLineNumbers_NoMatchKeepsZero(t *testing.T) {
diffs := []model.Diff{
{NewPath: "test.go", Diff: testDiff},
}
comments := []model.LlmComment{
{Path: "test.go", ExistingCode: `totally unrelated code`},
}
result := ResolveLineNumbers(comments, diffs)
cm := result[0]
if cm.StartLine != 0 || cm.EndLine != 0 {
t.Errorf("no match: expected 0..0, got %d..%d", cm.StartLine, cm.EndLine)
}
}
func TestResolveLineNumbers_NoExistingCode(t *testing.T) {
diffs := []model.Diff{
{NewPath: "test.go", Diff: testDiff},
}
comments := []model.LlmComment{
{Path: "test.go", Content: "some comment without existing_code"},
}
result := ResolveLineNumbers(comments, diffs)
if result[0].StartLine != 0 {
t.Errorf("empty ExistingCode: expected 0, got %d", result[0].StartLine)
}
}
func TestResolveLineNumbers_PathNotFound(t *testing.T) {
diffs := []model.Diff{
{NewPath: "other.go", Diff: testDiff},
}
comments := []model.LlmComment{
{Path: "missing.go", ExistingCode: `some code`},
}
result := ResolveLineNumbers(comments, diffs)
if result[0].StartLine != 0 {
t.Errorf("path not found: expected 0, got %d", result[0].StartLine)
}
}
func TestResolveLineNumbers_EmptyInputs(t *testing.T) {
// No comments
r1 := ResolveLineNumbers([]model.LlmComment{}, []model.Diff{{}})
if len(r1) != 0 {
t.Errorf("empty comments: expected 0 results, got %d", len(r1))
}
// No diffs — returns comments unchanged (line numbers stay at 0)
r2 := ResolveLineNumbers([]model.LlmComment{{}}, []model.Diff{})
if len(r2) != 1 || r2[0].StartLine != 0 {
t.Errorf("empty diffs: expected 1 result with StartLine=0, got %d", len(r2))
}
}
func TestNormalizeLine(t *testing.T) {
tests := []struct {
input string
want string
}{
{" hello ", "hello"},
{"+added line", "added line"},
{"-deleted line", "deleted line"},
{"\tindented\t", "indented"},
{"", ""},
}
for _, tt := range tests {
got := normalizeLine(tt.input)
if got != tt.want {
t.Errorf("normalizeLine(%q) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestSplitAndNormalize_SkipsEmptyLines(t *testing.T) {
lines := splitAndNormalize(`line1
line2`)
if len(lines) != 2 {
t.Errorf("expected 2 lines, got %d", len(lines))
}
if lines[0] != "line1" || lines[1] != "line2" {
t.Errorf("got %v", lines)
}
}
func TestResolveFromHunk_ContextLinesOnly(t *testing.T) {
// When existing_code matches context (unchanged) lines rather than deleted ones
raw := `diff --git a/test.go b/test.go
--- a/test.go
+++ b/test.go
@@ -3,3 +3,4 @@
func main() {
fmt.Println("hello")
+ fmt.Println("world")
}`
diffs := []model.Diff{{NewPath: "test.go", Diff: raw}}
comments := []model.LlmComment{
{Path: "test.go", ExistingCode: ` fmt.Println("hello")`},
}
result := ResolveLineNumbers(comments, diffs)
cm := result[0]
if cm.StartLine == 0 {
t.Errorf("context-line match: expected non-zero start, got 0")
}
if cm.StartLine != 4 {
t.Errorf("expected line 4, got %d", cm.StartLine)
}
}