init_integration_test.go
1//go:build integration
2
3package main
4
5import (
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "github.com/git-bug/git-bug/entities/identity"
13 "github.com/git-bug/git-bug/repository"
14)
15
16// setupGitRepo creates a git repo with an origin remote for testing
17func setupGitRepo(t *testing.T) string {
18 tmpDir := t.TempDir()
19
20 // Initialize a git repo
21 initCmd := exec.Command("git", "init")
22 initCmd.Dir = tmpDir
23 if err := initCmd.Run(); err != nil {
24 t.Fatalf("failed to init git repo: %v", err)
25 }
26 // Add origin remote for bug init
27 remoteCmd := exec.Command("git", "remote", "add", "origin", "[email protected]:test/repo.git")
28 remoteCmd.Dir = tmpDir
29 if err := remoteCmd.Run(); err != nil {
30 t.Fatalf("failed to add origin remote: %v", err)
31 }
32
33 // Configure git user for the repo (needed for git operations)
34 exec.Command("git", "-C", tmpDir, "config", "user.email", "[email protected]").Run()
35 exec.Command("git", "-C", tmpDir, "config", "user.name", "Test User").Run()
36
37 return tmpDir
38}
39
40// setupGitRepoWithInit creates a git repo with origin remote and runs bug init
41func setupGitRepoWithInit(t *testing.T) string {
42 tmpDir := setupGitRepo(t)
43
44 // Initialize git-bug
45 if err := runInitWithReader(tmpDir, strings.NewReader("Test User\[email protected]\n")); err != nil {
46 t.Fatalf("runInit failed: %v", err)
47 }
48
49 return tmpDir
50}
51
52// TestInitCommand_WithJJ tests init when jj config is available
53func TestInitCommand_WithJJ(t *testing.T) {
54 tmpDir := setupGitRepo(t)
55
56 // Create .jj directory and config file directly
57 jjDir := filepath.Join(tmpDir, ".jj")
58 if err := os.MkdirAll(jjDir, 0755); err != nil {
59 t.Fatalf("failed to create .jj directory: %v", err)
60 }
61
62 // Write jj config file directly
63 configContent := `[user]
64name = "JJ Test User"
65email = "[email protected]"
66`
67 configPath := filepath.Join(jjDir, "config.toml")
68 if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
69 t.Fatalf("failed to write jj config: %v", err)
70 }
71
72 // Run init
73 err := runInit(tmpDir)
74 if err != nil {
75 t.Fatalf("runInit failed: %v", err)
76 }
77
78 // Verify identities were created
79 repo, err := repository.OpenGoGitRepo(tmpDir, "", nil)
80 if err != nil {
81 t.Fatalf("failed to open repo: %v", err)
82 }
83 defer repo.Close()
84
85 ids, err := identity.ListLocalIds(repo)
86 if err != nil {
87 t.Fatalf("ListLocalIds failed: %v", err)
88 }
89
90 // Should have at least 2 identities (user + agent)
91 if len(ids) < 2 {
92 t.Errorf("expected at least 2 identities, got %d", len(ids))
93 }
94
95 // Check if user identity is set
96 isSet, err := identity.IsUserIdentitySet(repo)
97 if err != nil {
98 t.Fatalf("IsUserIdentitySet failed: %v", err)
99 }
100 if !isSet {
101 t.Error("expected user identity to be set")
102 }
103
104 // Verify refspecs were configured
105 config, err := parseGitConfig(tmpDir)
106 if err != nil {
107 t.Fatalf("failed to parse git config: %v", err)
108 }
109
110 originSection := `remote "origin"`
111 if _, exists := config[originSection]; !exists {
112 t.Fatal("origin remote not found in config")
113 }
114
115 // Check fetch refspecs
116 hasBugsFetch := false
117 hasIdentitiesFetch := false
118 for _, refspec := range config[originSection]["fetch"] {
119 if refspec == "+refs/bugs/*:refs/remotes/origin/bugs/*" {
120 hasBugsFetch = true
121 }
122 if refspec == "+refs/identities/*:refs/remotes/origin/identities/*" {
123 hasIdentitiesFetch = true
124 }
125 }
126 if !hasBugsFetch {
127 t.Error("bugs fetch refspec not configured")
128 }
129 if !hasIdentitiesFetch {
130 t.Error("identities fetch refspec not configured")
131 }
132
133 // Check push refspecs
134 hasBugsPush := false
135 hasIdentitiesPush := false
136 for _, refspec := range config[originSection]["push"] {
137 if refspec == "+refs/bugs/*:refs/bugs/*" {
138 hasBugsPush = true
139 }
140 if refspec == "+refs/identities/*:refs/identities/*" {
141 hasIdentitiesPush = true
142 }
143 }
144 if !hasBugsPush {
145 t.Error("bugs push refspec not configured")
146 }
147 if !hasIdentitiesPush {
148 t.Error("identities push refspec not configured")
149 }
150}
151
152// TestInitCommand_WithoutJJ tests init without jj config (interactive mode)
153func TestInitCommand_WithoutJJ(t *testing.T) {
154 tmpDir := setupGitRepo(t)
155
156 // Simulate user input for interactive mode
157 input := "Interactive User\[email protected]\n"
158
159 // Run init with simulated input
160 err := runInitWithReader(tmpDir, strings.NewReader(input))
161 if err != nil {
162 t.Fatalf("runInitWithReader failed: %v", err)
163 }
164
165 // Verify identities were created
166 repo, err := repository.OpenGoGitRepo(tmpDir, "", nil)
167 if err != nil {
168 t.Fatalf("failed to open repo: %v", err)
169 }
170 defer repo.Close()
171
172 ids, err := identity.ListLocalIds(repo)
173 if err != nil {
174 t.Fatalf("ListLocalIds failed: %v", err)
175 }
176
177 // Should have at least 2 identities (user + agent)
178 if len(ids) < 2 {
179 t.Errorf("expected at least 2 identities, got %d", len(ids))
180 }
181
182 // Verify the user identity has the correct info
183 isSet, err := identity.IsUserIdentitySet(repo)
184 if err != nil {
185 t.Fatalf("IsUserIdentitySet failed: %v", err)
186 }
187 if !isSet {
188 t.Error("expected user identity to be set")
189 }
190
191 userIdentity, err := identity.GetUserIdentity(repo)
192 if err != nil {
193 t.Fatalf("GetUserIdentity failed: %v", err)
194 }
195
196 if userIdentity.Name() != "Interactive User" {
197 t.Errorf("expected name 'Interactive User', got %q", userIdentity.Name())
198 }
199
200 if userIdentity.Email() != "[email protected]" {
201 t.Errorf("expected email '[email protected]', got %q", userIdentity.Email())
202 }
203}
204
205// TestInitCommand_EmptyNameError tests that empty name is rejected
206func TestInitCommand_EmptyNameError(t *testing.T) {
207 tmpDir := setupGitRepo(t)
208
209 // Simulate empty name input
210 input := "\n"
211
212 err := runInitWithReader(tmpDir, strings.NewReader(input))
213 if err == nil {
214 t.Error("expected error for empty name, got nil")
215 }
216
217 if !strings.Contains(err.Error(), "name cannot be empty") {
218 t.Errorf("expected 'name cannot be empty' error, got: %v", err)
219 }
220}
221
222// TestInitCommand_NoOriginRemote tests that init fails without origin remote
223func TestInitCommand_NoOriginRemote(t *testing.T) {
224 tmpDir := t.TempDir()
225
226 // Initialize a git repo WITHOUT origin remote
227 initCmd := exec.Command("git", "init")
228 initCmd.Dir = tmpDir
229 if err := initCmd.Run(); err != nil {
230 t.Fatalf("failed to init git repo: %v", err)
231 }
232
233 // Configure git user
234 exec.Command("git", "-C", tmpDir, "config", "user.email", "[email protected]").Run()
235 exec.Command("git", "-C", tmpDir, "config", "user.name", "Test User").Run()
236
237 // Create jj config to avoid interactive mode
238 jjDir := filepath.Join(tmpDir, ".jj")
239 if err := os.MkdirAll(jjDir, 0755); err != nil {
240 t.Fatalf("failed to create .jj directory: %v", err)
241 }
242 configContent := `[user]
243name = "JJ Test User"
244email = "[email protected]"
245`
246 configPath := filepath.Join(jjDir, "config.toml")
247 if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
248 t.Fatalf("failed to write jj config: %v", err)
249 }
250
251 // Run init - should fail with ErrNoOriginRemote
252 err := runInit(tmpDir)
253 if err == nil {
254 t.Fatal("expected error when no origin remote, got nil")
255 }
256
257 // Check that it's the specific ErrNoOriginRemote type
258 if _, ok := err.(ErrNoOriginRemote); !ok {
259 t.Errorf("expected ErrNoOriginRemote, got: %T - %v", err, err)
260 }
261}
262
263// TestInitCommand_IdempotentIdentities tests that running init twice doesn't duplicate identities
264func TestInitCommand_IdempotentIdentities(t *testing.T) {
265 tmpDir := setupGitRepo(t)
266
267 // Create jj config
268 jjDir := filepath.Join(tmpDir, ".jj")
269 if err := os.MkdirAll(jjDir, 0755); err != nil {
270 t.Fatalf("failed to create .jj directory: %v", err)
271 }
272 configContent := `[user]
273name = "JJ Test User"
274email = "[email protected]"
275`
276 configPath := filepath.Join(jjDir, "config.toml")
277 if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
278 t.Fatalf("failed to write jj config: %v", err)
279 }
280
281 // Run init first time
282 err := runInit(tmpDir)
283 if err != nil {
284 t.Fatalf("first runInit failed: %v", err)
285 }
286
287 // Count identities after first run
288 repo, err := repository.OpenGoGitRepo(tmpDir, "", nil)
289 if err != nil {
290 t.Fatalf("failed to open repo: %v", err)
291 }
292
293 ids1, err := identity.ListLocalIds(repo)
294 if err != nil {
295 t.Fatalf("ListLocalIds failed: %v", err)
296 }
297 repo.Close()
298
299 // Run init second time
300 err = runInit(tmpDir)
301 if err != nil {
302 t.Fatalf("second runInit failed: %v", err)
303 }
304
305 // Count identities after second run
306 repo, err = repository.OpenGoGitRepo(tmpDir, "", nil)
307 if err != nil {
308 t.Fatalf("failed to open repo: %v", err)
309 }
310 defer repo.Close()
311
312 ids2, err := identity.ListLocalIds(repo)
313 if err != nil {
314 t.Fatalf("ListLocalIds failed: %v", err)
315 }
316
317 // Should have same number of identities
318 if len(ids1) != len(ids2) {
319 t.Errorf("identities count changed after second init: first=%d, second=%d", len(ids1), len(ids2))
320 }
321
322 // Verify identities are the same
323 idMap := make(map[string]bool)
324 for _, id := range ids1 {
325 idMap[id.String()] = true
326 }
327 for _, id := range ids2 {
328 if !idMap[id.String()] {
329 t.Errorf("new identity found after second init: %s", id.String())
330 }
331 }
332}
333
334// TestInitCommand_IdempotentRefspecs tests that running init twice doesn't duplicate refspecs
335func TestInitCommand_IdempotentRefspecs(t *testing.T) {
336 tmpDir := setupGitRepo(t)
337
338 // Create jj config
339 jjDir := filepath.Join(tmpDir, ".jj")
340 if err := os.MkdirAll(jjDir, 0755); err != nil {
341 t.Fatalf("failed to create .jj directory: %v", err)
342 }
343 configContent := `[user]
344name = "JJ Test User"
345email = "[email protected]"
346`
347 configPath := filepath.Join(jjDir, "config.toml")
348 if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
349 t.Fatalf("failed to write jj config: %v", err)
350 }
351
352 // Run init first time
353 err := runInit(tmpDir)
354 if err != nil {
355 t.Fatalf("first runInit failed: %v", err)
356 }
357
358 // Get refspec count after first run
359 config1, err := parseGitConfig(tmpDir)
360 if err != nil {
361 t.Fatalf("failed to parse git config: %v", err)
362 }
363
364 originSection := `remote "origin"`
365 fetchCount1 := len(config1[originSection]["fetch"])
366 pushCount1 := len(config1[originSection]["push"])
367
368 // Run init second time
369 err = runInit(tmpDir)
370 if err != nil {
371 t.Fatalf("second runInit failed: %v", err)
372 }
373
374 // Get refspec count after second run
375 config2, err := parseGitConfig(tmpDir)
376 if err != nil {
377 t.Fatalf("failed to parse git config: %v", err)
378 }
379
380 fetchCount2 := len(config2[originSection]["fetch"])
381 pushCount2 := len(config2[originSection]["push"])
382
383 // Should have same number of refspecs
384 if fetchCount1 != fetchCount2 {
385 t.Errorf("fetch refspecs count changed: first=%d, second=%d", fetchCount1, fetchCount2)
386 }
387 if pushCount1 != pushCount2 {
388 t.Errorf("push refspecs count changed: first=%d, second=%d", pushCount1, pushCount2)
389 }
390}