1package server
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "testing"
12)
13
14// TestGetGitRoot tests the getGitRoot function
15func TestGetGitRoot(t *testing.T) {
16 // Create a temporary directory for testing
17 tempDir := t.TempDir()
18
19 // Test with non-git directory
20 _, err := getGitRoot(tempDir)
21 if err == nil {
22 t.Error("expected error for non-git directory, got nil")
23 }
24
25 // Create a git repository
26 gitDir := filepath.Join(tempDir, "repo")
27 err = os.MkdirAll(gitDir, 0o755)
28 if err != nil {
29 t.Fatal(err)
30 }
31
32 // Initialize git repo
33 cmd := exec.Command("git", "init")
34 cmd.Dir = gitDir
35 err = cmd.Run()
36 if err != nil {
37 t.Fatal(err)
38 }
39
40 // Configure git user for commits
41 cmd = exec.Command("git", "config", "user.name", "Test User")
42 cmd.Dir = gitDir
43 err = cmd.Run()
44 if err != nil {
45 t.Fatal(err)
46 }
47
48 cmd = exec.Command("git", "config", "user.email", "test@example.com")
49 cmd.Dir = gitDir
50 err = cmd.Run()
51 if err != nil {
52 t.Fatal(err)
53 }
54
55 // Test with git directory
56 root, err := getGitRoot(gitDir)
57 if err != nil {
58 t.Errorf("unexpected error for git directory: %v", err)
59 }
60 if root != gitDir {
61 t.Errorf("expected root %s, got %s", gitDir, root)
62 }
63
64 // Test with subdirectory of git directory
65 subDir := filepath.Join(gitDir, "subdir")
66 err = os.MkdirAll(subDir, 0o755)
67 if err != nil {
68 t.Fatal(err)
69 }
70
71 root, err = getGitRoot(subDir)
72 if err != nil {
73 t.Errorf("unexpected error for git subdirectory: %v", err)
74 }
75 if root != gitDir {
76 t.Errorf("expected root %s, got %s", gitDir, root)
77 }
78}
79
80// TestParseDiffStat tests the parseDiffStat function
81func TestParseDiffStat(t *testing.T) {
82 // Test empty output
83 additions, deletions, filesCount := parseDiffStat("")
84 if additions != 0 || deletions != 0 || filesCount != 0 {
85 t.Errorf("expected 0,0,0 for empty output, got %d,%d,%d", additions, deletions, filesCount)
86 }
87
88 // Test single file
89 output := "5\t3\tfile1.txt\n"
90 additions, deletions, filesCount = parseDiffStat(output)
91 if additions != 5 || deletions != 3 || filesCount != 1 {
92 t.Errorf("expected 5,3,1 for single file, got %d,%d,%d", additions, deletions, filesCount)
93 }
94
95 // Test multiple files
96 output = "5\t3\tfile1.txt\n10\t2\tfile2.txt\n"
97 additions, deletions, filesCount = parseDiffStat(output)
98 if additions != 15 || deletions != 5 || filesCount != 2 {
99 t.Errorf("expected 15,5,2 for multiple files, got %d,%d,%d", additions, deletions, filesCount)
100 }
101
102 // Test file with additions only
103 output = "5\t0\tfile1.txt\n"
104 additions, deletions, filesCount = parseDiffStat(output)
105 if additions != 5 || deletions != 0 || filesCount != 1 {
106 t.Errorf("expected 5,0,1 for additions only, got %d,%d,%d", additions, deletions, filesCount)
107 }
108
109 // Test file with deletions only
110 output = "0\t3\tfile1.txt\n"
111 additions, deletions, filesCount = parseDiffStat(output)
112 if additions != 0 || deletions != 3 || filesCount != 1 {
113 t.Errorf("expected 0,3,1 for deletions only, got %d,%d,%d", additions, deletions, filesCount)
114 }
115
116 // Test file with binary content (represented as -)
117 output = "-\t-\tfile1.bin\n"
118 additions, deletions, filesCount = parseDiffStat(output)
119 if additions != 0 || deletions != 0 || filesCount != 1 {
120 t.Errorf("expected 0,0,1 for binary file, got %d,%d,%d", additions, deletions, filesCount)
121 }
122}
123
124// setupTestGitRepo creates a temporary git repository with some content for testing
125func setupTestGitRepo(t *testing.T) string {
126 // Create a temporary directory for testing
127 tempDir := t.TempDir()
128
129 // Initialize git repo
130 cmd := exec.Command("git", "init")
131 cmd.Dir = tempDir
132 err := cmd.Run()
133 if err != nil {
134 t.Fatal(err)
135 }
136
137 // Configure git user for commits
138 cmd = exec.Command("git", "config", "user.name", "Test User")
139 cmd.Dir = tempDir
140 err = cmd.Run()
141 if err != nil {
142 t.Fatal(err)
143 }
144
145 cmd = exec.Command("git", "config", "user.email", "test@example.com")
146 cmd.Dir = tempDir
147 err = cmd.Run()
148 if err != nil {
149 t.Fatal(err)
150 }
151
152 // Create and commit a file
153 filePath := filepath.Join(tempDir, "test.txt")
154 content := "Hello, World!\n"
155 err = os.WriteFile(filePath, []byte(content), 0o644)
156 if err != nil {
157 t.Fatal(err)
158 }
159
160 cmd = exec.Command("git", "add", "test.txt")
161 cmd.Dir = tempDir
162 err = cmd.Run()
163 if err != nil {
164 t.Fatal(err)
165 }
166
167 cmd = exec.Command("git", "commit", "-m", "Initial commit\n\nPrompt: Initial test commit for git handlers test", "--author=Test <test@example.com>")
168 cmd.Dir = tempDir
169 output, err := cmd.CombinedOutput()
170 if err != nil {
171 t.Logf("git commit failed: %v", err)
172 t.Logf("git commit output: %s", string(output))
173 t.Fatal(err)
174 }
175
176 // Modify the file (staged changes)
177 newContent := "Hello, World!\nModified content\n"
178 err = os.WriteFile(filePath, []byte(newContent), 0o644)
179 if err != nil {
180 t.Fatal(err)
181 }
182
183 cmd = exec.Command("git", "add", "test.txt")
184 cmd.Dir = tempDir
185 err = cmd.Run()
186 if err != nil {
187 t.Fatal(err)
188 }
189
190 // Modify the file again (unstaged changes)
191 unstagedContent := "Hello, World!\nModified content\nMore changes\n"
192 err = os.WriteFile(filePath, []byte(unstagedContent), 0o644)
193 if err != nil {
194 t.Fatal(err)
195 }
196
197 // Create another file (untracked)
198 untrackedPath := filepath.Join(tempDir, "untracked.txt")
199 untrackedContent := "Untracked file\n"
200 err = os.WriteFile(untrackedPath, []byte(untrackedContent), 0o644)
201 if err != nil {
202 t.Fatal(err)
203 }
204
205 return tempDir
206}
207
208// TestHandleGitDiffs tests the handleGitDiffs function
209func TestHandleGitDiffs(t *testing.T) {
210 h := NewTestHarness(t)
211 defer h.Close()
212
213 // Test with non-git directory
214 req := httptest.NewRequest("GET", "/api/git/diffs?cwd=/tmp", nil)
215 w := httptest.NewRecorder()
216 h.server.handleGitDiffs(w, req)
217
218 if w.Code != http.StatusBadRequest {
219 t.Errorf("expected status 400 for non-git directory, got %d", w.Code)
220 }
221
222 // Setup a test git repository
223 gitDir := setupTestGitRepo(t)
224
225 // Test with valid git directory
226 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/diffs?cwd=%s", gitDir), nil)
227 w = httptest.NewRecorder()
228 h.server.handleGitDiffs(w, req)
229
230 if w.Code != http.StatusOK {
231 t.Errorf("expected status 200 for git directory, got %d: %s", w.Code, w.Body.String())
232 }
233
234 // Check response content type
235 if w.Header().Get("Content-Type") != "application/json" {
236 t.Errorf("expected content-type application/json, got %s", w.Header().Get("Content-Type"))
237 }
238
239 // Parse response
240 var response struct {
241 Diffs []GitDiffInfo `json:"diffs"`
242 GitRoot string `json:"gitRoot"`
243 }
244 err := json.Unmarshal(w.Body.Bytes(), &response)
245 if err != nil {
246 t.Fatalf("failed to parse response: %v", err)
247 }
248
249 // Check that we have at least one diff (working changes)
250 if len(response.Diffs) == 0 {
251 t.Error("expected at least one diff (working changes)")
252 }
253
254 // Check that the first diff is working changes
255 if len(response.Diffs) > 0 {
256 diff := response.Diffs[0]
257 if diff.ID != "working" {
258 t.Errorf("expected first diff ID to be 'working', got %s", diff.ID)
259 }
260 if diff.Message != "Working Changes" {
261 t.Errorf("expected first diff message to be 'Working Changes', got %s", diff.Message)
262 }
263 }
264
265 // Check that git root is correct
266 if response.GitRoot != gitDir {
267 t.Errorf("expected git root %s, got %s", gitDir, response.GitRoot)
268 }
269
270 // Test with subdirectory of git directory
271 subDir := filepath.Join(gitDir, "subdir")
272 err = os.MkdirAll(subDir, 0o755)
273 if err != nil {
274 t.Fatal(err)
275 }
276
277 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/diffs?cwd=%s", subDir), nil)
278 w = httptest.NewRecorder()
279 h.server.handleGitDiffs(w, req)
280
281 if w.Code != http.StatusOK {
282 t.Errorf("expected status 200 for git subdirectory, got %d: %s", w.Code, w.Body.String())
283 }
284}
285
286// TestHandleGitDiffFiles tests the handleGitDiffFiles function
287func TestHandleGitDiffFiles(t *testing.T) {
288 h := NewTestHarness(t)
289 defer h.Close()
290
291 // Setup a test git repository
292 gitDir := setupTestGitRepo(t)
293
294 // Test with invalid method
295 req := httptest.NewRequest("POST", fmt.Sprintf("/api/git/diffs/working/files?cwd=%s", gitDir), nil)
296 w := httptest.NewRecorder()
297 h.server.handleGitDiffFiles(w, req)
298
299 if w.Code != http.StatusMethodNotAllowed {
300 t.Errorf("expected status 405 for invalid method, got %d", w.Code)
301 }
302
303 // Test with invalid path
304 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/diffs/working?cwd=%s", gitDir), nil)
305 w = httptest.NewRecorder()
306 h.server.handleGitDiffFiles(w, req)
307
308 if w.Code != http.StatusBadRequest {
309 t.Errorf("expected status 400 for invalid path, got %d", w.Code)
310 }
311
312 // Test with non-git directory
313 req = httptest.NewRequest("GET", "/api/git/diffs/working/files?cwd=/tmp", nil)
314 w = httptest.NewRecorder()
315 h.server.handleGitDiffFiles(w, req)
316
317 if w.Code != http.StatusBadRequest {
318 t.Errorf("expected status 400 for non-git directory, got %d", w.Code)
319 }
320
321 // Test with working changes
322 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/diffs/working/files?cwd=%s", gitDir), nil)
323 w = httptest.NewRecorder()
324 h.server.handleGitDiffFiles(w, req)
325
326 if w.Code != http.StatusOK {
327 t.Errorf("expected status 200 for working changes, got %d: %s", w.Code, w.Body.String())
328 }
329
330 // Check response content type
331 if w.Header().Get("Content-Type") != "application/json" {
332 t.Errorf("expected content-type application/json, got %s", w.Header().Get("Content-Type"))
333 }
334
335 // Parse response
336 var files []GitFileInfo
337 err := json.Unmarshal(w.Body.Bytes(), &files)
338 if err != nil {
339 t.Fatalf("failed to parse response: %v", err)
340 }
341
342 // Check that we have at least one file
343 if len(files) == 0 {
344 t.Error("expected at least one file in working changes")
345 }
346
347 // Check file information
348 if len(files) > 0 {
349 file := files[0]
350 if file.Path != "test.txt" {
351 t.Errorf("expected file path test.txt, got %s", file.Path)
352 }
353 if file.Status != "modified" {
354 t.Errorf("expected file status modified, got %s", file.Status)
355 }
356 }
357}
358
359// TestHandleGitFileDiff tests the handleGitFileDiff function
360func TestHandleGitFileDiff(t *testing.T) {
361 h := NewTestHarness(t)
362 defer h.Close()
363
364 // Setup a test git repository
365 gitDir := setupTestGitRepo(t)
366
367 // Test with invalid method
368 req := httptest.NewRequest("POST", fmt.Sprintf("/api/git/file-diff/working/test.txt?cwd=%s", gitDir), nil)
369 w := httptest.NewRecorder()
370 h.server.handleGitFileDiff(w, req)
371
372 if w.Code != http.StatusMethodNotAllowed {
373 t.Errorf("expected status 405 for invalid method, got %d", w.Code)
374 }
375
376 // Test with invalid path (missing diff ID)
377 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/file-diff/test.txt?cwd=%s", gitDir), nil)
378 w = httptest.NewRecorder()
379 h.server.handleGitFileDiff(w, req)
380
381 if w.Code != http.StatusBadRequest {
382 t.Errorf("expected status 400 for invalid path, got %d", w.Code)
383 }
384
385 // Test with non-git directory
386 req = httptest.NewRequest("GET", "/api/git/file-diff/working/test.txt?cwd=/tmp", nil)
387 w = httptest.NewRecorder()
388 h.server.handleGitFileDiff(w, req)
389
390 if w.Code != http.StatusBadRequest {
391 t.Errorf("expected status 400 for non-git directory, got %d", w.Code)
392 }
393
394 // Test with working changes
395 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/file-diff/working/test.txt?cwd=%s", gitDir), nil)
396 w = httptest.NewRecorder()
397 h.server.handleGitFileDiff(w, req)
398
399 if w.Code != http.StatusOK {
400 t.Errorf("expected status 200 for working changes, got %d: %s", w.Code, w.Body.String())
401 }
402
403 // Check response content type
404 if w.Header().Get("Content-Type") != "application/json" {
405 t.Errorf("expected content-type application/json, got %s", w.Header().Get("Content-Type"))
406 }
407
408 // Parse response
409 var fileDiff GitFileDiff
410 err := json.Unmarshal(w.Body.Bytes(), &fileDiff)
411 if err != nil {
412 t.Fatalf("failed to parse response: %v", err)
413 }
414
415 // Check file information
416 if fileDiff.Path != "test.txt" {
417 t.Errorf("expected file path test.txt, got %s", fileDiff.Path)
418 }
419
420 // Check that we have content
421 if fileDiff.OldContent == "" {
422 t.Error("expected old content")
423 }
424
425 if fileDiff.NewContent == "" {
426 t.Error("expected new content")
427 }
428
429 // Test with path traversal attempt (should be blocked)
430 req = httptest.NewRequest("GET", fmt.Sprintf("/api/git/file-diff/working/../etc/passwd?cwd=%s", gitDir), nil)
431 w = httptest.NewRecorder()
432 h.server.handleGitFileDiff(w, req)
433
434 if w.Code != http.StatusBadRequest {
435 t.Errorf("expected status 400 for path traversal attempt, got %d", w.Code)
436 }
437}