pkg-proxy/internal/mirror/job_test.go
Andrew Nesbitt c01f0a5c05
Fix metadata caching, 404 propagation, mirror progress, and registry stubs
- ProxyCached now stores upstream Last-Modified in the cache and uses it
  (along with ETag) for conditional request handling, returning 304 when
  client validators match. Adds Content-Length to cached responses.

- Handlers calling FetchOrCacheMetadata (pypi, composer, pub, nuget) now
  check for ErrUpstreamNotFound and return 404 instead of 502, matching
  the existing npm and cargo behavior.

- Mirror jobs report live progress via a periodic callback while running,
  so API polls return real counts instead of zeroed progress.

- Registry mirroring removed from CLI flags, API acceptance, README, and
  docs since every enumerator was a stub returning "not yet implemented".

- Added tests for the conditional metadata path (ETag/If-None-Match,
  Last-Modified/If-Modified-Since, 304 responses, header omission).
2026-04-13 09:01:05 +01:00

183 lines
4.2 KiB
Go

package mirror
import (
"context"
"testing"
"time"
)
func TestJobStoreCreateAndGet(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
id, err := js.Create(JobRequest{
PURLs: []string{"pkg:npm/lodash@4.17.21"},
})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if id == "" {
t.Fatal("expected non-empty job ID")
}
// Wait for the job to start (it runs async)
time.Sleep(100 * time.Millisecond)
job := js.Get(id)
if job == nil {
t.Fatal("Get() returned nil")
}
if job.ID != id {
t.Errorf("job ID = %q, want %q", job.ID, id)
}
}
func TestJobStoreGetNotFound(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
job := js.Get("nonexistent")
if job != nil {
t.Errorf("expected nil for nonexistent job, got %v", job)
}
}
func TestJobStoreCancelNotFound(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
if js.Cancel("nonexistent") {
t.Error("expected Cancel to return false for nonexistent job")
}
}
func TestJobStoreCreateInvalidRequest(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
_, err := js.Create(JobRequest{})
if err == nil {
t.Fatal("expected error for empty request")
}
}
func TestJobStoreMultipleJobs(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
id1, err := js.Create(JobRequest{PURLs: []string{"pkg:npm/lodash@4.17.21"}})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
id2, err := js.Create(JobRequest{PURLs: []string{"pkg:cargo/serde@1.0.0"}})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if id1 == id2 {
t.Error("expected different job IDs")
}
job1 := js.Get(id1)
job2 := js.Get(id2)
if job1 == nil || job2 == nil {
t.Fatal("expected both jobs to exist")
}
}
func TestSourceFromRequestPURLs(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
source, err := js.sourceFromRequest(JobRequest{PURLs: []string{"pkg:npm/lodash@1.0.0"}})
if err != nil {
t.Fatalf("sourceFromRequest() error = %v", err)
}
if _, ok := source.(*PURLSource); !ok {
t.Errorf("expected *PURLSource, got %T", source)
}
}
func TestSourceFromRequestRegistryRejected(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
_, err := js.sourceFromRequest(JobRequest{Registry: "npm"})
if err == nil {
t.Fatal("expected error for registry request")
}
}
func TestJobStoreCleanup(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
// Add a completed job with old CreatedAt
js.mu.Lock()
js.jobs["old-job"] = &Job{
ID: "old-job",
State: JobStateComplete,
CreatedAt: time.Now().Add(-2 * time.Hour),
}
js.jobs["recent-job"] = &Job{
ID: "recent-job",
State: JobStateComplete,
CreatedAt: time.Now(),
}
js.jobs["running-job"] = &Job{
ID: "running-job",
State: JobStateRunning,
CreatedAt: time.Now().Add(-2 * time.Hour),
}
js.mu.Unlock()
js.Cleanup()
if js.Get("old-job") != nil {
t.Error("expected old completed job to be cleaned up")
}
if js.Get("recent-job") == nil {
t.Error("expected recent completed job to be kept")
}
if js.Get("running-job") == nil {
t.Error("expected running job to be kept regardless of age")
}
}
func TestJobStoreCancelPreservesStateAfterRunJob(t *testing.T) {
m := setupTestMirror(t, 1)
js := NewJobStore(context.Background(), m)
// Create a job with a PURL that will fail (no real upstream in test)
id, err := js.Create(JobRequest{PURLs: []string{"pkg:npm/nonexistent-pkg@0.0.0"}})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
// Cancel immediately -- the job may already be running
js.Cancel(id)
// Wait for runJob goroutine to finish
time.Sleep(200 * time.Millisecond)
job := js.Get(id)
if job == nil {
t.Fatal("Get() returned nil")
}
if job.State != JobStateCanceled {
t.Errorf("state = %q, want %q (cancel should not be overwritten by runJob)", job.State, JobStateCanceled)
}
}
func TestNewJobIDUnique(t *testing.T) {
ids := make(map[string]bool)
for range 100 {
id := newJobID()
if ids[id] {
t.Fatalf("duplicate job ID: %s", id)
}
ids[id] = true
}
}