package cmd import ( "bytes" "os" "strings" "testing" "git.membo.co.uk/dtomlinson/gitlocal/internal/config" "git.membo.co.uk/dtomlinson/gitlocal/internal/git" "git.membo.co.uk/dtomlinson/gitlocal/internal/testutil" ) // setupTestConfig creates a temporary home directory and config for testing func setupTestConfig(t *testing.T) (cleanup func()) { t.Helper() originalHome := os.Getenv("HOME") tempHome := t.TempDir() os.Setenv("HOME", tempHome) return func() { os.Setenv("HOME", originalHome) } } func TestConvertSingleRepo(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() repoDir := testutil.CreateTempGitRepoWithRemote(t, "git@github.com:test/repo.git") // Run convert command convertCmd.Flags().Set("recursive", "false") convertCmd.Flags().Set("dry-run", "false") err := runConvert(convertCmd, []string{repoDir}) if err != nil { t.Fatalf("convert command failed: %v", err) } // Verify .git was renamed to .gitlocal if git.IsGitRepo(repoDir) { t.Error("expected .git to be converted") } if !git.IsGitLocalRepo(repoDir) { t.Error("expected .gitlocal to exist") } // Verify config was updated cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 1 { t.Fatalf("expected 1 repo in config, got %d", len(cfg.Repos)) } if cfg.Repos[0].Path != repoDir { t.Errorf("expected path %s, got %s", repoDir, cfg.Repos[0].Path) } if cfg.Repos[0].OriginalRemote != "git@github.com:test/repo.git" { t.Errorf("expected remote to be saved in config, got %s", cfg.Repos[0].OriginalRemote) } } func TestConvertDryRun(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() repoDir := testutil.CreateTempGitRepo(t) // Run convert with dry-run dryRun = true defer func() { dryRun = false }() convertCmd.Flags().Set("dry-run", "true") err := runConvert(convertCmd, []string{repoDir}) if err != nil { t.Fatalf("convert dry-run failed: %v", err) } // Verify nothing was actually converted if !git.IsGitRepo(repoDir) { t.Error("expected .git to still exist (dry-run)") } if git.IsGitLocalRepo(repoDir) { t.Error("expected .gitlocal to not exist (dry-run)") } // Verify config was not updated cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 0 { t.Errorf("expected config to be empty (dry-run), got %d repos", len(cfg.Repos)) } } func TestConvertRecursive(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() rootDir, nestedDirs := testutil.CreateNestedRepoStructure(t) // Run convert recursively recursive = true defer func() { recursive = false }() convertCmd.Flags().Set("recursive", "true") err := runConvert(convertCmd, []string{rootDir}) if err != nil { t.Fatalf("convert recursive failed: %v", err) } // Verify all nested repos were converted for _, nestedDir := range nestedDirs { if git.IsGitRepo(nestedDir) { t.Errorf("expected %s to be converted", nestedDir) } if !git.IsGitLocalRepo(nestedDir) { t.Errorf("expected %s to have .gitlocal", nestedDir) } } // Verify config has all repos cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != len(nestedDirs) { t.Errorf("expected %d repos in config, got %d", len(nestedDirs), len(cfg.Repos)) } } func TestConvertAlreadyConverted(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() repoDir := testutil.CreateTempGitRepo(t) // First convert convertCmd.Flags().Set("recursive", "false") err := runConvert(convertCmd, []string{repoDir}) if err != nil { t.Fatalf("first convert failed: %v", err) } // Try to convert again - should register it err = runConvert(convertCmd, []string{repoDir}) if err != nil { t.Errorf("expected no error when re-converting, got: %v", err) } // Should still only have 1 entry in config cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 1 { t.Errorf("expected 1 repo in config, got %d", len(cfg.Repos)) } } func TestConvertNoGitRepo(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() nonGitDir := t.TempDir() err := runConvert(convertCmd, []string{nonGitDir}) if err == nil { t.Error("expected error when converting non-git directory") } if !strings.Contains(err.Error(), "no .git directory") { t.Errorf("expected 'no .git directory' error, got: %v", err) } } func TestRevertSingleRepo(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() repoDir := testutil.CreateTempGitRepo(t) // Convert first convertCmd.Flags().Set("recursive", "false") if err := runConvert(convertCmd, []string{repoDir}); err != nil { t.Fatalf("convert failed: %v", err) } // Now revert err := runRevert(revertCmd, []string{repoDir}) if err != nil { t.Fatalf("revert command failed: %v", err) } // Verify .gitlocal was renamed back to .git if !git.IsGitRepo(repoDir) { t.Error("expected .git to exist after revert") } if git.IsGitLocalRepo(repoDir) { t.Error("expected .gitlocal to be gone after revert") } // Verify config was updated cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 0 { t.Errorf("expected config to be empty after revert, got %d repos", len(cfg.Repos)) } } func TestRevertAll(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() // Create and convert multiple repos repo1 := testutil.CreateTempGitRepo(t) repo2 := testutil.CreateTempGitRepo(t) repo3 := testutil.CreateTempGitRepo(t) for _, repo := range []string{repo1, repo2, repo3} { convertCmd.Flags().Set("recursive", "false") if err := runConvert(convertCmd, []string{repo}); err != nil { t.Fatalf("convert failed for %s: %v", repo, err) } } // Verify all were converted cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 3 { t.Fatalf("expected 3 repos in config, got %d", len(cfg.Repos)) } // Revert all revertAll = true defer func() { revertAll = false }() revertCmd.Flags().Set("all", "true") err = runRevert(revertCmd, []string{}) if err != nil { t.Fatalf("revert --all failed: %v", err) } // Verify all were reverted for _, repo := range []string{repo1, repo2, repo3} { if !git.IsGitRepo(repo) { t.Errorf("expected %s to have .git after revert --all", repo) } if git.IsGitLocalRepo(repo) { t.Errorf("expected %s to not have .gitlocal after revert --all", repo) } } // Verify config is empty cfg, err = config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 0 { t.Errorf("expected config to be empty after revert --all, got %d repos", len(cfg.Repos)) } } func TestRevertNoGitLocal(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() nonGitDir := t.TempDir() err := runRevert(revertCmd, []string{nonGitDir}) if err == nil { t.Error("expected error when reverting directory without .gitlocal") } if !strings.Contains(err.Error(), "no .gitlocal directory") { t.Errorf("expected 'no .gitlocal directory' error, got: %v", err) } } func TestStatus(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() // Create and convert a repo repoDir := testutil.CreateTempGitRepoWithRemote(t, "git@github.com:test/repo.git") convertCmd.Flags().Set("recursive", "false") if err := runConvert(convertCmd, []string{repoDir}); err != nil { t.Fatalf("convert failed: %v", err) } // Capture output var buf bytes.Buffer originalStdout := os.Stdout r, w, _ := os.Pipe() os.Stdout = w err := runStatus(statusCmd, []string{}) w.Close() os.Stdout = originalStdout buf.ReadFrom(r) output := buf.String() if err != nil { t.Fatalf("status command failed: %v", err) } // Verify output contains repo info if !strings.Contains(output, repoDir) { t.Errorf("expected output to contain repo path %s", repoDir) } if !strings.Contains(output, "git@github.com:test/repo.git") { t.Error("expected output to contain remote URL") } } func TestStatusEmpty(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() // Capture output var buf bytes.Buffer originalStdout := os.Stdout r, w, _ := os.Pipe() os.Stdout = w err := runStatus(statusCmd, []string{}) w.Close() os.Stdout = originalStdout buf.ReadFrom(r) output := buf.String() if err != nil { t.Fatalf("status command failed: %v", err) } if !strings.Contains(output, "No converted repositories") { t.Error("expected 'No converted repositories' message") } } func TestConvertCurrentDirectory(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() repoDir := testutil.CreateTempGitRepo(t) // Change to repo directory originalWd, _ := os.Getwd() os.Chdir(repoDir) defer os.Chdir(originalWd) // Run convert without arguments (should use current directory) convertCmd.Flags().Set("recursive", "false") err := runConvert(convertCmd, []string{}) if err != nil { t.Fatalf("convert current directory failed: %v", err) } // Verify conversion if !git.IsGitLocalRepo(repoDir) { t.Error("expected current directory to be converted") } } func TestIntegrationFullWorkflow(t *testing.T) { cleanup := setupTestConfig(t) defer cleanup() // Create a root repo with nested repos rootDir, nestedDirs := testutil.CreateNestedRepoStructure(t) // Step 1: Convert all recursively recursive = true defer func() { recursive = false }() convertCmd.Flags().Set("recursive", "true") if err := runConvert(convertCmd, []string{rootDir}); err != nil { t.Fatalf("recursive convert failed: %v", err) } // Verify all converted for _, nestedDir := range nestedDirs { if !git.IsGitLocalRepo(nestedDir) { t.Errorf("expected %s to be converted", nestedDir) } } // Step 2: Check status cfg, err := config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != len(nestedDirs) { t.Errorf("expected %d repos in config, got %d", len(nestedDirs), len(cfg.Repos)) } // Step 3: Revert one repo revertAll = false if err := runRevert(revertCmd, []string{nestedDirs[0]}); err != nil { t.Fatalf("revert single repo failed: %v", err) } // Verify it was reverted if !git.IsGitRepo(nestedDirs[0]) { t.Error("expected reverted repo to have .git") } // Step 4: Revert all remaining revertAll = true revertCmd.Flags().Set("all", "true") if err := runRevert(revertCmd, []string{}); err != nil { t.Fatalf("revert all failed: %v", err) } // Verify all reverted for _, nestedDir := range nestedDirs { if !git.IsGitRepo(nestedDir) { t.Errorf("expected %s to be reverted", nestedDir) } } // Verify config is empty cfg, err = config.Load() if err != nil { t.Fatalf("failed to load config: %v", err) } if len(cfg.Repos) != 0 { t.Errorf("expected empty config, got %d repos", len(cfg.Repos)) } }