forked from mirrors/pkg-proxy
195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
|
|
package mirror
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"log/slog"
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/git-pkgs/proxy/internal/database"
|
||
|
|
"github.com/git-pkgs/proxy/internal/handler"
|
||
|
|
"github.com/git-pkgs/proxy/internal/storage"
|
||
|
|
"github.com/git-pkgs/registries/fetch"
|
||
|
|
)
|
||
|
|
|
||
|
|
// setupTestMirror creates a Mirror with real DB and filesystem storage for integration tests.
|
||
|
|
func setupTestMirror(t *testing.T, workers int) *Mirror {
|
||
|
|
t.Helper()
|
||
|
|
|
||
|
|
dbPath := t.TempDir() + "/test.db"
|
||
|
|
db, err := database.Create(dbPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("creating database: %v", err)
|
||
|
|
}
|
||
|
|
if err := db.MigrateSchema(); err != nil {
|
||
|
|
t.Fatalf("migrating schema: %v", err)
|
||
|
|
}
|
||
|
|
t.Cleanup(func() { _ = db.Close() })
|
||
|
|
|
||
|
|
storeDir := t.TempDir()
|
||
|
|
store, err := storage.OpenBucket(context.Background(), "file://"+storeDir)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("opening storage: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelWarn}))
|
||
|
|
fetcher := fetch.NewFetcher()
|
||
|
|
resolver := fetch.NewResolver()
|
||
|
|
proxy := handler.NewProxy(db, store, fetcher, resolver, logger)
|
||
|
|
|
||
|
|
return New(proxy, db, store, logger, workers)
|
||
|
|
}
|
||
|
|
|
||
|
|
const testPackageLodash = "lodash"
|
||
|
|
|
||
|
|
func TestMirrorRunEmptySource(t *testing.T) {
|
||
|
|
m := setupTestMirror(t, 2)
|
||
|
|
|
||
|
|
source := &PURLSource{PURLs: []string{}}
|
||
|
|
progress, err := m.Run(context.Background(), source)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Run() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if progress.Total != 0 {
|
||
|
|
t.Errorf("total = %d, want 0", progress.Total)
|
||
|
|
}
|
||
|
|
if progress.Phase != "complete" {
|
||
|
|
t.Errorf("phase = %q, want %q", progress.Phase, "complete")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMirrorRunDryRun(t *testing.T) {
|
||
|
|
m := setupTestMirror(t, 1)
|
||
|
|
|
||
|
|
source := &PURLSource{
|
||
|
|
PURLs: []string{
|
||
|
|
"pkg:npm/lodash@4.17.21",
|
||
|
|
"pkg:cargo/serde@1.0.0",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
items, err := m.RunDryRun(context.Background(), source)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("RunDryRun() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(items) != 2 {
|
||
|
|
t.Fatalf("got %d items, want 2", len(items))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Dry run should not modify the database
|
||
|
|
stats, err := m.db.GetCacheStats()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetCacheStats() error = %v", err)
|
||
|
|
}
|
||
|
|
if stats.TotalArtifacts != 0 {
|
||
|
|
t.Errorf("artifacts = %d, want 0 (dry run should not cache)", stats.TotalArtifacts)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMirrorRunCanceled(t *testing.T) {
|
||
|
|
m := setupTestMirror(t, 1)
|
||
|
|
|
||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
|
cancel() // cancel immediately
|
||
|
|
|
||
|
|
// Use a source that produces items but they'll all fail due to canceled context
|
||
|
|
source := &PURLSource{
|
||
|
|
PURLs: []string{"pkg:npm/lodash@4.17.21"},
|
||
|
|
}
|
||
|
|
|
||
|
|
progress, err := m.Run(ctx, source)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Run() error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// With a canceled context, the fetch should fail
|
||
|
|
if progress.Failed != 1 {
|
||
|
|
t.Errorf("failed = %d, want 1", progress.Failed)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProgressTrackerSnapshot(t *testing.T) {
|
||
|
|
pt := newProgressTracker()
|
||
|
|
pt.total.Store(10)
|
||
|
|
pt.completed.Store(5)
|
||
|
|
pt.skipped.Store(3)
|
||
|
|
pt.failed.Store(2)
|
||
|
|
pt.bytes.Store(1024)
|
||
|
|
pt.phase.Store("downloading")
|
||
|
|
pt.addError("npm", testPackageLodash, "4.17.21", "fetch failed")
|
||
|
|
|
||
|
|
snap := pt.snapshot()
|
||
|
|
if snap.Total != 10 {
|
||
|
|
t.Errorf("total = %d, want 10", snap.Total)
|
||
|
|
}
|
||
|
|
if snap.Completed != 5 {
|
||
|
|
t.Errorf("completed = %d, want 5", snap.Completed)
|
||
|
|
}
|
||
|
|
if snap.Skipped != 3 {
|
||
|
|
t.Errorf("skipped = %d, want 3", snap.Skipped)
|
||
|
|
}
|
||
|
|
if snap.Failed != 2 {
|
||
|
|
t.Errorf("failed = %d, want 2", snap.Failed)
|
||
|
|
}
|
||
|
|
if snap.Bytes != 1024 {
|
||
|
|
t.Errorf("bytes = %d, want 1024", snap.Bytes)
|
||
|
|
}
|
||
|
|
if snap.Phase != "downloading" {
|
||
|
|
t.Errorf("phase = %q, want %q", snap.Phase, "downloading")
|
||
|
|
}
|
||
|
|
if len(snap.Errors) != 1 {
|
||
|
|
t.Fatalf("errors = %d, want 1", len(snap.Errors))
|
||
|
|
}
|
||
|
|
if snap.Errors[0].Name != testPackageLodash {
|
||
|
|
t.Errorf("error name = %q, want %q", snap.Errors[0].Name, testPackageLodash)
|
||
|
|
}
|
||
|
|
if snap.StartedAt.IsZero() {
|
||
|
|
t.Error("started_at should not be zero")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProgressTrackerConcurrentAccess(t *testing.T) {
|
||
|
|
pt := newProgressTracker()
|
||
|
|
done := make(chan struct{})
|
||
|
|
|
||
|
|
for range 10 {
|
||
|
|
go func() {
|
||
|
|
pt.completed.Add(1)
|
||
|
|
pt.addError("npm", "test", "1.0.0", "error")
|
||
|
|
_ = pt.snapshot()
|
||
|
|
done <- struct{}{}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
timeout := time.After(5 * time.Second)
|
||
|
|
for range 10 {
|
||
|
|
select {
|
||
|
|
case <-done:
|
||
|
|
case <-timeout:
|
||
|
|
t.Fatal("timed out waiting for goroutines")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
snap := pt.snapshot()
|
||
|
|
if snap.Completed != 10 {
|
||
|
|
t.Errorf("completed = %d, want 10", snap.Completed)
|
||
|
|
}
|
||
|
|
if len(snap.Errors) != 10 {
|
||
|
|
t.Errorf("errors = %d, want 10", len(snap.Errors))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNewMirrorDefaultWorkers(t *testing.T) {
|
||
|
|
m := New(nil, nil, nil, slog.Default(), 0)
|
||
|
|
if m.workers != 1 {
|
||
|
|
t.Errorf("workers = %d, want 1 (minimum)", m.workers)
|
||
|
|
}
|
||
|
|
|
||
|
|
m = New(nil, nil, nil, slog.Default(), -5)
|
||
|
|
if m.workers != 1 {
|
||
|
|
t.Errorf("workers = %d, want 1 (minimum)", m.workers)
|
||
|
|
}
|
||
|
|
}
|