mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-10 16:19:16 -05:00
The compare page loaded diff2html from cdn.jsdelivr.net without a version or integrity hash, so whatever jsDelivr served as the latest release ran with the proxy origin's privileges, and the page broke where the proxy cannot reach the internet. - Vendor diff2html 3.4.56 (MIT) under static/vendor like tailwind and lucide - Load its script and stylesheet from /ui/static/vendor - Fail a test when any template loads a script or stylesheet from another origin
738 lines
20 KiB
Go
738 lines
20 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"io/fs"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/git-pkgs/proxy/internal/database"
|
|
)
|
|
|
|
func TestTemplatesRenderAllPages(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
tests := []struct {
|
|
page string
|
|
data any
|
|
}{
|
|
{"dashboard", DashboardData{
|
|
Stats: DashboardStats{
|
|
CachedArtifacts: 42,
|
|
TotalSize: "1.5 GB",
|
|
TotalPackages: 10,
|
|
TotalVersions: 25,
|
|
},
|
|
EnrichmentStats: EnrichmentStatsView{
|
|
EnrichedPackages: 5,
|
|
TotalVulnerabilities: 3,
|
|
CriticalVulns: 1,
|
|
HasVulns: true,
|
|
},
|
|
PopularPackages: []PackageInfo{
|
|
{Ecosystem: "npm", Name: "lodash", Hits: 100, Size: "500 KB", License: "MIT", LicenseCategory: "permissive"},
|
|
},
|
|
RecentPackages: []PackageInfo{
|
|
{Ecosystem: "cargo", Name: "serde", Version: "1.0.0", Size: "200 KB", CachedAt: "1 hour ago"},
|
|
},
|
|
}},
|
|
{"analytics", AnalyticsData{
|
|
Totals: AnalyticsTotals{
|
|
DownloadedBytes: 1_500_000_000,
|
|
Downloaded: "1.4 GB",
|
|
Downloads: "12,004",
|
|
CacheSize: "420.0 MB",
|
|
CachedArtifacts: "1,204",
|
|
Packages: "310",
|
|
Versions: "902",
|
|
Ecosystems: 3,
|
|
ActiveEcosystems: 2,
|
|
Amplification: "3.4x",
|
|
},
|
|
EnrichmentStats: EnrichmentStatsView{TotalVulnerabilities: 3, CriticalVulns: 1, HasVulns: true},
|
|
Ecosystems: []EcosystemRow{
|
|
{Ecosystem: "npm", DownloadedBytes: 1_000_000_000, Downloaded: "953.7 MB", Downloads: "9,000", CacheSize: "300.0 MB", AvgArtifactSize: "120 KB", Artifacts: "900", Packages: "200", Versions: "700", SharePct: "66.7"},
|
|
{Ecosystem: "cargo", DownloadedBytes: 500_000_000, Downloaded: "476.8 MB", Downloads: "3,004", CacheSize: "120.0 MB", AvgArtifactSize: "400 KB", Artifacts: "300", Packages: "100", Versions: "200", SharePct: "33.3"},
|
|
{Ecosystem: "rpm", Downloaded: "0 B", Downloads: "0", CacheSize: "0 B", AvgArtifactSize: "0 B", Artifacts: "0", Packages: "10", Versions: "2", SharePct: "0.0"},
|
|
},
|
|
Donut: donutView([]database.EcosystemStats{
|
|
{Ecosystem: "npm", CacheSize: 300, Downloads: 9000, DownloadedBytes: 1_000_000_000},
|
|
{Ecosystem: "cargo", CacheSize: 120, Downloads: 3004, DownloadedBytes: 500_000_000},
|
|
}, 1_500_000_000, "1.4 GB"),
|
|
Runtime: RuntimeView{
|
|
Available: true,
|
|
Requests: "12,004",
|
|
ActiveRequests: "2",
|
|
RequestMean: "8.1 ms",
|
|
CacheHits: "9,000",
|
|
CacheMisses: "1,000",
|
|
CacheHitRatio: "90.0",
|
|
HasCacheTraffic: true,
|
|
StatusClasses: []LabelledCount{{Label: "2xx", Count: "11,900"}, {Label: "5xx", Count: "104", Bad: true}},
|
|
StorageOps: []LabelledStat{{Label: "get", Count: "9,000", Mean: "412 µs"}},
|
|
ScanErrors: []LabelledCount{{Label: "npm · clamav · timeout", Count: "3", Bad: true}},
|
|
ScanningOn: true,
|
|
Breakers: []BreakerRow{{Registry: "registry.npmjs.org", State: "closed"}},
|
|
},
|
|
}},
|
|
{"analytics", AnalyticsData{}},
|
|
{"install", struct {
|
|
Layout
|
|
BaseURL string
|
|
Registries []RegistryConfig
|
|
}{
|
|
BaseURL: "http://localhost:8080",
|
|
Registries: getRegistryConfigs("http://localhost:8080"),
|
|
}},
|
|
{"search", SearchPageData{
|
|
Query: "lodash",
|
|
Ecosystem: "npm",
|
|
Results: []SearchResultItem{{Ecosystem: "npm", Name: "lodash", LatestVersion: "4.17.21", Hits: 50, SizeFormatted: "1 MB"}},
|
|
Count: 1,
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalPages: 1,
|
|
}},
|
|
{"search", SearchPageData{
|
|
Query: "nothing",
|
|
Results: []SearchResultItem{},
|
|
Count: 0,
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalPages: 0,
|
|
}},
|
|
{"packages_list", PackagesListPageData{
|
|
Ecosystem: "",
|
|
SortBy: defaultSortBy,
|
|
Results: []SearchResultItem{{Ecosystem: "npm", Name: "express", Hits: 200, SizeFormatted: "2 MB"}},
|
|
Count: 1,
|
|
TotalPackages: 1,
|
|
EcosystemFilters: []EcosystemFilter{{Ecosystem: "npm", Count: 1}},
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalPages: 1,
|
|
}},
|
|
{"package_show", PackageShowData{
|
|
Package: &database.Package{
|
|
PURL: "pkg:npm/lodash",
|
|
Ecosystem: "npm",
|
|
Name: "lodash",
|
|
LatestVersion: sql.NullString{String: "4.17.21", Valid: true},
|
|
License: sql.NullString{String: "MIT", Valid: true},
|
|
},
|
|
Versions: []database.Version{
|
|
{PURL: "pkg:npm/lodash@4.17.21", PackagePURL: "pkg:npm/lodash"},
|
|
},
|
|
Vulnerabilities: []database.Vulnerability{},
|
|
LicenseCategory: "permissive",
|
|
}},
|
|
{"package_show", PackageShowData{
|
|
Package: &database.Package{
|
|
PURL: "pkg:npm/minimal",
|
|
Ecosystem: "npm",
|
|
Name: "minimal",
|
|
},
|
|
Versions: []database.Version{},
|
|
Vulnerabilities: []database.Vulnerability{},
|
|
LicenseCategory: "unknown",
|
|
}},
|
|
{"version_show", VersionShowData{
|
|
Package: &database.Package{
|
|
PURL: "pkg:npm/lodash",
|
|
Ecosystem: "npm",
|
|
Name: "lodash",
|
|
LatestVersion: sql.NullString{String: "4.17.21", Valid: true},
|
|
License: sql.NullString{String: "MIT", Valid: true},
|
|
},
|
|
Version: &database.Version{
|
|
PURL: "pkg:npm/lodash@4.17.20",
|
|
PackagePURL: "pkg:npm/lodash",
|
|
},
|
|
Artifacts: []database.Artifact{
|
|
{
|
|
VersionPURL: "pkg:npm/lodash@4.17.20",
|
|
Filename: "lodash-4.17.20.tgz",
|
|
StoragePath: sql.NullString{String: "npm/lodash/4.17.20/lodash-4.17.20.tgz", Valid: true},
|
|
Size: sql.NullInt64{Int64: 1024, Valid: true},
|
|
HitCount: 42,
|
|
},
|
|
},
|
|
Vulnerabilities: []database.Vulnerability{},
|
|
IsOutdated: true,
|
|
LicenseCategory: "permissive",
|
|
HasCachedArtifact: true,
|
|
}},
|
|
{"browse_source", BrowseSourceData{
|
|
Ecosystem: "npm",
|
|
PackageName: "lodash",
|
|
Version: "4.17.21",
|
|
}},
|
|
{"compare_versions", ComparePageData{
|
|
Ecosystem: "npm",
|
|
PackageName: "lodash",
|
|
FromVersion: "4.17.20",
|
|
ToVersion: "4.17.21",
|
|
}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.page, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
err := templates.Render(w, tt.page, tt.data)
|
|
if err != nil {
|
|
t.Fatalf("Render(%q) failed: %v", tt.page, err)
|
|
}
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
if w.Header().Get("Content-Type") != "text/html; charset=utf-8" {
|
|
t.Errorf("Content-Type = %q, want text/html", w.Header().Get("Content-Type"))
|
|
}
|
|
body := w.Body.String()
|
|
if body == "" {
|
|
t.Error("rendered page is empty")
|
|
}
|
|
if !strings.Contains(body, "<!DOCTYPE html>") && !strings.Contains(body, "<html") {
|
|
t.Error("rendered page doesn't look like HTML")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderEmitsCanonicalAndOG(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
data := DashboardData{
|
|
Layout: Layout{
|
|
UIBaseURL: "https://ui.example.com/ui",
|
|
CanonicalPath: "/ui/",
|
|
},
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
if err := templates.Render(w, "dashboard", data); err != nil {
|
|
t.Fatalf("Render failed: %v", err)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
want := []string{
|
|
`<link rel="canonical" href="https://ui.example.com/ui/ui/">`,
|
|
`<meta property="og:url" content="https://ui.example.com/ui/ui/">`,
|
|
`<meta property="og:site_name" content="git-pkgs proxy">`,
|
|
}
|
|
for _, s := range want {
|
|
if !strings.Contains(body, s) {
|
|
t.Errorf("rendered body missing %q", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFooterUsesBuildInfoWhenPageDefinesVersion(t *testing.T) {
|
|
templates := &Templates{}
|
|
buildInfo := BuildInfo{Version: "proxy-build-1.2.3", Commit: "abc123def"}
|
|
wantFooter := "proxy proxy-build-1.2.3 (abc123def)"
|
|
|
|
tests := []struct {
|
|
name string
|
|
page string
|
|
data any
|
|
shadow string
|
|
}{
|
|
{
|
|
name: "version show page",
|
|
page: "version_show",
|
|
data: VersionShowData{
|
|
Layout: Layout{BuildInfo: buildInfo},
|
|
Package: &database.Package{
|
|
PURL: "pkg:npm/lodash",
|
|
Ecosystem: "npm",
|
|
Name: "lodash",
|
|
},
|
|
Version: &database.Version{
|
|
PURL: "pkg:npm/lodash@9.9.9",
|
|
PackagePURL: "pkg:npm/lodash",
|
|
},
|
|
},
|
|
shadow: "9.9.9",
|
|
},
|
|
{
|
|
name: "browse source page",
|
|
page: "browse_source",
|
|
data: BrowseSourceData{
|
|
Layout: Layout{BuildInfo: buildInfo},
|
|
Ecosystem: "npm",
|
|
PackageName: "lodash",
|
|
Version: "9.9.9",
|
|
},
|
|
shadow: "9.9.9",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
if err := templates.Render(w, tt.page, tt.data); err != nil {
|
|
t.Fatalf("Render(%q) failed: %v", tt.page, err)
|
|
}
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, wantFooter) {
|
|
t.Errorf("footer missing build info %q", wantFooter)
|
|
}
|
|
if strings.Contains(body, "proxy "+tt.shadow) {
|
|
t.Errorf("footer used page Version %q instead of BuildInfo", tt.shadow)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderOmitsCanonicalWhenUIBaseURLUnset(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
w := httptest.NewRecorder()
|
|
if err := templates.Render(w, "dashboard", DashboardData{}); err != nil {
|
|
t.Fatalf("Render failed: %v", err)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if strings.Contains(body, `rel="canonical"`) {
|
|
t.Error("canonical tag should be omitted when UIBaseURL is empty")
|
|
}
|
|
if strings.Contains(body, `property="og:url"`) {
|
|
t.Error("og:url tag should be omitted when UIBaseURL is empty")
|
|
}
|
|
}
|
|
|
|
func TestInstallPageBannerWhenUIDiffersFromBaseURL(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
data := struct {
|
|
Layout
|
|
BaseURL string
|
|
Registries []RegistryConfig
|
|
}{
|
|
Layout: Layout{
|
|
UIBaseURL: "https://ui.example.com/ui",
|
|
CanonicalPath: "/ui/install",
|
|
},
|
|
BaseURL: "http://pkg-proxy:8080",
|
|
Registries: getRegistryConfigs("http://pkg-proxy:8080"),
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
if err := templates.Render(w, "install", data); err != nil {
|
|
t.Fatalf("Render failed: %v", err)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, "https://ui.example.com/ui") || !strings.Contains(body, "http://pkg-proxy:8080") {
|
|
t.Error("install banner should mention both UIBaseURL and BaseURL when they differ")
|
|
}
|
|
if !strings.Contains(body, "package managers should be configured") {
|
|
t.Error("install banner copy missing")
|
|
}
|
|
}
|
|
|
|
func TestInstallPageNoBannerWhenURLsMatch(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
data := struct {
|
|
Layout
|
|
BaseURL string
|
|
Registries []RegistryConfig
|
|
}{
|
|
Layout: Layout{
|
|
UIBaseURL: "http://localhost:8080",
|
|
CanonicalPath: "/ui/install",
|
|
},
|
|
BaseURL: "http://localhost:8080",
|
|
Registries: getRegistryConfigs("http://localhost:8080"),
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
if err := templates.Render(w, "install", data); err != nil {
|
|
t.Fatalf("Render failed: %v", err)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if strings.Contains(body, "package managers should be configured") {
|
|
t.Error("install banner should be hidden when UIBaseURL == BaseURL")
|
|
}
|
|
}
|
|
|
|
func TestTemplatesLazyLoading(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
if templates.pages != nil {
|
|
t.Fatal("expected pages to be nil before first Render call")
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
_ = templates.Render(w, "dashboard", DashboardData{})
|
|
|
|
if templates.pages == nil {
|
|
t.Fatal("expected pages to be populated after first Render call")
|
|
}
|
|
}
|
|
|
|
func TestTemplatesRenderUnknownPage(t *testing.T) {
|
|
templates := &Templates{}
|
|
|
|
w := httptest.NewRecorder()
|
|
err := templates.Render(w, "nonexistent_page", nil)
|
|
if err == nil {
|
|
t.Error("expected error for unknown page")
|
|
}
|
|
}
|
|
|
|
func TestInstallPage(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
req := httptest.NewRequest("GET", "/ui/install", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
|
|
// Should contain instructions for all registries
|
|
registries := []string{"npm", "Cargo", "RubyGems", "Go Modules", "PyPI", "Maven", "Gradle Build Cache", "NuGet", "Composer", "Conan", "Conda", "CRAN"}
|
|
for _, reg := range registries {
|
|
if !strings.Contains(body, reg) {
|
|
t.Errorf("install page should contain %s instructions", reg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPackageShowPage(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
// Create a package with license and version
|
|
pkg := &database.Package{
|
|
PURL: "pkg:npm/test-show",
|
|
Ecosystem: "npm",
|
|
Name: "test-show",
|
|
LatestVersion: sql.NullString{String: "2.0.0", Valid: true},
|
|
License: sql.NullString{String: "MIT", Valid: true},
|
|
}
|
|
if err := ts.db.UpsertPackage(pkg); err != nil {
|
|
t.Fatalf("failed to upsert package: %v", err)
|
|
}
|
|
ver := &database.Version{PURL: "pkg:npm/test-show@2.0.0", PackagePURL: pkg.PURL}
|
|
if err := ts.db.UpsertVersion(ver); err != nil {
|
|
t.Fatalf("failed to upsert version: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/ui/package/npm/test-show", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, "test-show") {
|
|
t.Error("page should contain package name")
|
|
}
|
|
if !strings.Contains(body, "2.0.0") {
|
|
t.Error("page should contain version")
|
|
}
|
|
if !strings.Contains(body, "MIT") {
|
|
t.Error("page should contain license")
|
|
}
|
|
}
|
|
|
|
func TestPackageShowPage_NotFound(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
req := httptest.NewRequest("GET", "/ui/package/npm/nonexistent", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestVersionShowPage_NotFound(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
req := httptest.NewRequest("GET", "/ui/package/npm/nonexistent/1.0.0", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestSearchPage_EmptyQuery(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
req := httptest.NewRequest("GET", "/ui/search", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
// Empty query should redirect to homepage
|
|
if w.Code != http.StatusSeeOther {
|
|
t.Errorf("expected redirect (303), got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestSearchPage_WithQuery(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
req := httptest.NewRequest("GET", "/ui/search?q=test", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, "test") {
|
|
t.Error("search page should contain the query")
|
|
}
|
|
}
|
|
|
|
func TestSearchPage_Pagination(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
// Page 0 or negative should default to page 1
|
|
req := httptest.NewRequest("GET", "/ui/search?q=test&page=0", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
// Non-numeric page should default to page 1
|
|
req = httptest.NewRequest("GET", "/ui/search?q=test&page=abc", nil)
|
|
w = httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestSearchPage_EcosystemFilter(t *testing.T) {
|
|
ts := newTestServer(t)
|
|
defer ts.close()
|
|
|
|
req := httptest.NewRequest("GET", "/ui/search?q=test&ecosystem=npm", nil)
|
|
w := httptest.NewRecorder()
|
|
ts.handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEcosystemBadgeLabel(t *testing.T) {
|
|
tests := []struct {
|
|
ecosystem string
|
|
want string
|
|
}{
|
|
{"oci", "container"},
|
|
{"deb", "debian"},
|
|
{"npm", "npm"},
|
|
{"cargo", "cargo"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := ecosystemBadgeLabel(tt.ecosystem)
|
|
if got != tt.want {
|
|
t.Errorf("ecosystemBadgeLabel(%q) = %q, want %q", tt.ecosystem, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOCIRegistryInstructionsDockerPull(t *testing.T) {
|
|
registries := getRegistryConfigs("http://package-proxy:8080")
|
|
for _, registry := range registries {
|
|
if registry.ID != "oci" {
|
|
continue
|
|
}
|
|
want := "docker pull package-proxy:8080/library/nginx:latest"
|
|
if !strings.Contains(string(registry.Instructions), want) {
|
|
t.Errorf("OCI instructions = %q, want substring %q", registry.Instructions, want)
|
|
}
|
|
return
|
|
}
|
|
t.Fatal("OCI registry instructions not found")
|
|
}
|
|
|
|
func TestSwiftRegistryInstructionsAllowLocalHTTP(t *testing.T) {
|
|
registries := getRegistryConfigs("http://localhost:8080")
|
|
for _, registry := range registries {
|
|
if registry.ID != "swift" {
|
|
continue
|
|
}
|
|
if !strings.Contains(string(registry.Instructions), "--allow-insecure-http") {
|
|
t.Error("Swift HTTP instructions do not allow the insecure local registry")
|
|
}
|
|
return
|
|
}
|
|
t.Fatal("Swift registry instructions not found")
|
|
}
|
|
|
|
func TestEcosystemBadgeClasses(t *testing.T) {
|
|
// Every supported ecosystem should return a non-empty class string
|
|
ecosystems := supportedEcosystems()
|
|
for _, eco := range ecosystems {
|
|
classes := ecosystemBadgeClasses(eco)
|
|
if classes == "" {
|
|
t.Errorf("ecosystemBadgeClasses(%q) returned empty string", eco)
|
|
}
|
|
if !strings.Contains(classes, "inline-flex") {
|
|
t.Errorf("ecosystemBadgeClasses(%q) missing base classes", eco)
|
|
}
|
|
}
|
|
|
|
// Unknown ecosystem should still return classes
|
|
classes := ecosystemBadgeClasses("unknown")
|
|
if !strings.Contains(classes, "gray") {
|
|
t.Error("unknown ecosystem should use gray classes")
|
|
}
|
|
}
|
|
|
|
func TestBuildEcosystemFilters(t *testing.T) {
|
|
filters := buildEcosystemFilters(map[string]int64{
|
|
"npm": 3,
|
|
"cargo": 2,
|
|
"empty": 0,
|
|
})
|
|
|
|
if len(filters) != 2 {
|
|
t.Fatalf("expected 2 filters, got %d", len(filters))
|
|
}
|
|
if filters[0].Ecosystem != "cargo" || filters[0].Count != 2 {
|
|
t.Errorf("first filter = %#v, want cargo with count 2", filters[0])
|
|
}
|
|
if filters[1].Ecosystem != "npm" || filters[1].Count != 3 {
|
|
t.Errorf("second filter = %#v, want npm with count 3", filters[1])
|
|
}
|
|
|
|
extra := buildEcosystemFilters(map[string]int64{"custom": 1})
|
|
if len(extra) != 1 || extra[0].Ecosystem != "custom" {
|
|
t.Errorf("unexpected extra filters: %#v", extra)
|
|
}
|
|
}
|
|
|
|
func TestEcosystemPillClasses(t *testing.T) {
|
|
classes := ecosystemPillClasses("npm")
|
|
if !strings.Contains(classes, "rounded-full") {
|
|
t.Error("pill classes should use rounded-full")
|
|
}
|
|
if !strings.Contains(classes, "bg-red-100") {
|
|
t.Error("npm pill should use npm colors")
|
|
}
|
|
}
|
|
|
|
func TestFormatSize(t *testing.T) {
|
|
tests := []struct {
|
|
bytes int64
|
|
want string
|
|
}{
|
|
{0, "0 B"},
|
|
{100, "100 B"},
|
|
{1024, "1.0 KB"},
|
|
{1536, "1.5 KB"},
|
|
{1048576, "1.0 MB"},
|
|
{1073741824, "1.0 GB"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := formatSize(tt.bytes)
|
|
if got != tt.want {
|
|
t.Errorf("formatSize(%d) = %q, want %q", tt.bytes, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCategorizeLicense(t *testing.T) {
|
|
tests := []struct {
|
|
license sql.NullString
|
|
want string
|
|
}{
|
|
{sql.NullString{Valid: false}, "unknown"},
|
|
{sql.NullString{String: "", Valid: true}, "unknown"},
|
|
{sql.NullString{String: "MIT", Valid: true}, "permissive"},
|
|
{sql.NullString{String: "GPL-3.0", Valid: true}, "copyleft"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := categorizeLicense(tt.license)
|
|
if got != tt.want {
|
|
t.Errorf("categorizeLicense(%v) = %q, want %q", tt.license, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkTemplatesParse(b *testing.B) {
|
|
for b.Loop() {
|
|
t := &Templates{}
|
|
if err := t.load(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkServerCreate(b *testing.B) {
|
|
for b.Loop() {
|
|
_ = &Server{
|
|
templates: &Templates{},
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkFirstRender(b *testing.B) {
|
|
for b.Loop() {
|
|
t := &Templates{}
|
|
w := httptest.NewRecorder()
|
|
if err := t.Render(w, "dashboard", DashboardData{}); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// thirdPartyAsset matches a script or stylesheet loaded from another origin.
|
|
var thirdPartyAsset = regexp.MustCompile(`<(?:script[^>]*\ssrc|link[^>]*\shref)="(?:https?:)?//[^"]*"`)
|
|
|
|
// Pages run on the proxy origin, so a script or stylesheet from elsewhere
|
|
// would run with the proxy's privileges and break offline deployments.
|
|
// Vendor it under static/vendor instead.
|
|
func TestTemplatesLoadNoThirdPartyAssets(t *testing.T) {
|
|
err := fs.WalkDir(templatesFS, "templates", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return err
|
|
}
|
|
content, err := templatesFS.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, match := range thirdPartyAsset.FindAllString(string(content), -1) {
|
|
t.Errorf("%s loads a third-party asset: %s", path, match)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|