blob: 399943b66bccd139062d32f793dcdaea12d77ee3 [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"
11 "time"
12)
13
14// TestGitCommitTracking tests the git commit tracking functionality
15func TestGitCommitTracking(t *testing.T) {
16 // Create a temporary directory for our test git repo
17 tempDir := t.TempDir() // Automatically cleaned up when the test completes
18
19 // Initialize a git repo in the temp directory
20 cmd := exec.Command("git", "init")
21 cmd.Dir = tempDir
22 if err := cmd.Run(); err != nil {
23 t.Fatalf("Failed to initialize git repo: %v", err)
24 }
25
26 // Configure git user for commits
27 cmd = exec.Command("git", "config", "user.name", "Test User")
28 cmd.Dir = tempDir
29 if err := cmd.Run(); err != nil {
30 t.Fatalf("Failed to configure git user name: %v", err)
31 }
32
33 cmd = exec.Command("git", "config", "user.email", "test@example.com")
34 cmd.Dir = tempDir
35 if err := cmd.Run(); err != nil {
36 t.Fatalf("Failed to configure git user email: %v", err)
37 }
38
39 // Make an initial commit
40 testFile := filepath.Join(tempDir, "test.txt")
41 if err := os.WriteFile(testFile, []byte("initial content\n"), 0o644); err != nil {
42 t.Fatalf("Failed to write file: %v", err)
43 }
44
45 cmd = exec.Command("git", "add", "test.txt")
46 cmd.Dir = tempDir
47 if err := cmd.Run(); err != nil {
48 t.Fatalf("Failed to add file: %v", err)
49 }
50
51 cmd = exec.Command("git", "commit", "-m", "Initial commit")
52 cmd.Dir = tempDir
53 if err := cmd.Run(); err != nil {
54 t.Fatalf("Failed to create initial commit: %v", err)
55 }
56
57 // Get the initial commit hash
58 cmd = exec.Command("git", "rev-parse", "HEAD")
59 cmd.Dir = tempDir
60 initialCommitOutput, err := cmd.Output()
61 if err != nil {
62 t.Fatalf("Failed to get initial commit hash: %v", err)
63 }
64 initialCommit := strings.TrimSpace(string(initialCommitOutput))
65
66 // Create agent with the temp repo
67 agent := &Agent{
68 workingDir: tempDir,
69 repoRoot: tempDir, // Set repoRoot to same as workingDir for this test
70 outbox: make(chan AgentMessage, 100),
71 seenCommits: make(map[string]bool),
72 initialCommit: initialCommit,
73 }
74
75 // Make a new commit
76 if err := os.WriteFile(testFile, []byte("updated content\n"), 0o644); err != nil {
77 t.Fatalf("Failed to update file: %v", err)
78 }
79
80 cmd = exec.Command("git", "add", "test.txt")
81 cmd.Dir = tempDir
82 if err := cmd.Run(); err != nil {
83 t.Fatalf("Failed to add updated file: %v", err)
84 }
85
86 cmd = exec.Command("git", "commit", "-m", "Second commit\n\nThis commit has a multi-line message\nwith details about the changes.")
87 cmd.Dir = tempDir
88 if err := cmd.Run(); err != nil {
89 t.Fatalf("Failed to create second commit: %v", err)
90 }
91
92 // Call handleGitCommits and verify we get a commit message
93 ctx := context.Background()
94 _, err = agent.handleGitCommits(ctx)
95 if err != nil {
96 t.Fatalf("handleGitCommits failed: %v", err)
97 }
98
99 // Check if we received a commit message
100 var commitMsg AgentMessage
101 select {
102 case commitMsg = <-agent.outbox:
103 // We got a message
104 case <-time.After(500 * time.Millisecond):
105 t.Fatal("Timed out waiting for commit message")
106 }
107
108 // Verify the commit message
109 if commitMsg.Type != CommitMessageType {
110 t.Errorf("Expected message type %s, got %s", CommitMessageType, commitMsg.Type)
111 }
112
113 if len(commitMsg.Commits) < 1 {
114 t.Fatalf("Expected at least 1 commit, got %d", len(commitMsg.Commits))
115 }
116
117 // Find the second commit
118 var commit *GitCommit
119 found := false
120 for _, c := range commitMsg.Commits {
121 if strings.HasPrefix(c.Subject, "Second commit") {
122 commit = c
123 found = true
124 break
125 }
126 }
127
128 if !found {
129 t.Fatalf("Could not find 'Second commit' in commits")
130 }
131 if !strings.HasPrefix(commit.Subject, "Second commit") {
132 t.Errorf("Expected commit subject 'Second commit', got '%s'", commit.Subject)
133 }
134
135 if !strings.Contains(commit.Body, "multi-line message") {
136 t.Errorf("Expected body to contain 'multi-line message', got '%s'", commit.Body)
137 }
138
139 // Test with many commits
140 if testing.Short() {
141 t.Skip("Skipping multiple commits test in short mode")
142 }
143
144 // Make multiple commits (more than 100)
145 for i := 0; i < 110; i++ {
146 newContent := []byte(fmt.Sprintf("content update %d\n", i))
147 if err := os.WriteFile(testFile, newContent, 0o644); err != nil {
148 t.Fatalf("Failed to update file: %v", err)
149 }
150
151 cmd = exec.Command("git", "add", "test.txt")
152 cmd.Dir = tempDir
153 if err := cmd.Run(); err != nil {
154 t.Fatalf("Failed to add updated file: %v", err)
155 }
156
157 cmd = exec.Command("git", "commit", "-m", fmt.Sprintf("Commit %d", i+3))
158 cmd.Dir = tempDir
159 if err := cmd.Run(); err != nil {
160 t.Fatalf("Failed to create commit %d: %v", i+3, err)
161 }
162 }
163
164 // Reset the outbox channel and seen commits map
165 agent.outbox = make(chan AgentMessage, 100)
166 agent.seenCommits = make(map[string]bool)
167
168 // Call handleGitCommits again - it should still work but only show at most 100 commits
169 _, err = agent.handleGitCommits(ctx)
170 if err != nil {
171 t.Fatalf("handleGitCommits failed: %v", err)
172 }
173
174 // Check if we received a commit message
175 select {
176 case commitMsg = <-agent.outbox:
177 // We got a message
178 case <-time.After(500 * time.Millisecond):
179 t.Fatal("Timed out waiting for commit message")
180 }
181
182 // Should have at most 100 commits due to the -n 100 limit in git log
183 if len(commitMsg.Commits) > 100 {
184 t.Errorf("Expected at most 100 commits, got %d", len(commitMsg.Commits))
185 }
186
187 if len(commitMsg.Commits) < 50 {
188 t.Errorf("Expected at least 50 commits, but only got %d", len(commitMsg.Commits))
189 }
190
191 t.Logf("Received %d commits out of 112 total", len(commitMsg.Commits))
192}
193
194// TestParseGitLog tests the parseGitLog function
195func TestParseGitLog(t *testing.T) {
196 tests := []struct {
197 name string
198 input string
199 expected []GitCommit
200 }{
201 {
202 name: "Empty input",
203 input: "",
204 expected: []GitCommit{},
205 },
206 {
207 name: "Single commit",
208 input: "abcdef1234567890\x00Initial commit\x00This is the first commit\x00",
209 expected: []GitCommit{
210 {Hash: "abcdef1234567890", Subject: "Initial commit", Body: "This is the first commit"},
211 },
212 },
213 {
214 name: "Multiple commits",
215 input: "abcdef1234567890\x00Initial commit\x00This is the first commit\x00" +
216 "fedcba0987654321\x00Second commit\x00This is the second commit\x00" +
217 "123456abcdef7890\x00Third commit\x00This is the third commit\x00",
218 expected: []GitCommit{
219 {Hash: "abcdef1234567890", Subject: "Initial commit", Body: "This is the first commit"},
220 {Hash: "fedcba0987654321", Subject: "Second commit", Body: "This is the second commit"},
221 {Hash: "123456abcdef7890", Subject: "Third commit", Body: "This is the third commit"},
222 },
223 },
224 {
225 name: "Commit with multi-line body",
226 input: "abcdef1234567890\x00Commit with multi-line body\x00This is a commit\nwith a multi-line\nbody message\x00",
227 expected: []GitCommit{
228 {Hash: "abcdef1234567890", Subject: "Commit with multi-line body", Body: "This is a commit\nwith a multi-line\nbody message"},
229 },
230 },
231 {
232 name: "Commit with empty body",
233 input: "abcdef1234567890\x00Commit with empty body\x00\x00",
234 expected: []GitCommit{
235 {Hash: "abcdef1234567890", Subject: "Commit with empty body", Body: ""},
236 },
237 },
238 {
239 name: "Empty parts removed",
240 input: "\x00abcdef1234567890\x00Initial commit\x00This is the first commit\x00\x00",
241 expected: []GitCommit{
242 {Hash: "abcdef1234567890", Subject: "Initial commit", Body: "This is the first commit"},
243 },
244 },
245 }
246
247 for _, tt := range tests {
248 t.Run(tt.name, func(t *testing.T) {
249 actual := parseGitLog(tt.input)
250
251 if len(actual) != len(tt.expected) {
252 t.Fatalf("Expected %d commits, got %d", len(tt.expected), len(actual))
253 }
254
255 for i, commit := range actual {
256 expected := tt.expected[i]
257 if commit.Hash != expected.Hash || commit.Subject != expected.Subject || commit.Body != expected.Body {
258 t.Errorf("Commit %d doesn't match:\nExpected: %+v\nGot: %+v", i, expected, commit)
259 }
260 }
261 })
262 }
263}