Improve internal/beads test coverage from 48% to 80%
Add comprehensive tests for fingerprint.go: - canonicalizeGitURL: URL normalization for HTTPS, SSH, SCP-style, HTTP, git protocol - ComputeRepoID: With/without remote, not-git-repo, canonical URL consistency - GetCloneID: Basic, different dirs, worktree, hostname inclusion Add tests for beads.go: - findDatabaseInBeadsDir: Direct tests for database discovery - FindAllDatabases: Multi-database discovery, no-database, stops-at-first Coverage: 48.4% → 80.2% (bd-tvu3) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1427,6 +1427,237 @@ func TestIsWispDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFindDatabaseInBeadsDir tests the database discovery within a .beads directory
|
||||
func TestFindDatabaseInBeadsDir(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
files []string
|
||||
configJSON string
|
||||
expectDB string
|
||||
warnOnIssues bool
|
||||
}{
|
||||
{
|
||||
name: "canonical beads.db only",
|
||||
files: []string{"beads.db"},
|
||||
expectDB: "beads.db",
|
||||
},
|
||||
{
|
||||
name: "legacy bd.db only",
|
||||
files: []string{"bd.db"},
|
||||
expectDB: "bd.db",
|
||||
},
|
||||
{
|
||||
name: "prefers beads.db over other db files",
|
||||
files: []string{"custom.db", "beads.db", "other.db"},
|
||||
expectDB: "beads.db",
|
||||
},
|
||||
{
|
||||
name: "skips backup files",
|
||||
files: []string{"beads.backup.db", "real.db"},
|
||||
expectDB: "real.db",
|
||||
},
|
||||
{
|
||||
name: "skips vc.db",
|
||||
files: []string{"vc.db", "beads.db"},
|
||||
expectDB: "beads.db",
|
||||
},
|
||||
{
|
||||
name: "no db files returns empty",
|
||||
files: []string{"readme.txt", "config.yaml"},
|
||||
expectDB: "",
|
||||
},
|
||||
{
|
||||
name: "only backup files returns empty",
|
||||
files: []string{"beads.backup.db", "vc.db"},
|
||||
expectDB: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "beads-findindir-test-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create test files
|
||||
for _, file := range tt.files {
|
||||
path := filepath.Join(tmpDir, file)
|
||||
if err := os.WriteFile(path, []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write config.json if specified
|
||||
if tt.configJSON != "" {
|
||||
configPath := filepath.Join(tmpDir, "config.json")
|
||||
if err := os.WriteFile(configPath, []byte(tt.configJSON), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
result := findDatabaseInBeadsDir(tmpDir, tt.warnOnIssues)
|
||||
|
||||
if tt.expectDB == "" {
|
||||
if result != "" {
|
||||
t.Errorf("findDatabaseInBeadsDir() = %q, want empty string", result)
|
||||
}
|
||||
} else {
|
||||
expected := filepath.Join(tmpDir, tt.expectDB)
|
||||
if result != expected {
|
||||
t.Errorf("findDatabaseInBeadsDir() = %q, want %q", result, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFindAllDatabases tests the multi-database discovery
|
||||
func TestFindAllDatabases(t *testing.T) {
|
||||
// Save original state
|
||||
originalEnv := os.Getenv("BEADS_DIR")
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DIR", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DIR")
|
||||
}
|
||||
}()
|
||||
os.Unsetenv("BEADS_DIR")
|
||||
|
||||
// Create temp directory structure
|
||||
tmpDir, err := os.MkdirTemp("", "beads-findall-test-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create .beads directory with database
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dbPath := filepath.Join(beadsDir, "beads.db")
|
||||
if err := os.WriteFile(dbPath, []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create subdirectory and change to it
|
||||
subDir := filepath.Join(tmpDir, "sub", "nested")
|
||||
if err := os.MkdirAll(subDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Chdir(subDir)
|
||||
|
||||
// FindAllDatabases should find the parent .beads
|
||||
result := FindAllDatabases()
|
||||
|
||||
if len(result) == 0 {
|
||||
t.Error("FindAllDatabases() returned empty slice, expected at least one database")
|
||||
} else {
|
||||
// Verify the path matches
|
||||
resultResolved, _ := filepath.EvalSymlinks(result[0].Path)
|
||||
dbPathResolved, _ := filepath.EvalSymlinks(dbPath)
|
||||
if resultResolved != dbPathResolved {
|
||||
t.Errorf("FindAllDatabases()[0].Path = %q, want %q", result[0].Path, dbPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFindAllDatabases_NoDatabase tests FindAllDatabases when no database exists
|
||||
func TestFindAllDatabases_NoDatabase(t *testing.T) {
|
||||
// Save original state
|
||||
originalEnv := os.Getenv("BEADS_DIR")
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DIR", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DIR")
|
||||
}
|
||||
}()
|
||||
os.Unsetenv("BEADS_DIR")
|
||||
|
||||
// Create temp directory without .beads
|
||||
tmpDir, err := os.MkdirTemp("", "beads-findall-nodb-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// FindAllDatabases should return empty slice (not nil)
|
||||
result := FindAllDatabases()
|
||||
|
||||
if result == nil {
|
||||
t.Error("FindAllDatabases() returned nil, expected empty slice")
|
||||
}
|
||||
if len(result) != 0 {
|
||||
t.Errorf("FindAllDatabases() returned %d databases, expected 0", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
// TestFindAllDatabases_StopsAtFirst tests that FindAllDatabases stops at first .beads found
|
||||
func TestFindAllDatabases_StopsAtFirst(t *testing.T) {
|
||||
// Save original state
|
||||
originalEnv := os.Getenv("BEADS_DIR")
|
||||
defer func() {
|
||||
if originalEnv != "" {
|
||||
os.Setenv("BEADS_DIR", originalEnv)
|
||||
} else {
|
||||
os.Unsetenv("BEADS_DIR")
|
||||
}
|
||||
}()
|
||||
os.Unsetenv("BEADS_DIR")
|
||||
|
||||
// Create temp directory structure with nested .beads dirs
|
||||
tmpDir, err := os.MkdirTemp("", "beads-findall-nested-*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create parent .beads
|
||||
parentBeadsDir := filepath.Join(tmpDir, ".beads")
|
||||
if err := os.MkdirAll(parentBeadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(parentBeadsDir, "beads.db"), []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create child project with its own .beads
|
||||
childDir := filepath.Join(tmpDir, "child")
|
||||
childBeadsDir := filepath.Join(childDir, ".beads")
|
||||
if err := os.MkdirAll(childBeadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
childDBPath := filepath.Join(childBeadsDir, "beads.db")
|
||||
if err := os.WriteFile(childDBPath, []byte{}, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Change to child directory
|
||||
t.Chdir(childDir)
|
||||
|
||||
// FindAllDatabases should return only the child's database (stops at first)
|
||||
result := FindAllDatabases()
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Errorf("FindAllDatabases() returned %d databases, expected 1 (should stop at first)", len(result))
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
resultResolved, _ := filepath.EvalSymlinks(result[0].Path)
|
||||
childDBResolved, _ := filepath.EvalSymlinks(childDBPath)
|
||||
if resultResolved != childDBResolved {
|
||||
t.Errorf("FindAllDatabases() found %q, expected child database %q", result[0].Path, childDBPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEnsureWispGitignore tests that EnsureWispGitignore correctly
|
||||
// adds the wisp directory to .gitignore
|
||||
func TestEnsureWispGitignore(t *testing.T) {
|
||||
|
||||
507
internal/beads/fingerprint_test.go
Normal file
507
internal/beads/fingerprint_test.go
Normal file
@@ -0,0 +1,507 @@
|
||||
package beads
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCanonicalizeGitURL tests URL normalization for various git URL formats
|
||||
func TestCanonicalizeGitURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
// HTTPS URLs
|
||||
{
|
||||
name: "https basic",
|
||||
input: "https://github.com/user/repo",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "https with .git suffix",
|
||||
input: "https://github.com/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "https with trailing slash",
|
||||
input: "https://github.com/user/repo/",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "https uppercase host",
|
||||
input: "https://GitHub.COM/User/Repo.git",
|
||||
expected: "github.com/User/Repo",
|
||||
},
|
||||
{
|
||||
name: "https with port 443",
|
||||
input: "https://github.com:443/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "https with custom port",
|
||||
input: "https://gitlab.company.com:8443/user/repo.git",
|
||||
expected: "gitlab.company.com:8443/user/repo",
|
||||
},
|
||||
|
||||
// SSH URLs (protocol style)
|
||||
{
|
||||
name: "ssh protocol basic",
|
||||
input: "ssh://git@github.com/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "ssh with port 22",
|
||||
input: "ssh://git@github.com:22/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "ssh with custom port",
|
||||
input: "ssh://git@gitlab.company.com:2222/user/repo.git",
|
||||
expected: "gitlab.company.com:2222/user/repo",
|
||||
},
|
||||
|
||||
// SCP-style URLs (git@host:path)
|
||||
{
|
||||
name: "scp style basic",
|
||||
input: "git@github.com:user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "scp style without .git",
|
||||
input: "git@github.com:user/repo",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "scp style uppercase host",
|
||||
input: "git@GITHUB.COM:User/Repo.git",
|
||||
expected: "github.com/User/Repo",
|
||||
},
|
||||
{
|
||||
name: "scp style with trailing slash",
|
||||
input: "git@github.com:user/repo/",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "scp style deep path",
|
||||
input: "git@gitlab.com:org/team/project/repo.git",
|
||||
expected: "gitlab.com/org/team/project/repo",
|
||||
},
|
||||
|
||||
// HTTP URLs (less common but valid)
|
||||
{
|
||||
name: "http basic",
|
||||
input: "http://github.com/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "http with port 80",
|
||||
input: "http://github.com:80/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
|
||||
// Git protocol
|
||||
{
|
||||
name: "git protocol",
|
||||
input: "git://github.com/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
|
||||
// Whitespace handling
|
||||
{
|
||||
name: "with leading whitespace",
|
||||
input: " https://github.com/user/repo.git",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "with trailing whitespace",
|
||||
input: "https://github.com/user/repo.git ",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
{
|
||||
name: "with newline",
|
||||
input: "https://github.com/user/repo.git\n",
|
||||
expected: "github.com/user/repo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := canonicalizeGitURL(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("canonicalizeGitURL(%q) error = %v", tt.input, err)
|
||||
}
|
||||
if result != tt.expected {
|
||||
t.Errorf("canonicalizeGitURL(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCanonicalizeGitURL_LocalPath tests that local paths are handled
|
||||
func TestCanonicalizeGitURL_LocalPath(t *testing.T) {
|
||||
// Create a temp directory to use as a "local path"
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Local absolute path
|
||||
result, err := canonicalizeGitURL(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("canonicalizeGitURL(%q) error = %v", tmpDir, err)
|
||||
}
|
||||
|
||||
// Should return a forward-slash path
|
||||
if strings.Contains(result, "\\") {
|
||||
t.Errorf("canonicalizeGitURL(%q) = %q, should use forward slashes", tmpDir, result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCanonicalizeGitURL_WindowsPath tests Windows path detection
|
||||
func TestCanonicalizeGitURL_WindowsPath(t *testing.T) {
|
||||
// This tests the Windows path detection logic (C:/)
|
||||
// The function should NOT treat "C:/foo/bar" as an scp-style URL
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
// These are NOT scp-style URLs - they're Windows paths
|
||||
{"C:/Users/test/repo", "C:/Users/test/repo"},
|
||||
{"D:/projects/myrepo", "D:/projects/myrepo"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result, err := canonicalizeGitURL(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("canonicalizeGitURL(%q) error = %v", tt.input, err)
|
||||
}
|
||||
// Should preserve the Windows path structure (forward slashes)
|
||||
if !strings.Contains(result, "/") {
|
||||
t.Errorf("canonicalizeGitURL(%q) = %q, expected path with slashes", tt.input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestComputeRepoID_WithRemote tests ComputeRepoID when remote.origin.url exists
|
||||
func TestComputeRepoID_WithRemote(t *testing.T) {
|
||||
// Create temporary directory for test repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize git repo
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
// Configure git user
|
||||
cmd = exec.Command("git", "config", "user.email", "test@example.com")
|
||||
cmd.Dir = tmpDir
|
||||
_ = cmd.Run()
|
||||
cmd = exec.Command("git", "config", "user.name", "Test User")
|
||||
cmd.Dir = tmpDir
|
||||
_ = cmd.Run()
|
||||
|
||||
// Set remote.origin.url
|
||||
cmd = exec.Command("git", "remote", "add", "origin", "https://github.com/user/test-repo.git")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("git remote add failed: %v", err)
|
||||
}
|
||||
|
||||
// Change to repo dir
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// ComputeRepoID should return a consistent hash
|
||||
result1, err := ComputeRepoID()
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeRepoID() error = %v", err)
|
||||
}
|
||||
|
||||
// Should be a 32-character hex string (16 bytes)
|
||||
if len(result1) != 32 {
|
||||
t.Errorf("ComputeRepoID() = %q, expected 32 character hex string", result1)
|
||||
}
|
||||
|
||||
// Should be consistent across calls
|
||||
result2, err := ComputeRepoID()
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeRepoID() second call error = %v", err)
|
||||
}
|
||||
if result1 != result2 {
|
||||
t.Errorf("ComputeRepoID() not consistent: %q vs %q", result1, result2)
|
||||
}
|
||||
}
|
||||
|
||||
// TestComputeRepoID_NoRemote tests ComputeRepoID when no remote exists
|
||||
func TestComputeRepoID_NoRemote(t *testing.T) {
|
||||
// Create temporary directory for test repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize git repo (no remote)
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
// Change to repo dir
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// ComputeRepoID should fall back to using the local path
|
||||
result, err := ComputeRepoID()
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeRepoID() error = %v", err)
|
||||
}
|
||||
|
||||
// Should still return a 32-character hex string
|
||||
if len(result) != 32 {
|
||||
t.Errorf("ComputeRepoID() = %q, expected 32 character hex string", result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestComputeRepoID_NotGitRepo tests ComputeRepoID when not in a git repo
|
||||
func TestComputeRepoID_NotGitRepo(t *testing.T) {
|
||||
// Create temporary directory that is NOT a git repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// ComputeRepoID should return an error
|
||||
_, err := ComputeRepoID()
|
||||
if err == nil {
|
||||
t.Error("ComputeRepoID() expected error for non-git directory, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not a git repository") {
|
||||
t.Errorf("ComputeRepoID() error = %q, expected 'not a git repository'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TestComputeRepoID_DifferentRemotesSameCanonical tests that different URL formats
|
||||
// for the same repo produce the same ID
|
||||
func TestComputeRepoID_DifferentRemotesSameCanonical(t *testing.T) {
|
||||
remotes := []string{
|
||||
"https://github.com/user/repo.git",
|
||||
"git@github.com:user/repo.git",
|
||||
"ssh://git@github.com/user/repo.git",
|
||||
}
|
||||
|
||||
var ids []string
|
||||
|
||||
for _, remote := range remotes {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize git repo
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
// Set remote
|
||||
cmd = exec.Command("git", "remote", "add", "origin", remote)
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("git remote add failed for %q: %v", remote, err)
|
||||
}
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
id, err := ComputeRepoID()
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeRepoID() for remote %q error = %v", remote, err)
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
// All IDs should be the same since they point to the same canonical repo
|
||||
for i := 1; i < len(ids); i++ {
|
||||
if ids[i] != ids[0] {
|
||||
t.Errorf("ComputeRepoID() produced different IDs for same repo:\n remote[0]=%q id=%s\n remote[%d]=%q id=%s",
|
||||
remotes[0], ids[0], i, remotes[i], ids[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCloneID_Basic tests GetCloneID returns a consistent ID
|
||||
func TestGetCloneID_Basic(t *testing.T) {
|
||||
// Create temporary directory for test repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize git repo
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// GetCloneID should return a consistent hash
|
||||
result1, err := GetCloneID()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCloneID() error = %v", err)
|
||||
}
|
||||
|
||||
// Should be a 16-character hex string (8 bytes)
|
||||
if len(result1) != 16 {
|
||||
t.Errorf("GetCloneID() = %q, expected 16 character hex string", result1)
|
||||
}
|
||||
|
||||
// Should be consistent across calls
|
||||
result2, err := GetCloneID()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCloneID() second call error = %v", err)
|
||||
}
|
||||
if result1 != result2 {
|
||||
t.Errorf("GetCloneID() not consistent: %q vs %q", result1, result2)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCloneID_DifferentDirs tests GetCloneID produces different IDs for different clones
|
||||
func TestGetCloneID_DifferentDirs(t *testing.T) {
|
||||
ids := make(map[string]string)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize git repo
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
id, err := GetCloneID()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCloneID() error = %v", err)
|
||||
}
|
||||
|
||||
// Each clone should have a unique ID
|
||||
if prev, exists := ids[id]; exists {
|
||||
t.Errorf("GetCloneID() produced duplicate ID %q for dirs %q and %q", id, prev, tmpDir)
|
||||
}
|
||||
ids[id] = tmpDir
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCloneID_NotGitRepo tests GetCloneID when not in a git repo
|
||||
func TestGetCloneID_NotGitRepo(t *testing.T) {
|
||||
// Create temporary directory that is NOT a git repo
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// GetCloneID should return an error
|
||||
_, err := GetCloneID()
|
||||
if err == nil {
|
||||
t.Error("GetCloneID() expected error for non-git directory, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not a git repository") {
|
||||
t.Errorf("GetCloneID() error = %q, expected 'not a git repository'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCloneID_IncludesHostname tests that GetCloneID includes hostname
|
||||
// to differentiate the same path on different machines
|
||||
func TestGetCloneID_IncludesHostname(t *testing.T) {
|
||||
// This test verifies the concept - we can't actually test different hostnames
|
||||
// but we can verify that the same path produces the same ID on this machine
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize git repo
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
id, err := GetCloneID()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCloneID() error = %v", err)
|
||||
}
|
||||
|
||||
// Just verify we got a valid ID - we can't test different hostnames
|
||||
// but the implementation includes hostname in the hash
|
||||
if len(id) != 16 {
|
||||
t.Errorf("GetCloneID() = %q, expected 16 character hex string (hostname=%s)", id, hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCloneID_Worktree tests GetCloneID in a worktree
|
||||
func TestGetCloneID_Worktree(t *testing.T) {
|
||||
// Create temporary directory for test
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Initialize main git repo
|
||||
mainRepoDir := filepath.Join(tmpDir, "main-repo")
|
||||
if err := os.MkdirAll(mainRepoDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = mainRepoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("git not available: %v", err)
|
||||
}
|
||||
|
||||
// Configure git user
|
||||
cmd = exec.Command("git", "config", "user.email", "test@example.com")
|
||||
cmd.Dir = mainRepoDir
|
||||
_ = cmd.Run()
|
||||
cmd = exec.Command("git", "config", "user.name", "Test User")
|
||||
cmd.Dir = mainRepoDir
|
||||
_ = cmd.Run()
|
||||
|
||||
// Create initial commit (required for worktree)
|
||||
dummyFile := filepath.Join(mainRepoDir, "README.md")
|
||||
if err := os.WriteFile(dummyFile, []byte("# Test\n"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd = exec.Command("git", "add", "README.md")
|
||||
cmd.Dir = mainRepoDir
|
||||
_ = cmd.Run()
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial commit")
|
||||
cmd.Dir = mainRepoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("git commit failed: %v", err)
|
||||
}
|
||||
|
||||
// Create a worktree
|
||||
worktreeDir := filepath.Join(tmpDir, "worktree")
|
||||
cmd = exec.Command("git", "worktree", "add", worktreeDir, "HEAD")
|
||||
cmd.Dir = mainRepoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("git worktree add failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
cmd := exec.Command("git", "worktree", "remove", worktreeDir)
|
||||
cmd.Dir = mainRepoDir
|
||||
_ = cmd.Run()
|
||||
}()
|
||||
|
||||
// Get IDs from both locations
|
||||
t.Chdir(mainRepoDir)
|
||||
mainID, err := GetCloneID()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCloneID() in main repo error = %v", err)
|
||||
}
|
||||
|
||||
t.Chdir(worktreeDir)
|
||||
worktreeID, err := GetCloneID()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCloneID() in worktree error = %v", err)
|
||||
}
|
||||
|
||||
// Worktree should have a DIFFERENT ID than main repo
|
||||
// because they're different paths (different clones conceptually)
|
||||
if mainID == worktreeID {
|
||||
t.Errorf("GetCloneID() returned same ID for main repo and worktree - should be different")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user