scooter  ·  2026-04-13

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}