|
7 | 7 | "net/http" |
8 | 8 | "net/http/httptest" |
9 | 9 | "strings" |
| 10 | + "sync/atomic" |
10 | 11 | "testing" |
11 | 12 | "time" |
12 | 13 |
|
@@ -292,3 +293,60 @@ func TestPyPIDownloadCooldown(t *testing.T) { |
292 | 293 | }) |
293 | 294 | } |
294 | 295 | } |
| 296 | + |
| 297 | +// TestPyPIDownloadCooldownMetadataCache ensures that repeated downloads that |
| 298 | +// trigger cooldown filtering reuse the cached PyPI JSON metadata instead of |
| 299 | +// fetching it from upstream once per download. |
| 300 | +func TestPyPIDownloadCooldownMetadataCache(t *testing.T) { |
| 301 | + now := time.Now() |
| 302 | + releases := `{"releases": { |
| 303 | + "1.0.0": [{"upload_time_iso_8601": "` + now.Add(-30*24*time.Hour).Format(time.RFC3339) + `"}], |
| 304 | + "2.0.0": [{"upload_time_iso_8601": "` + now.Add(-1*time.Hour).Format(time.RFC3339) + `"}] |
| 305 | + }}` |
| 306 | + |
| 307 | + var metadataRequests atomic.Int64 |
| 308 | + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 309 | + if r.URL.Path == "/pypi/newpkg/json" { |
| 310 | + metadataRequests.Add(1) |
| 311 | + w.Header().Set("Content-Type", "application/json") |
| 312 | + _, _ = io.WriteString(w, releases) |
| 313 | + return |
| 314 | + } |
| 315 | + w.Header().Set("Content-Type", "application/octet-stream") |
| 316 | + _, _ = io.WriteString(w, "package data") |
| 317 | + })) |
| 318 | + defer upstream.Close() |
| 319 | + |
| 320 | + proxy, _, _, fetcher := setupTestProxy(t) |
| 321 | + proxy.HTTPClient = upstream.Client() |
| 322 | + proxy.CacheMetadata = true |
| 323 | + proxy.MetadataTTL = time.Hour |
| 324 | + proxy.Cooldown = &cooldown.Config{Default: "7d"} |
| 325 | + fetcher.artifact = &fetch.Artifact{ |
| 326 | + Body: io.NopCloser(strings.NewReader("package data")), |
| 327 | + ContentType: "application/octet-stream", |
| 328 | + } |
| 329 | + |
| 330 | + h := &PyPIHandler{ |
| 331 | + proxy: proxy, |
| 332 | + upstreamURL: upstream.URL, |
| 333 | + proxyURL: "http://localhost", |
| 334 | + } |
| 335 | + srv := httptest.NewServer(h.Routes()) |
| 336 | + defer srv.Close() |
| 337 | + |
| 338 | + // Two downloads of the same package: one outside the cooldown window |
| 339 | + // (served) and one inside (withheld). Both go through the download path |
| 340 | + // that resolves filtered versions. |
| 341 | + for _, filename := range []string{"newpkg-1.0.0.tar.gz", "newpkg-2.0.0.tar.gz"} { |
| 342 | + resp, err := http.Get(srv.URL + "/packages/packages/ab/cd/ef0123456789/" + filename) |
| 343 | + if err != nil { |
| 344 | + t.Fatalf("request failed: %v", err) |
| 345 | + } |
| 346 | + _ = resp.Body.Close() |
| 347 | + } |
| 348 | + |
| 349 | + if got := metadataRequests.Load(); got != 1 { |
| 350 | + t.Errorf("upstream metadata JSON requests = %d, want 1 (repeated downloads should reuse the cached metadata)", got) |
| 351 | + } |
| 352 | +} |
0 commit comments