//go:build integration package main import ( "os" "os/exec" "path/filepath" "strings" "testing" "github.com/git-bug/git-bug/entities/identity" "github.com/git-bug/git-bug/repository" ) // setupGitRepo creates a git repo with an origin remote for testing func setupGitRepo(t *testing.T) string { tmpDir := t.TempDir() // Initialize a git repo initCmd := exec.Command("git", "init") initCmd.Dir = tmpDir if err := initCmd.Run(); err != nil { t.Fatalf("failed to init git repo: %v", err) } // Add origin remote for bug init remoteCmd := exec.Command("git", "remote", "add", "origin", localBareRemote(t)) remoteCmd.Dir = tmpDir if err := remoteCmd.Run(); err != nil { t.Fatalf("failed to add origin remote: %v", err) } // Configure git user for the repo (needed for git operations) exec.Command("git", "-C", tmpDir, "config", "user.email", "test@example.com").Run() exec.Command("git", "-C", tmpDir, "config", "user.name", "Test User").Run() return tmpDir } // setupGitRepoWithInit creates a git repo with origin remote and runs bug init func setupGitRepoWithInit(t *testing.T) string { tmpDir := setupGitRepo(t) // Initialize git-bug if err := runInitWithReader(tmpDir, strings.NewReader("Test User\ntest@example.com\n")); err != nil { t.Fatalf("runInit failed: %v", err) } return tmpDir } // TestInitCommand_WithJJ tests init when jj config is available func TestInitCommand_WithJJ(t *testing.T) { tmpDir := setupGitRepo(t) // Create .jj directory and config file directly jjDir := filepath.Join(tmpDir, ".jj") if err := os.MkdirAll(jjDir, 0755); err != nil { t.Fatalf("failed to create .jj directory: %v", err) } // Write jj config file directly configContent := `[user] name = "JJ Test User" email = "jj-test@example.com" ` configPath := filepath.Join(jjDir, "config.toml") if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { t.Fatalf("failed to write jj config: %v", err) } // Run init err := runInit(tmpDir) if err != nil { t.Fatalf("runInit failed: %v", err) } // Verify identities were created repo, err := repository.OpenGoGitRepo(tmpDir, "", nil) if err != nil { t.Fatalf("failed to open repo: %v", err) } defer repo.Close() ids, err := identity.ListLocalIds(repo) if err != nil { t.Fatalf("ListLocalIds failed: %v", err) } // Should have at least 2 identities (user + agent) if len(ids) < 2 { t.Errorf("expected at least 2 identities, got %d", len(ids)) } // Check if user identity is set isSet, err := identity.IsUserIdentitySet(repo) if err != nil { t.Fatalf("IsUserIdentitySet failed: %v", err) } if !isSet { t.Error("expected user identity to be set") } // Verify refspecs were configured config, err := parseGitConfig(tmpDir) if err != nil { t.Fatalf("failed to parse git config: %v", err) } originSection := `remote "origin"` if _, exists := config[originSection]; !exists { t.Fatal("origin remote not found in config") } // Check fetch refspecs hasBugsFetch := false hasIdentitiesFetch := false for _, refspec := range config[originSection]["fetch"] { if refspec == "+refs/bugs/*:refs/remotes/origin/bugs/*" { hasBugsFetch = true } if refspec == "+refs/identities/*:refs/remotes/origin/identities/*" { hasIdentitiesFetch = true } } if !hasBugsFetch { t.Error("bugs fetch refspec not configured") } if !hasIdentitiesFetch { t.Error("identities fetch refspec not configured") } // Check push refspecs hasBugsPush := false hasIdentitiesPush := false for _, refspec := range config[originSection]["push"] { if refspec == "+refs/bugs/*:refs/bugs/*" { hasBugsPush = true } if refspec == "+refs/identities/*:refs/identities/*" { hasIdentitiesPush = true } } if !hasBugsPush { t.Error("bugs push refspec not configured") } if !hasIdentitiesPush { t.Error("identities push refspec not configured") } } // TestInitCommand_WithoutJJ tests init without jj config (interactive mode) func TestInitCommand_WithoutJJ(t *testing.T) { tmpDir := setupGitRepo(t) // Simulate user input for interactive mode input := "Interactive User\ninteractive@example.com\n" // Run init with simulated input err := runInitWithReader(tmpDir, strings.NewReader(input)) if err != nil { t.Fatalf("runInitWithReader failed: %v", err) } // Verify identities were created repo, err := repository.OpenGoGitRepo(tmpDir, "", nil) if err != nil { t.Fatalf("failed to open repo: %v", err) } defer repo.Close() ids, err := identity.ListLocalIds(repo) if err != nil { t.Fatalf("ListLocalIds failed: %v", err) } // Should have at least 2 identities (user + agent) if len(ids) < 2 { t.Errorf("expected at least 2 identities, got %d", len(ids)) } // Verify the user identity has the correct info isSet, err := identity.IsUserIdentitySet(repo) if err != nil { t.Fatalf("IsUserIdentitySet failed: %v", err) } if !isSet { t.Error("expected user identity to be set") } userIdentity, err := identity.GetUserIdentity(repo) if err != nil { t.Fatalf("GetUserIdentity failed: %v", err) } if userIdentity.Name() != "Interactive User" { t.Errorf("expected name 'Interactive User', got %q", userIdentity.Name()) } if userIdentity.Email() != "interactive@example.com" { t.Errorf("expected email 'interactive@example.com', got %q", userIdentity.Email()) } } // TestInitCommand_EmptyNameError tests that empty name is rejected func TestInitCommand_EmptyNameError(t *testing.T) { tmpDir := setupGitRepo(t) // Simulate empty name input input := "\n" err := runInitWithReader(tmpDir, strings.NewReader(input)) if err == nil { t.Error("expected error for empty name, got nil") } if !strings.Contains(err.Error(), "name cannot be empty") { t.Errorf("expected 'name cannot be empty' error, got: %v", err) } } // TestInitCommand_NoOriginRemote tests that init fails without origin remote func TestInitCommand_NoOriginRemote(t *testing.T) { tmpDir := t.TempDir() // Initialize a git repo WITHOUT origin remote initCmd := exec.Command("git", "init") initCmd.Dir = tmpDir if err := initCmd.Run(); err != nil { t.Fatalf("failed to init git repo: %v", err) } // Configure git user exec.Command("git", "-C", tmpDir, "config", "user.email", "test@example.com").Run() exec.Command("git", "-C", tmpDir, "config", "user.name", "Test User").Run() // Create jj config to avoid interactive mode jjDir := filepath.Join(tmpDir, ".jj") if err := os.MkdirAll(jjDir, 0755); err != nil { t.Fatalf("failed to create .jj directory: %v", err) } configContent := `[user] name = "JJ Test User" email = "jj-test@example.com" ` configPath := filepath.Join(jjDir, "config.toml") if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { t.Fatalf("failed to write jj config: %v", err) } // Run init - should fail with ErrNoOriginRemote err := runInit(tmpDir) if err == nil { t.Fatal("expected error when no origin remote, got nil") } // Check that it's the specific ErrNoOriginRemote type if _, ok := err.(ErrNoOriginRemote); !ok { t.Errorf("expected ErrNoOriginRemote, got: %T - %v", err, err) } } // TestInitCommand_IdempotentIdentities tests that running init twice doesn't duplicate identities func TestInitCommand_IdempotentIdentities(t *testing.T) { tmpDir := setupGitRepo(t) // Create jj config jjDir := filepath.Join(tmpDir, ".jj") if err := os.MkdirAll(jjDir, 0755); err != nil { t.Fatalf("failed to create .jj directory: %v", err) } configContent := `[user] name = "JJ Test User" email = "jj-test@example.com" ` configPath := filepath.Join(jjDir, "config.toml") if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { t.Fatalf("failed to write jj config: %v", err) } // Run init first time err := runInit(tmpDir) if err != nil { t.Fatalf("first runInit failed: %v", err) } // Count identities after first run repo, err := repository.OpenGoGitRepo(tmpDir, "", nil) if err != nil { t.Fatalf("failed to open repo: %v", err) } ids1, err := identity.ListLocalIds(repo) if err != nil { t.Fatalf("ListLocalIds failed: %v", err) } repo.Close() // Run init second time err = runInit(tmpDir) if err != nil { t.Fatalf("second runInit failed: %v", err) } // Count identities after second run repo, err = repository.OpenGoGitRepo(tmpDir, "", nil) if err != nil { t.Fatalf("failed to open repo: %v", err) } defer repo.Close() ids2, err := identity.ListLocalIds(repo) if err != nil { t.Fatalf("ListLocalIds failed: %v", err) } // Should have same number of identities if len(ids1) != len(ids2) { t.Errorf("identities count changed after second init: first=%d, second=%d", len(ids1), len(ids2)) } // Verify identities are the same idMap := make(map[string]bool) for _, id := range ids1 { idMap[id.String()] = true } for _, id := range ids2 { if !idMap[id.String()] { t.Errorf("new identity found after second init: %s", id.String()) } } } // TestInitCommand_IdempotentRefspecs tests that running init twice doesn't duplicate refspecs func TestInitCommand_IdempotentRefspecs(t *testing.T) { tmpDir := setupGitRepo(t) // Create jj config jjDir := filepath.Join(tmpDir, ".jj") if err := os.MkdirAll(jjDir, 0755); err != nil { t.Fatalf("failed to create .jj directory: %v", err) } configContent := `[user] name = "JJ Test User" email = "jj-test@example.com" ` configPath := filepath.Join(jjDir, "config.toml") if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { t.Fatalf("failed to write jj config: %v", err) } // Run init first time err := runInit(tmpDir) if err != nil { t.Fatalf("first runInit failed: %v", err) } // Get refspec count after first run config1, err := parseGitConfig(tmpDir) if err != nil { t.Fatalf("failed to parse git config: %v", err) } originSection := `remote "origin"` fetchCount1 := len(config1[originSection]["fetch"]) pushCount1 := len(config1[originSection]["push"]) // Run init second time err = runInit(tmpDir) if err != nil { t.Fatalf("second runInit failed: %v", err) } // Get refspec count after second run config2, err := parseGitConfig(tmpDir) if err != nil { t.Fatalf("failed to parse git config: %v", err) } fetchCount2 := len(config2[originSection]["fetch"]) pushCount2 := len(config2[originSection]["push"]) // Should have same number of refspecs if fetchCount1 != fetchCount2 { t.Errorf("fetch refspecs count changed: first=%d, second=%d", fetchCount1, fetchCount2) } if pushCount1 != pushCount2 { t.Errorf("push refspecs count changed: first=%d, second=%d", pushCount1, pushCount2) } }