package service import ( "context" "io" "log/slog" "os" "os/exec" "path/filepath" "strings" "testing" "time" "gitmanager/internal/activity" "gitmanager/internal/git" "gitmanager/internal/repos" ) // TestGitActions exercises the mutating commands (commit, discard) on a throwaway // temp repo — never a real one. func TestGitActions(t *testing.T) { root := t.TempDir() repoPath := filepath.Join(root, "r") mustMkdir(t, repoPath) runGit(t, repoPath, "init", "-b", "main") runGit(t, repoPath, "config", "user.email", "t@e.com") runGit(t, repoPath, "config", "user.name", "T") writeFile(t, filepath.Join(repoPath, "a.txt"), "one\n") runGit(t, repoPath, "add", "-A") runGit(t, repoPath, "commit", "-m", "init") log := slog.New(slog.NewTextHandler(io.Discard, nil)) g := git.New("git") scanner := repos.NewScanner(g, log, []string{root}, 3, nil, time.Minute, false) scanner.Refresh(context.Background()) feed := activity.New(log, 200) svc := New(g, scanner.Index, feed, nil, scanner.RefreshRepo) ctx := context.Background() // Commit a new file, then the repo should be clean in the index. writeFile(t, filepath.Join(repoPath, "b.txt"), "two\n") if _, err := svc.GitCommit(ctx, activity.ActorUser, repoPath, "add b"); err != nil { t.Fatalf("GitCommit: %v", err) } if st, _ := svc.GetRepo(repoPath); st.Dirty { t.Fatalf("expected clean repo after commit, got dirty") } // Commit with a blank message is rejected. if _, err := svc.GitCommit(ctx, activity.ActorUser, repoPath, " "); err == nil { t.Fatalf("expected error committing with blank message") } // Modify a tracked file, then discard resets it. (We check the file itself // rather than index dirtiness, since the index only updates on a refresh.) writeFile(t, filepath.Join(repoPath, "a.txt"), "CHANGED\n") if _, err := svc.GitDiscard(ctx, activity.ActorUser, repoPath); err != nil { t.Fatalf("GitDiscard: %v", err) } if st, _ := svc.GetRepo(repoPath); st.Dirty { t.Fatalf("expected clean repo after discard") } // Trim to ignore autocrlf line-ending normalization on Windows. if got := strings.TrimSpace(readFile(t, filepath.Join(repoPath, "a.txt"))); got != "one" { t.Fatalf("a.txt = %q, want restored to \"one\"", got) } // The feed recorded the successful actions. kinds := map[string]bool{} for _, e := range feed.Events(0) { if e.Detail == "ok" { kinds[e.Kind] = true } } if !kinds["git-commit"] || !kinds["git-discard"] { t.Fatalf("expected git-commit and git-discard ok events, got %v", kinds) } } func mustMkdir(t *testing.T, p string) { t.Helper() if err := os.Mkdir(p, 0o755); err != nil { t.Fatal(err) } } func writeFile(t *testing.T, p, s string) { t.Helper() if err := os.WriteFile(p, []byte(s), 0o644); err != nil { t.Fatal(err) } } func readFile(t *testing.T, p string) string { t.Helper() b, err := os.ReadFile(p) if err != nil { t.Fatal(err) } return string(b) } func runGit(t *testing.T, dir string, args ...string) { t.Helper() cmd := exec.Command("git", args...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } }