mcp_test.go

  1package chat
  2
  3import (
  4	"strings"
  5	"testing"
  6)
  7
  8func TestLooksLikeDiff(t *testing.T) {
  9	t.Parallel()
 10
 11	tests := []struct {
 12		name    string
 13		content string
 14		want    bool
 15	}{
 16		{
 17			name: "simple unified diff",
 18			content: `diff --git a/main.go b/main.go
 19--- a/main.go
 20+++ b/main.go
 21@@ -1,5 +1,6 @@
 22 package main
 23 
 24+import "fmt"
 25+
 26 func main() {
 27-    println("hello")
 28+    fmt.Println("hello")
 29 }
 30`,
 31			want: true,
 32		},
 33		{
 34			name:    "plain text",
 35			content: "This is just some plain text with no diff markers.",
 36			want:    false,
 37		},
 38		{
 39			name:    "empty string",
 40			content: "",
 41			want:    false,
 42		},
 43		{
 44			name: "markdown with headers",
 45			content: `# Title
 46
 47Some content here.
 48
 49## Subtitle
 50
 51More content with **bold** text.
 52`,
 53			want: false,
 54		},
 55		{
 56			name: "diff with mixed content",
 57			content: `diff --git a/file.txt b/file.txt
 58--- a/file.txt
 59+++ b/file.txt
 60@@ -1 +1 @@
 61-old line
 62+new line
 63`,
 64			want: true,
 65		},
 66		{
 67			name: "only plus/minus without hunk or headers",
 68			content: `Hello world
 69---
 70This is not really a diff
 71Just some text with a few symbols
 72+ another line
 73More regular content here
 74And even more content
 75`,
 76			want: false,
 77		},
 78		{
 79			name: "GitHub PR diff format",
 80			content: `diff --git a/src/app.ts b/src/app.ts
 81index abc1234..def5678 100644
 82--- a/src/app.ts
 83+++ b/src/app.ts
 84@@ -10,6 +10,8 @@ function handleRequest() {
 85   const data = getData();
 86+  validate(data);
 87+  log(data);
 88   return process(data);
 89 }
 90`,
 91			want: true,
 92		},
 93		{
 94			name: "non-git unified patch with hunk and headers",
 95			content: `--- a/old.c
 96+++ b/old.c
 97@@ -1,3 +1,4 @@
 98 #include <stdio.h>
 99-int main() {
100+int main(int argc, char **argv) {
101     return 0;
102 }
103`,
104			want: true,
105		},
106		{
107			name: "file headers without hunk markers",
108			content: `--- a/somefile.txt
109+++ b/somefile.txt
110Just some content here
111No hunk markers at all
112`,
113			want: false,
114		},
115		{
116			name: "hunk markers without file headers",
117			content: `@@ -1,3 +1,4 @@
118 some line
119-another line
120+changed line
121`,
122			want: false,
123		},
124		{
125			name: "markdown list with plus signs",
126			content: `- Item one
127- Item two
128+ Bonus item
129- Item three
130`,
131			want: false,
132		},
133	}
134
135	for _, tt := range tests {
136		t.Run(tt.name, func(t *testing.T) {
137			t.Parallel()
138			got := looksLikeDiff(tt.content)
139			if got != tt.want {
140				t.Errorf("looksLikeDiff() = %v, want %v", got, tt.want)
141			}
142		})
143	}
144}
145
146func TestParseUnifiedDiff(t *testing.T) {
147	t.Parallel()
148
149	tests := []struct {
150		name  string
151		input string
152		want  []parsedDiffFile
153	}{
154		{
155			name: "simple diff with additions and removals",
156			input: `diff --git a/main.go b/main.go
157--- a/main.go
158+++ b/main.go
159@@ -1,5 +1,6 @@
160 package main
161 
162+import "fmt"
163+
164 func main() {
165-    println("hello")
166+    fmt.Println("hello")
167 }
168`,
169			want: []parsedDiffFile{
170				{
171					path:   "main.go",
172					before: "package main\n\nfunc main() {\n    println(\"hello\")\n}",
173					after:  "package main\n\nimport \"fmt\"\n\nfunc main() {\n    fmt.Println(\"hello\")\n}",
174				},
175			},
176		},
177		{
178			name: "new file creation",
179			input: `diff --git a/newfile.go b/newfile.go
180new file mode 100644
181--- /dev/null
182+++ b/newfile.go
183@@ -0,0 +1,3 @@
184+package main
185+
186+func main() {}
187`,
188			want: []parsedDiffFile{
189				{
190					path:   "newfile.go",
191					before: "",
192					after:  "package main\n\nfunc main() {}",
193				},
194			},
195		},
196		{
197			name: "file deletion",
198			input: `diff --git a/oldfile.go b/oldfile.go
199deleted file mode 100644
200--- a/oldfile.go
201+++ /dev/null
202@@ -1,3 +0,0 @@
203-package main
204-
205-func main() {}
206`,
207			want: []parsedDiffFile{
208				{
209					path:   "oldfile.go",
210					before: "package main\n\nfunc main() {}",
211					after:  "",
212				},
213			},
214		},
215		{
216			name:  "non-diff content",
217			input: "Just some regular text",
218			want:  nil,
219		},
220		{
221			name: "diff with timestamp in header",
222			input: `diff --git a/config.yml b/config.yml
223--- a/config.yml	2024-01-15 10:30:00
224+++ b/config.yml	2024-01-15 10:31:00
225@@ -1,3 +1,4 @@
226 name: myapp
227-version: 1.0
228+version: 1.1
229+debug: true
230`,
231			want: []parsedDiffFile{
232				{
233					path:   "config.yml",
234					before: "name: myapp\nversion: 1.0",
235					after:  "name: myapp\nversion: 1.1\ndebug: true",
236				},
237			},
238		},
239		{
240			name: "multi-file diff",
241			input: `diff --git a/one.txt b/one.txt
242--- a/one.txt
243+++ b/one.txt
244@@ -1,3 +1,3 @@
245 line one
246-line two
247+line two updated
248 line three
249diff --git a/two.txt b/two.txt
250--- a/two.txt
251+++ b/two.txt
252@@ -1,2 +1,3 @@
253 alpha
254+beta
255 gamma
256`,
257			want: []parsedDiffFile{
258				{
259					path:   "one.txt",
260					before: "line one\nline two\nline three",
261					after:  "line one\nline two updated\nline three",
262				},
263				{
264					path:   "two.txt",
265					before: "alpha\ngamma",
266					after:  "alpha\nbeta\ngamma",
267				},
268			},
269		},
270		{
271			name: "non-git unified patch",
272			input: `--- old.c
273+++ old.c
274@@ -1,3 +1,4 @@
275 #include <stdio.h>
276-int main() {
277+int main(int argc, char **argv) {
278     return 0;
279 }
280`,
281			want: []parsedDiffFile{
282				{
283					path:   "old.c",
284					before: "#include <stdio.h>\nint main() {\n    return 0;\n}",
285					after:  "#include <stdio.h>\nint main(int argc, char **argv) {\n    return 0;\n}",
286				},
287			},
288		},
289		{
290			name: "non-git new file from /dev/null",
291			input: `--- /dev/null
292+++ newfile.txt
293@@ -0,0 +1,2 @@
294+hello
295+world
296`,
297			want: []parsedDiffFile{
298				{
299					path:   "newfile.txt",
300					before: "",
301					after:  "hello\nworld",
302				},
303			},
304		},
305		{
306			name: "non-git new file with only +++ header",
307			input: `+++ brand_new.go
308@@ -0,0 +1,3 @@
309+package main
310+
311+func main() {}
312`,
313			want: []parsedDiffFile{
314				{
315					path:   "brand_new.go",
316					before: "",
317					after:  "package main\n\nfunc main() {}",
318				},
319			},
320		},
321		{
322			name: "multi-hunk single file",
323			input: `diff --git a/big.go b/big.go
324--- a/big.go
325+++ b/big.go
326@@ -1,4 +1,5 @@
327 package main
328+import "os"
329 
330 func init() {
331@@ -10,3 +11,3 @@
332-    println("done")
333+    fmt.Println("done")
334 }
335`,
336			want: []parsedDiffFile{
337				{
338					path:   "big.go",
339					before: "package main\n\nfunc init() {\n    println(\"done\")\n}",
340					after:  "package main\nimport \"os\"\n\nfunc init() {\n    fmt.Println(\"done\")\n}",
341				},
342			},
343		},
344		{
345			name: "hunk content starting with header-like prefixes",
346			input: `diff --git a/file.txt b/file.txt
347--- a/file.txt
348+++ b/file.txt
349@@ -1,3 +1,3 @@
350---- tricky
351++++ newer
352 keep
353`,
354			want: []parsedDiffFile{
355				{
356					path:   "file.txt",
357					before: "--- tricky\nkeep",
358					after:  "+++ newer\nkeep",
359				},
360			},
361		},
362	}
363
364	for _, tt := range tests {
365		t.Run(tt.name, func(t *testing.T) {
366			t.Parallel()
367			got := parseUnifiedDiff(tt.input)
368			if len(got) != len(tt.want) {
369				t.Errorf("parseUnifiedDiff() returned %d files, want %d", len(got), len(tt.want))
370				return
371			}
372			for i, w := range tt.want {
373				if got[i].path != w.path {
374					t.Errorf("parseUnifiedDiff()[%d].path = %q, want %q", i, got[i].path, w.path)
375				}
376				if got[i].before != w.before {
377					t.Errorf("parseUnifiedDiff()[%d].before = %q, want %q", i, got[i].before, w.before)
378				}
379				if got[i].after != w.after {
380					t.Errorf("parseUnifiedDiff()[%d].after = %q, want %q", i, got[i].after, w.after)
381				}
382			}
383		})
384	}
385}
386
387func TestLooksLikeDiffVersusMarkdown(t *testing.T) {
388	t.Parallel()
389
390	// A unified diff should be detected as a diff, not markdown,
391	// even though it contains "-" which could match markdown patterns.
392	diffContent := strings.Join([]string{
393		"diff --git a/README.md b/README.md",
394		"--- a/README.md",
395		"+++ b/README.md",
396		"@@ -1,3 +1,3 @@",
397		" # Title",
398		"-Old subtitle",
399		"+New subtitle",
400		" Some content",
401	}, "\n")
402
403	if !looksLikeDiff(diffContent) {
404		t.Error("looksLikeDiff() should detect unified diff")
405	}
406}