blob: 0df795d502aeb250baf27b723649cb9a0150c8da [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001package loop
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "testing"
Earl Lee2e463fb2025-04-17 11:22:22 -070011)
12
13// TestGitCommitTracking tests the git commit tracking functionality
14func TestGitCommitTracking(t *testing.T) {
15 // Create a temporary directory for our test git repo
16 tempDir := t.TempDir() // Automatically cleaned up when the test completes
17
18 // Initialize a git repo in the temp directory
19 cmd := exec.Command("git", "init")
20 cmd.Dir = tempDir
21 if err := cmd.Run(); err != nil {
22 t.Fatalf("Failed to initialize git repo: %v", err)
23 }
24
25 // Configure git user for commits
26 cmd = exec.Command("git", "config", "user.name", "Test User")
27 cmd.Dir = tempDir
28 if err := cmd.Run(); err != nil {
29 t.Fatalf("Failed to configure git user name: %v", err)
30 }
31
32 cmd = exec.Command("git", "config", "user.email", "test@example.com")
33 cmd.Dir = tempDir
34 if err := cmd.Run(); err != nil {
35 t.Fatalf("Failed to configure git user email: %v", err)
36 }
37
38 // Make an initial commit
39 testFile := filepath.Join(tempDir, "test.txt")
40 if err := os.WriteFile(testFile, []byte("initial content\n"), 0o644); err != nil {
41 t.Fatalf("Failed to write file: %v", err)
42 }
43
44 cmd = exec.Command("git", "add", "test.txt")
45 cmd.Dir = tempDir
46 if err := cmd.Run(); err != nil {
47 t.Fatalf("Failed to add file: %v", err)
48 }
49
50 cmd = exec.Command("git", "commit", "-m", "Initial commit")
51 cmd.Dir = tempDir
52 if err := cmd.Run(); err != nil {
53 t.Fatalf("Failed to create initial commit: %v", err)
54 }
55
Philip Zeyliger49edc922025-05-14 09:45:45 -070056 // Note: The initial commit will be tagged as sketch-base later
Earl Lee2e463fb2025-04-17 11:22:22 -070057
58 // Create agent with the temp repo
59 agent := &Agent{
Philip Zeyliger49edc922025-05-14 09:45:45 -070060 workingDir: tempDir,
61 repoRoot: tempDir, // Set repoRoot to same as workingDir for this test
Philip Zeyliger49edc922025-05-14 09:45:45 -070062 subscribers: []chan *AgentMessage{},
63 config: AgentConfig{
64 SessionID: "test-session",
65 InDocker: false,
66 },
67 history: []AgentMessage{},
Philip Zeyligerf2872992025-05-22 10:35:28 -070068 gitState: AgentGitState{
69 seenCommits: make(map[string]bool),
70 },
Philip Zeyliger49edc922025-05-14 09:45:45 -070071 }
72
73 // Create sketch-base-test-session tag at current HEAD to serve as the base commit
74 cmd = exec.Command("git", "tag", "-f", "sketch-base-test-session", "HEAD")
75 cmd.Dir = tempDir
76 if err := cmd.Run(); err != nil {
77 t.Fatalf("Failed to create sketch-base tag: %v", err)
Earl Lee2e463fb2025-04-17 11:22:22 -070078 }
79
Josh Bleecher Snyder715b8d92025-06-06 12:36:38 -070080 // Create sketch-wip branch (simulating what happens in agent initialization)
81 cmd = exec.Command("git", "checkout", "-b", "sketch-wip")
82 cmd.Dir = tempDir
83 if err := cmd.Run(); err != nil {
84 t.Fatalf("Failed to create sketch-wip branch: %v", err)
85 }
86
Earl Lee2e463fb2025-04-17 11:22:22 -070087 // Make a new commit
88 if err := os.WriteFile(testFile, []byte("updated content\n"), 0o644); err != nil {
89 t.Fatalf("Failed to update file: %v", err)
90 }
91
92 cmd = exec.Command("git", "add", "test.txt")
93 cmd.Dir = tempDir
94 if err := cmd.Run(); err != nil {
95 t.Fatalf("Failed to add updated file: %v", err)
96 }
97
98 cmd = exec.Command("git", "commit", "-m", "Second commit\n\nThis commit has a multi-line message\nwith details about the changes.")
99 cmd.Dir = tempDir
100 if err := cmd.Run(); err != nil {
101 t.Fatalf("Failed to create second commit: %v", err)
102 }
103
104 // Call handleGitCommits and verify we get a commit message
105 ctx := context.Background()
Philip Zeyliger49edc922025-05-14 09:45:45 -0700106 _, gitErr := agent.handleGitCommits(ctx)
107 if gitErr != nil {
108 t.Fatalf("handleGitCommits failed: %v", gitErr)
Earl Lee2e463fb2025-04-17 11:22:22 -0700109 }
110
111 // Check if we received a commit message
Philip Zeyliger9373c072025-05-01 10:27:01 -0700112 agent.mu.Lock()
113 if len(agent.history) == 0 {
114 agent.mu.Unlock()
115 t.Fatal("No commit message was added to history")
116 }
117 commitMsg := agent.history[len(agent.history)-1]
118 agent.mu.Unlock()
Earl Lee2e463fb2025-04-17 11:22:22 -0700119
120 // Verify the commit message
121 if commitMsg.Type != CommitMessageType {
122 t.Errorf("Expected message type %s, got %s", CommitMessageType, commitMsg.Type)
123 }
124
125 if len(commitMsg.Commits) < 1 {
126 t.Fatalf("Expected at least 1 commit, got %d", len(commitMsg.Commits))
127 }
128
129 // Find the second commit
130 var commit *GitCommit
131 found := false
132 for _, c := range commitMsg.Commits {
133 if strings.HasPrefix(c.Subject, "Second commit") {
134 commit = c
135 found = true
136 break
137 }
138 }
139
140 if !found {
141 t.Fatalf("Could not find 'Second commit' in commits")
142 }
143 if !strings.HasPrefix(commit.Subject, "Second commit") {
144 t.Errorf("Expected commit subject 'Second commit', got '%s'", commit.Subject)
145 }
146
147 if !strings.Contains(commit.Body, "multi-line message") {
148 t.Errorf("Expected body to contain 'multi-line message', got '%s'", commit.Body)
149 }
150
151 // Test with many commits
152 if testing.Short() {
153 t.Skip("Skipping multiple commits test in short mode")
154 }
155
Philip Zeyliger9373c072025-05-01 10:27:01 -0700156 // Skip the multiple commits test in short mode
157 if testing.Short() {
158 t.Log("Skipping multiple commits test in short mode")
159 return
160 }
161
162 // Make multiple commits - reduce from 110 to 20 for faster tests
163 // 20 is enough to verify the functionality without the time penalty
164 for i := range 20 {
165 newContent := fmt.Appendf(nil, "content update %d\n", i)
Earl Lee2e463fb2025-04-17 11:22:22 -0700166 if err := os.WriteFile(testFile, newContent, 0o644); err != nil {
167 t.Fatalf("Failed to update file: %v", err)
168 }
169
170 cmd = exec.Command("git", "add", "test.txt")
171 cmd.Dir = tempDir
172 if err := cmd.Run(); err != nil {
173 t.Fatalf("Failed to add updated file: %v", err)
174 }
175
176 cmd = exec.Command("git", "commit", "-m", fmt.Sprintf("Commit %d", i+3))
177 cmd.Dir = tempDir
178 if err := cmd.Run(); err != nil {
179 t.Fatalf("Failed to create commit %d: %v", i+3, err)
180 }
181 }
182
Philip Zeyliger9373c072025-05-01 10:27:01 -0700183 // Reset the seen commits map
Philip Zeyligerf2872992025-05-22 10:35:28 -0700184 agent.gitState.seenCommits = make(map[string]bool)
Earl Lee2e463fb2025-04-17 11:22:22 -0700185
Philip Zeyliger9373c072025-05-01 10:27:01 -0700186 // Call handleGitCommits again - it should show up to 20 commits (or whatever git defaults to)
Philip Zeyliger49edc922025-05-14 09:45:45 -0700187 _, handleErr := agent.handleGitCommits(ctx)
188 if handleErr != nil {
189 t.Fatalf("handleGitCommits failed: %v", handleErr)
Earl Lee2e463fb2025-04-17 11:22:22 -0700190 }
191
192 // Check if we received a commit message
Philip Zeyliger9373c072025-05-01 10:27:01 -0700193 agent.mu.Lock()
Philip Zeyligerb7c58752025-05-01 10:10:17 -0700194 commitMsg = agent.history[len(agent.history)-1]
Philip Zeyliger9373c072025-05-01 10:27:01 -0700195 agent.mu.Unlock()
Earl Lee2e463fb2025-04-17 11:22:22 -0700196
Philip Zeyliger9373c072025-05-01 10:27:01 -0700197 // We should have our commits
198 if len(commitMsg.Commits) < 5 {
199 t.Errorf("Expected at least 5 commits, but only got %d", len(commitMsg.Commits))
Earl Lee2e463fb2025-04-17 11:22:22 -0700200 }
201
Philip Zeyliger9373c072025-05-01 10:27:01 -0700202 t.Logf("Received %d commits total", len(commitMsg.Commits))
Earl Lee2e463fb2025-04-17 11:22:22 -0700203}
204
205// TestParseGitLog tests the parseGitLog function
206func TestParseGitLog(t *testing.T) {
207 tests := []struct {
208 name string
209 input string
210 expected []GitCommit
211 }{
212 {
213 name: "Empty input",
214 input: "",
215 expected: []GitCommit{},
216 },
217 {
218 name: "Single commit",
219 input: "abcdef1234567890\x00Initial commit\x00This is the first commit\x00",
220 expected: []GitCommit{
221 {Hash: "abcdef1234567890", Subject: "Initial commit", Body: "This is the first commit"},
222 },
223 },
224 {
225 name: "Multiple commits",
226 input: "abcdef1234567890\x00Initial commit\x00This is the first commit\x00" +
227 "fedcba0987654321\x00Second commit\x00This is the second commit\x00" +
228 "123456abcdef7890\x00Third commit\x00This is the third commit\x00",
229 expected: []GitCommit{
230 {Hash: "abcdef1234567890", Subject: "Initial commit", Body: "This is the first commit"},
231 {Hash: "fedcba0987654321", Subject: "Second commit", Body: "This is the second commit"},
232 {Hash: "123456abcdef7890", Subject: "Third commit", Body: "This is the third commit"},
233 },
234 },
235 {
236 name: "Commit with multi-line body",
237 input: "abcdef1234567890\x00Commit with multi-line body\x00This is a commit\nwith a multi-line\nbody message\x00",
238 expected: []GitCommit{
239 {Hash: "abcdef1234567890", Subject: "Commit with multi-line body", Body: "This is a commit\nwith a multi-line\nbody message"},
240 },
241 },
242 {
243 name: "Commit with empty body",
244 input: "abcdef1234567890\x00Commit with empty body\x00\x00",
245 expected: []GitCommit{
246 {Hash: "abcdef1234567890", Subject: "Commit with empty body", Body: ""},
247 },
248 },
249 {
250 name: "Empty parts removed",
251 input: "\x00abcdef1234567890\x00Initial commit\x00This is the first commit\x00\x00",
252 expected: []GitCommit{
253 {Hash: "abcdef1234567890", Subject: "Initial commit", Body: "This is the first commit"},
254 },
255 },
256 }
257
258 for _, tt := range tests {
259 t.Run(tt.name, func(t *testing.T) {
260 actual := parseGitLog(tt.input)
261
262 if len(actual) != len(tt.expected) {
263 t.Fatalf("Expected %d commits, got %d", len(tt.expected), len(actual))
264 }
265
266 for i, commit := range actual {
267 expected := tt.expected[i]
268 if commit.Hash != expected.Hash || commit.Subject != expected.Subject || commit.Body != expected.Body {
269 t.Errorf("Commit %d doesn't match:\nExpected: %+v\nGot: %+v", i, expected, commit)
270 }
271 }
272 })
273 }
274}
Josh Bleecher Snyder715b8d92025-06-06 12:36:38 -0700275
276// TestSketchBranchWorkflow tests that the sketch-wip branch is created and used for pushes
277func TestSketchBranchWorkflow(t *testing.T) {
278 // Create a temporary directory for our test git repo
279 tempDir := t.TempDir()
280
281 // Initialize a git repo in the temp directory
282 cmd := exec.Command("git", "init")
283 cmd.Dir = tempDir
284 if err := cmd.Run(); err != nil {
285 t.Fatalf("Failed to initialize git repo: %v", err)
286 }
287
288 // Configure git user for commits
289 cmd = exec.Command("git", "config", "user.name", "Test User")
290 cmd.Dir = tempDir
291 if err := cmd.Run(); err != nil {
292 t.Fatalf("Failed to configure git user name: %v", err)
293 }
294
295 cmd = exec.Command("git", "config", "user.email", "test@example.com")
296 cmd.Dir = tempDir
297 if err := cmd.Run(); err != nil {
298 t.Fatalf("Failed to configure git user email: %v", err)
299 }
300
301 // Make an initial commit
302 testFile := filepath.Join(tempDir, "test.txt")
303 if err := os.WriteFile(testFile, []byte("initial content\n"), 0o644); err != nil {
304 t.Fatalf("Failed to write file: %v", err)
305 }
306
307 cmd = exec.Command("git", "add", "test.txt")
308 cmd.Dir = tempDir
309 if err := cmd.Run(); err != nil {
310 t.Fatalf("Failed to add file: %v", err)
311 }
312
313 cmd = exec.Command("git", "commit", "-m", "Initial commit")
314 cmd.Dir = tempDir
315 if err := cmd.Run(); err != nil {
316 t.Fatalf("Failed to create initial commit: %v", err)
317 }
318
319 // Create agent with configuration that would create sketch-wip branch
320 agent := NewAgent(AgentConfig{
321 Context: context.Background(),
322 SessionID: "test-session",
323 InDocker: true,
324 })
325 agent.workingDir = tempDir
326
327 // Simulate the branch creation that happens in Init()
328 // when a commit is specified
329 cmd = exec.Command("git", "checkout", "-b", "sketch-wip")
330 cmd.Dir = tempDir
331 if err := cmd.Run(); err != nil {
332 t.Fatalf("Failed to create sketch-wip branch: %v", err)
333 }
334
335 // Verify that we're on the sketch-wip branch
336 cmd = exec.Command("git", "branch", "--show-current")
337 cmd.Dir = tempDir
338 out, err := cmd.Output()
339 if err != nil {
340 t.Fatalf("Failed to get current branch: %v", err)
341 }
342
343 currentBranch := strings.TrimSpace(string(out))
344 if currentBranch != "sketch-wip" {
345 t.Errorf("Expected to be on 'sketch-wip' branch, but on '%s'", currentBranch)
346 }
347
348 // Make a commit on the sketch-wip branch
349 if err := os.WriteFile(testFile, []byte("updated content\n"), 0o644); err != nil {
350 t.Fatalf("Failed to update file: %v", err)
351 }
352
353 cmd = exec.Command("git", "add", "test.txt")
354 cmd.Dir = tempDir
355 if err := cmd.Run(); err != nil {
356 t.Fatalf("Failed to add updated file: %v", err)
357 }
358
359 cmd = exec.Command("git", "commit", "-m", "Update on sketch-wip branch")
360 cmd.Dir = tempDir
361 if err := cmd.Run(); err != nil {
362 t.Fatalf("Failed to create commit on sketch-wip branch: %v", err)
363 }
364
365 // Verify that the commit exists on the sketch-wip branch
366 cmd = exec.Command("git", "log", "--oneline", "-n", "1")
367 cmd.Dir = tempDir
368 out, err = cmd.Output()
369 if err != nil {
370 t.Fatalf("Failed to get git log: %v", err)
371 }
372
373 logOutput := string(out)
374 if !strings.Contains(logOutput, "Update on sketch-wip branch") {
375 t.Errorf("Expected commit 'Update on sketch-wip branch' in log, got: %s", logOutput)
376 }
377}