package services import ( "os" "path/filepath" "testing" "time" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" ) // initTestRepo creates a temp git repo with two commits on main branch. // Returns the repo path and the two commit hashes (first, second). func initTestRepo(t *testing.T) (string, string, string) { t.Helper() dir := t.TempDir() repo, err := git.PlainInit(dir, false) if err != nil { t.Fatalf("PlainInit: %v", err) } w, err := repo.Worktree() if err != nil { t.Fatalf("Worktree: %v", err) } // Commit 1: create file filePath := filepath.Join(dir, "hello.txt") if err := os.WriteFile(filePath, []byte("hello world\n"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } w.Add("hello.txt") hash1, err := w.Commit("initial commit", &git.CommitOptions{ Author: &object.Signature{ Name: "Test", Email: "test@example.com", When: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), }, }) if err != nil { t.Fatalf("Commit 1: %v", err) } // Commit 2: modify file if err := os.WriteFile(filePath, []byte("hello world\nmodified\n"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } w.Add("hello.txt") hash2, err := w.Commit("second commit", &git.CommitOptions{ Author: &object.Signature{ Name: "Test", Email: "test@example.com", When: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC), }, }) if err != nil { t.Fatalf("Commit 2: %v", err) } return dir, hash1.String(), hash2.String() } func TestOpenRepo(t *testing.T) { dir, _, _ := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo() error: %v", err) } if repo == nil { t.Fatal("OpenRepo() returned nil repo") } } func TestOpenRepo_InvalidPath(t *testing.T) { _, err := OpenRepo("/nonexistent/path/12345") if err == nil { t.Error("OpenRepo() expected error for invalid path") } } func TestGetRefs(t *testing.T) { dir, _, _ := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } refs, err := GetRefs(repo) if err != nil { t.Fatalf("GetRefs() error: %v", err) } // Should have at least one branch (main or master depending on git config) found := false for _, r := range refs { if (r.Name == "main" || r.Name == "master") && !r.IsTag { found = true if r.Hash == "" { t.Error("branch ref has empty hash") } if !r.IsHead { t.Error("default branch should be HEAD") } } } if !found { t.Errorf("expected default branch in refs, got %v", refs) } } func TestGetGraph(t *testing.T) { dir, hash1, hash2 := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } graph, err := GetGraph(repo, 100) if err != nil { t.Fatalf("GetGraph() error: %v", err) } if len(graph.Commits) != 2 { t.Fatalf("commits count = %d, want 2", len(graph.Commits)) } // The first commit in the graph should be the latest (hash2) if graph.Commits[0].Hash != hash2 { t.Errorf("commits[0].Hash = %q, want %q", graph.Commits[0].Hash, hash2) } if graph.Commits[0].Message != "second commit" { t.Errorf("commits[0].Message = %q, want %q", graph.Commits[0].Message, "second commit") } // Second should be hash1 if graph.Commits[1].Hash != hash1 { t.Errorf("commits[1].Hash = %q, want %q", graph.Commits[1].Hash, hash1) } // Should have one edge (hash2 -> hash1) if len(graph.Edges) != 1 { t.Fatalf("edges count = %d, want 1", len(graph.Edges)) } if graph.Edges[0].Source != hash2 || graph.Edges[0].Target != hash1 { t.Errorf("edge = {%s -> %s}, want {%s -> %s}", graph.Edges[0].Source, graph.Edges[0].Target, hash2, hash1) } // Should have at least one ref if len(graph.Refs) < 1 { t.Error("expected at least 1 ref in graph") } } func TestGetGraph_MaxCommits(t *testing.T) { dir, _, _ := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } graph, err := GetGraph(repo, 1) // limit to 1 commit if err != nil { t.Fatalf("GetGraph() error: %v", err) } if len(graph.Commits) != 1 { t.Errorf("commits count = %d, want 1", len(graph.Commits)) } } func TestGetDiff(t *testing.T) { dir, hash1, hash2 := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } diff, err := GetDiff(repo, hash1, hash2) if err != nil { t.Fatalf("GetDiff() error: %v", err) } if diff == "" { t.Error("expected non-empty diff") } // The diff should contain the added line if !contains(diff, "modified") { t.Errorf("diff should contain 'modified', got: %s", diff) } } func TestGetDiffFiles(t *testing.T) { dir, hash1, hash2 := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } files, err := GetDiffFiles(repo, hash1, hash2) if err != nil { t.Fatalf("GetDiffFiles() error: %v", err) } if len(files) != 1 { t.Fatalf("files count = %d, want 1", len(files)) } if files[0].Filename != "hello.txt" { t.Errorf("filename = %q, want %q", files[0].Filename, "hello.txt") } if files[0].Patch == "" { t.Error("expected non-empty patch") } } func TestGetCommitLog(t *testing.T) { dir, hash1, hash2 := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } commits, err := GetCommitLog(repo, "HEAD", 100) if err != nil { t.Fatalf("GetCommitLog() error: %v", err) } if len(commits) != 2 { t.Fatalf("commits count = %d, want 2", len(commits)) } // First commit should be the latest if commits[0].Hash != hash2 { t.Errorf("commits[0].Hash = %q, want %q", commits[0].Hash, hash2) } if commits[0].Author != "Test" { t.Errorf("commits[0].Author = %q, want %q", commits[0].Author, "Test") } if commits[0].Email != "test@example.com" { t.Errorf("commits[0].Email = %q, want %q", commits[0].Email, "test@example.com") } if commits[1].Hash != hash1 { t.Errorf("commits[1].Hash = %q, want %q", commits[1].Hash, hash1) } } func TestGetCommitLog_Limit(t *testing.T) { dir, _, _ := initTestRepo(t) repo, err := OpenRepo(dir) if err != nil { t.Fatalf("OpenRepo: %v", err) } commits, err := GetCommitLog(repo, "HEAD", 1) if err != nil { t.Fatalf("GetCommitLog() error: %v", err) } if len(commits) != 1 { t.Errorf("commits count = %d, want 1", len(commits)) } } func TestGetDefaultBranch(t *testing.T) { dir, _, _ := initTestRepo(t) branch, err := GetDefaultBranch(dir) if err != nil { t.Fatalf("GetDefaultBranch() error: %v", err) } if branch != "main" && branch != "master" { t.Errorf("GetDefaultBranch() = %q, want %q or %q", branch, "main", "master") } } func TestGetDefaultBranch_InvalidPath(t *testing.T) { _, err := GetDefaultBranch("/nonexistent/path/12345") if err == nil { t.Error("expected error for invalid path") } } func TestGetRepoBranches(t *testing.T) { dir, _, _ := initTestRepo(t) branches, err := GetRepoBranches(dir) if err != nil { t.Fatalf("GetRepoBranches() error: %v", err) } found := false for _, b := range branches { if b == "main" || b == "master" { found = true } } if !found { t.Errorf("expected default branch in branches, got %v", branches) } } func TestGetRepoTags(t *testing.T) { dir, _, _ := initTestRepo(t) tags, err := GetRepoTags(dir) if err != nil { t.Fatalf("GetRepoTags() error: %v", err) } // No tags were created, should be empty if len(tags) != 0 { t.Errorf("expected 0 tags, got %v", tags) } } func TestDirSize(t *testing.T) { tmpDir := t.TempDir() // Create files with known sizes os.WriteFile(filepath.Join(tmpDir, "a.txt"), make([]byte, 100), 0o644) os.WriteFile(filepath.Join(tmpDir, "b.txt"), make([]byte, 200), 0o644) size := dirSize(tmpDir) if size < 300 { t.Errorf("dirSize() = %d, want >= 300", size) } } func TestDirSize_Empty(t *testing.T) { tmpDir := t.TempDir() size := dirSize(tmpDir) if size != 0 { t.Errorf("dirSize() = %d, want 0", size) } } func TestDirSize_Nonexistent(t *testing.T) { size := dirSize("/nonexistent/path/12345") if size != 0 { t.Errorf("dirSize() = %d, want 0", size) } } func TestDirSize_NestedFiles(t *testing.T) { tmpDir := t.TempDir() subDir := filepath.Join(tmpDir, "sub") os.MkdirAll(subDir, 0o755) os.WriteFile(filepath.Join(tmpDir, "root.txt"), make([]byte, 50), 0o644) os.WriteFile(filepath.Join(subDir, "nested.txt"), make([]byte, 75), 0o644) size := dirSize(tmpDir) if size < 125 { t.Errorf("dirSize() = %d, want >= 125", size) } } func TestCompareCommits(t *testing.T) { dir, hash1, hash2 := initTestRepo(t) diff, err := CompareCommits(dir, hash1, hash2) if err != nil { t.Fatalf("CompareCommits() error: %v", err) } if diff == "" { t.Error("expected non-empty diff") } } func TestCompareCommitsFiles(t *testing.T) { dir, hash1, hash2 := initTestRepo(t) files, err := CompareCommitsFiles(dir, hash1, hash2) if err != nil { t.Fatalf("CompareCommitsFiles() error: %v", err) } if len(files) != 1 { t.Fatalf("files count = %d, want 1", len(files)) } if files[0].Filename != "hello.txt" { t.Errorf("filename = %q, want %q", files[0].Filename, "hello.txt") } } func TestGetBranchCommits(t *testing.T) { dir, _, _ := initTestRepo(t) // Use the actual default branch name (main or master depending on git config) branch, err := GetDefaultBranch(dir) if err != nil { t.Fatalf("GetDefaultBranch: %v", err) } commits, err := GetBranchCommits(dir, branch, 100) if err != nil { t.Fatalf("GetBranchCommits() error: %v", err) } if len(commits) != 2 { t.Errorf("commits count = %d, want 2", len(commits)) } } // contains is a simple helper to check substring presence. func contains(s, substr string) bool { return len(s) >= len(substr) && searchString(s, substr) } func searchString(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }