package services import ( "os" "path/filepath" "testing" "time" ) func TestCleanupExpiredRepos(t *testing.T) { tmpDir := t.TempDir() // Create "old" repo (modify time in the past) oldDir := filepath.Join(tmpDir, "old-repo") os.MkdirAll(oldDir, 0o755) os.WriteFile(filepath.Join(oldDir, "file.txt"), []byte("old"), 0o644) oldTime := time.Now().AddDate(0, 0, -10) // 10 days ago os.Chtimes(oldDir, oldTime, oldTime) // Create "new" repo (recent) newDir := filepath.Join(tmpDir, "new-repo") os.MkdirAll(newDir, 0o755) os.WriteFile(filepath.Join(newDir, "file.txt"), []byte("new"), 0o644) result, err := CleanupExpiredRepos(tmpDir, 7) if err != nil { t.Fatalf("CleanupExpiredRepos() error: %v", err) } if result.RemovedCount != 1 { t.Errorf("RemovedCount = %d, want 1", result.RemovedCount) } if len(result.RemovedPaths) != 1 || result.RemovedPaths[0] != "old-repo" { t.Errorf("RemovedPaths = %v, want [old-repo]", result.RemovedPaths) } if result.FreedBytes <= 0 { t.Errorf("FreedBytes = %d, want > 0", result.FreedBytes) } // Verify new repo still exists if _, err := os.Stat(newDir); os.IsNotExist(err) { t.Error("new-repo should still exist") } // Verify old repo was removed if _, err := os.Stat(oldDir); !os.IsNotExist(err) { t.Error("old-repo should have been removed") } } func TestCleanupExpiredRepos_NonexistentDir(t *testing.T) { result, err := CleanupExpiredRepos("/nonexistent/path/12345", 7) if err != nil { t.Fatalf("CleanupExpiredRepos() error: %v", err) } if result.RemovedCount != 0 { t.Errorf("RemovedCount = %d, want 0", result.RemovedCount) } } func TestCleanupExpiredRepos_NoExpired(t *testing.T) { tmpDir := t.TempDir() // Create only recent repos for _, name := range []string{"repo1", "repo2"} { dir := filepath.Join(tmpDir, name) os.MkdirAll(dir, 0o755) } result, err := CleanupExpiredRepos(tmpDir, 7) if err != nil { t.Fatalf("CleanupExpiredRepos() error: %v", err) } if result.RemovedCount != 0 { t.Errorf("RemovedCount = %d, want 0", result.RemovedCount) } } func TestCleanupBySize(t *testing.T) { tmpDir := t.TempDir() // Create repos with known sizes; set mod times so ordering is deterministic for i, name := range []string{"oldest", "middle", "newest"} { dir := filepath.Join(tmpDir, name) os.MkdirAll(dir, 0o755) // Each repo: ~100 bytes os.WriteFile(filepath.Join(dir, "data.txt"), make([]byte, 100), 0o644) modTime := time.Now().AddDate(0, 0, -10+i) // -10, -9, -8 days os.Chtimes(dir, modTime, modTime) os.Chtimes(filepath.Join(dir, "data.txt"), modTime, modTime) } // Set max to 150 bytes — should remove oldest repos until under limit result, err := CleanupBySize(tmpDir, 0) // 0 MB = 0 bytes limit if err != nil { t.Fatalf("CleanupBySize() error: %v", err) } // With 0 MB limit, all repos should be removed if result.RemovedCount != 3 { t.Errorf("RemovedCount = %d, want 3", result.RemovedCount) } } func TestCleanupBySize_UnderLimit(t *testing.T) { tmpDir := t.TempDir() dir := filepath.Join(tmpDir, "small-repo") os.MkdirAll(dir, 0o755) os.WriteFile(filepath.Join(dir, "data.txt"), []byte("tiny"), 0o644) result, err := CleanupBySize(tmpDir, 100) // 100 MB limit if err != nil { t.Fatalf("CleanupBySize() error: %v", err) } if result.RemovedCount != 0 { t.Errorf("RemovedCount = %d, want 0", result.RemovedCount) } } func TestCleanupBySize_NonexistentDir(t *testing.T) { result, err := CleanupBySize("/nonexistent/path/12345", 100) if err != nil { t.Fatalf("CleanupBySize() error: %v", err) } if result.RemovedCount != 0 { t.Errorf("RemovedCount = %d, want 0", result.RemovedCount) } } func TestGetCacheStats(t *testing.T) { tmpDir := t.TempDir() // Create 3 repo dirs with files for _, name := range []string{"repo1", "repo2", "repo3"} { dir := filepath.Join(tmpDir, name) os.MkdirAll(dir, 0o755) os.WriteFile(filepath.Join(dir, "data.txt"), make([]byte, 50), 0o644) } // Create a file (not a dir) — should be ignored os.WriteFile(filepath.Join(tmpDir, "not-a-repo.txt"), []byte("ignored"), 0o644) count, totalBytes := GetCacheStats(tmpDir) if count != 3 { t.Errorf("count = %d, want 3", count) } if totalBytes < 150 { // 3 * 50 bytes minimum t.Errorf("totalBytes = %d, want >= 150", totalBytes) } } func TestGetCacheStats_EmptyDir(t *testing.T) { tmpDir := t.TempDir() count, totalBytes := GetCacheStats(tmpDir) if count != 0 { t.Errorf("count = %d, want 0", count) } if totalBytes != 0 { t.Errorf("totalBytes = %d, want 0", totalBytes) } } func TestGetCacheStats_NonexistentDir(t *testing.T) { count, totalBytes := GetCacheStats("/nonexistent/path/12345") if count != 0 { t.Errorf("count = %d, want 0", count) } if totalBytes != 0 { t.Errorf("totalBytes = %d, want 0", totalBytes) } } func TestSortReposByModTime(t *testing.T) { now := time.Now() repos := []repoEntry{ {name: "newest", modTime: now}, {name: "oldest", modTime: now.AddDate(0, 0, -10)}, {name: "middle", modTime: now.AddDate(0, 0, -5)}, } sortReposByModTime(repos) if repos[0].name != "oldest" { t.Errorf("repos[0] = %q, want %q", repos[0].name, "oldest") } if repos[1].name != "middle" { t.Errorf("repos[1] = %q, want %q", repos[1].name, "middle") } if repos[2].name != "newest" { t.Errorf("repos[2] = %q, want %q", repos[2].name, "newest") } } func TestSortReposByModTime_Empty(t *testing.T) { repos := []repoEntry{} sortReposByModTime(repos) // should not panic if len(repos) != 0 { t.Errorf("expected empty slice") } } func TestSortReposByModTime_Single(t *testing.T) { repos := []repoEntry{{name: "only", modTime: time.Now()}} sortReposByModTime(repos) // should not panic if repos[0].name != "only" { t.Errorf("repos[0] = %q, want %q", repos[0].name, "only") } }