Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
pkg-proxy/internal/server/stats_test.go
2026-11-05 15:09:16 +00:00

139 lines
4.2 KiB
Go

package server
import (
"database/sql"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/git-pkgs/proxy/internal/database"
)
func seedCachedArtifact(t *testing.T, db *database.DB, ecosystem, name, version string, size, hits int64) {
t.Helper()
pkgPURL := "pkg:" + ecosystem + "/" + name
versionPURL := pkgPURL + "@" + version
if err := db.UpsertPackage(&database.Package{PURL: pkgPURL, Ecosystem: ecosystem, Name: name}); err != nil {
t.Fatalf("UpsertPackage: %v", err)
}
if err := db.UpsertVersion(&database.Version{PURL: versionPURL, PackagePURL: pkgPURL}); err != nil {
t.Fatalf("UpsertVersion: %v", err)
}
if err := db.UpsertArtifact(&database.Artifact{
VersionPURL: versionPURL,
Filename: name + "-" + version + ".tgz",
UpstreamURL: "https://example.test/" + name,
StoragePath: sql.NullString{String: "objects/" + name, Valid: true},
Size: sql.NullInt64{Int64: size, Valid: true},
FetchedAt: sql.NullTime{Time: time.Now(), Valid: true},
HitCount: hits,
}); err != nil {
t.Fatalf("UpsertArtifact: %v", err)
}
}
func getStats(t *testing.T, ts *testServer) StatsResponse {
t.Helper()
w := httptest.NewRecorder()
ts.handler.ServeHTTP(w, httptest.NewRequest("GET", "/stats", nil))
if w.Code != http.StatusOK {
t.Fatalf("GET /stats = %d, want 200", w.Code)
}
var stats StatsResponse
if err := json.NewDecoder(w.Body).Decode(&stats); err != nil {
t.Fatalf("decode: %v", err)
}
return stats
}
func TestStatsReportsEcosystemBreakdown(t *testing.T) {
ts := newTestServer(t)
defer ts.close()
seedCachedArtifact(t, ts.db, "npm", "lodash", "4.17.21", 1000, 3)
seedCachedArtifact(t, ts.db, "cargo", "serde", "1.0.0", 500, 2)
stats := getStats(t, ts)
if stats.DownloadedBytes != 4000 {
t.Errorf("downloaded_bytes = %d, want 4000", stats.DownloadedBytes)
}
if stats.Downloads != 5 {
t.Errorf("downloads = %d, want 5", stats.Downloads)
}
if stats.DownloadedBytesHuman == "" {
t.Error("downloaded is empty; the human-readable total is part of the shape")
}
if stats.StatsUnavailable {
t.Error("stats_unavailable set on a healthy aggregation")
}
byEcosystem := make(map[string]EcosystemStatsEntry, len(stats.Ecosystems))
for _, e := range stats.Ecosystems {
byEcosystem[e.Ecosystem] = e
}
npm, ok := byEcosystem["npm"]
if !ok {
t.Fatalf("no npm row in %v", byEcosystem)
}
if npm.DownloadedBytes != 3000 || npm.Downloads != 3 || npm.CacheSize != 1000 {
t.Errorf("npm row = %+v, want 3000 bytes over 3 downloads and 1000 cached", npm)
}
}
// A failed aggregation with nothing to fall back on must not read as an idle
// proxy: the totals are zero either way, so the flag is the only thing
// telling a consumer which of the two it is looking at.
func TestStatsFlagsUnavailableFigures(t *testing.T) {
ts := newTestServer(t)
defer ts.close()
// GetEcosystemStats counts packages first; the artifact count and cache
// size the endpoint already reported do not touch that table.
if _, err := ts.db.Exec(`DROP TABLE packages`); err != nil {
t.Fatalf("DROP TABLE packages: %v", err)
}
stats := getStats(t, ts)
if !stats.StatsUnavailable {
t.Error("stats_unavailable not set after the aggregation failed with no snapshot")
}
if len(stats.Ecosystems) != 0 {
t.Errorf("ecosystems = %v, want empty", stats.Ecosystems)
}
if stats.TotalSizeHuman == "" {
t.Error("the rest of the response must still be served")
}
}
// A retained snapshot is served on with no flag: the figures are stale, not
// absent, and the page is where staleness is surfaced to a human.
func TestStatsServesRetainedSnapshot(t *testing.T) {
ts := newTestServer(t)
defer ts.close()
seedCachedArtifact(t, ts.db, "npm", "lodash", "4.17.21", 1000, 3)
if got := getStats(t, ts).DownloadedBytes; got != 3000 {
t.Fatalf("downloaded_bytes = %d, want 3000 before the failure", got)
}
if _, err := ts.db.Exec(`DROP TABLE packages`); err != nil {
t.Fatalf("DROP TABLE packages: %v", err)
}
ts.server.ecoStats.lastAt = time.Time{}
stats := getStats(t, ts)
if stats.StatsUnavailable {
t.Error("stats_unavailable set although a snapshot was retained")
}
if stats.DownloadedBytes != 3000 {
t.Errorf("downloaded_bytes = %d, want the retained 3000", stats.DownloadedBytes)
}
}