2026-01-29 19:35:15 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2026-02-03 22:40:23 +00:00
|
|
|
"database/sql"
|
2026-01-29 19:35:15 +00:00
|
|
|
"encoding/json"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"os"
|
2026-02-03 22:40:23 +00:00
|
|
|
"path/filepath"
|
2026-01-29 19:35:15 +00:00
|
|
|
"testing"
|
|
|
|
|
|
2026-02-03 22:40:23 +00:00
|
|
|
"github.com/git-pkgs/proxy/internal/database"
|
2026-01-29 19:35:15 +00:00
|
|
|
"github.com/git-pkgs/proxy/internal/enrichment"
|
Add handler download flow and server utility tests
Covers HTTP download paths for gem, hex, go, conda, cran, and maven
handlers with cache hit, invalid input, and upstream proxy scenarios.
Adds server tests for formatTimeAgo, formatSize, categorizeLicense,
LoggerMiddleware, search/pagination, and API packages list endpoint.
2026-03-17 20:31:54 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
2026-01-29 19:35:15 +00:00
|
|
|
)
|
|
|
|
|
|
2026-03-18 10:59:29 +00:00
|
|
|
const testEcosystemNPM = "npm"
|
|
|
|
|
|
2026-01-29 19:35:15 +00:00
|
|
|
func TestNewAPIHandler(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
if h == nil {
|
|
|
|
|
t.Fatal("NewAPIHandler returned nil")
|
|
|
|
|
}
|
|
|
|
|
if h.enrichment == nil {
|
|
|
|
|
t.Error("enrichment service is nil")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
func TestHandlePackagePath_MissingParams(t *testing.T) {
|
2026-01-29 19:35:15 +00:00
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
r := chi.NewRouter()
|
|
|
|
|
r.Get("/api/package/{ecosystem}/*", h.HandlePackagePath)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
req := httptest.NewRequest("GET", "/api/package//", nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
w := httptest.NewRecorder()
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
r.ServeHTTP(w, req)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
if w.Code != http.StatusBadRequest && w.Code != http.StatusNotFound {
|
|
|
|
|
t.Errorf("expected status 400 or 404, got %d", w.Code)
|
2026-01-29 19:35:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
func TestHandleVulnsPath_MissingParams(t *testing.T) {
|
2026-01-29 19:35:15 +00:00
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
r := chi.NewRouter()
|
|
|
|
|
r.Get("/api/vulns/{ecosystem}/*", h.HandleVulnsPath)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
req := httptest.NewRequest("GET", "/api/vulns//", nil)
|
|
|
|
|
w := httptest.NewRecorder()
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
r.ServeHTTP(w, req)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
if w.Code != http.StatusBadRequest && w.Code != http.StatusNotFound {
|
|
|
|
|
t.Errorf("expected status 400 or 404, got %d", w.Code)
|
2026-01-29 19:35:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHandleOutdated_EmptyBody(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
req := httptest.NewRequest("POST", "/api/outdated", bytes.NewReader([]byte("{}")))
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleOutdated(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status %d, got %d", http.StatusBadRequest, w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 12:04:12 +00:00
|
|
|
func TestHandleOutdated_OversizedBody(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
|
|
|
|
h := NewAPIHandler(svc, nil)
|
|
|
|
|
|
|
|
|
|
// Send a body larger than 1 MB
|
|
|
|
|
body := make([]byte, 2<<20)
|
|
|
|
|
for i := range body {
|
|
|
|
|
body[i] = 'x'
|
|
|
|
|
}
|
|
|
|
|
req := httptest.NewRequest("POST", "/api/outdated", bytes.NewReader(body))
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleOutdated(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status %d for oversized body, got %d", http.StatusBadRequest, w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 19:35:15 +00:00
|
|
|
func TestHandleOutdated_InvalidJSON(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
req := httptest.NewRequest("POST", "/api/outdated", bytes.NewReader([]byte("not json")))
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleOutdated(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status %d, got %d", http.StatusBadRequest, w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHandleBulkLookup_EmptyBody(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
req := httptest.NewRequest("POST", "/api/bulk", bytes.NewReader([]byte("{}")))
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleBulkLookup(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status %d, got %d", http.StatusBadRequest, w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHandleBulkLookup_InvalidJSON(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
2026-02-03 22:40:23 +00:00
|
|
|
h := NewAPIHandler(svc, nil)
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
req := httptest.NewRequest("POST", "/api/bulk", bytes.NewReader([]byte("not json")))
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleBulkLookup(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status %d, got %d", http.StatusBadRequest, w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWriteJSON(t *testing.T) {
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
|
|
data := map[string]string{"foo": "bar"}
|
|
|
|
|
writeJSON(w, data)
|
|
|
|
|
|
|
|
|
|
if w.Header().Get("Content-Type") != "application/json" {
|
|
|
|
|
t.Errorf("expected Content-Type application/json, got %s", w.Header().Get("Content-Type"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result map[string]string
|
|
|
|
|
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
|
|
|
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if result["foo"] != "bar" {
|
|
|
|
|
t.Errorf("expected foo=bar, got foo=%s", result["foo"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPackageResponseJSON(t *testing.T) {
|
|
|
|
|
resp := &PackageResponse{
|
2026-03-18 10:59:29 +00:00
|
|
|
Ecosystem: testEcosystemNPM,
|
2026-01-29 19:35:15 +00:00
|
|
|
Name: "lodash",
|
|
|
|
|
LatestVersion: "4.17.21",
|
|
|
|
|
License: "MIT",
|
|
|
|
|
LicenseCategory: "permissive",
|
|
|
|
|
Description: "A utility library",
|
|
|
|
|
Homepage: "https://lodash.com",
|
|
|
|
|
Repository: "https://github.com/lodash/lodash",
|
|
|
|
|
RegistryURL: "https://registry.npmjs.org",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var decoded PackageResponse
|
|
|
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
|
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 10:59:29 +00:00
|
|
|
if decoded.Ecosystem != testEcosystemNPM {
|
2026-01-29 19:35:15 +00:00
|
|
|
t.Errorf("expected ecosystem npm, got %s", decoded.Ecosystem)
|
|
|
|
|
}
|
|
|
|
|
if decoded.Name != "lodash" {
|
|
|
|
|
t.Errorf("expected name lodash, got %s", decoded.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestVulnResponseJSON(t *testing.T) {
|
|
|
|
|
resp := &VulnResponse{
|
|
|
|
|
ID: "CVE-2021-1234",
|
|
|
|
|
Summary: "Test vulnerability",
|
|
|
|
|
Severity: "HIGH",
|
|
|
|
|
CVSSScore: 8.5,
|
|
|
|
|
FixedVersion: "1.2.3",
|
|
|
|
|
References: []string{"https://example.com/advisory"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var decoded VulnResponse
|
|
|
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
|
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if decoded.ID != "CVE-2021-1234" {
|
|
|
|
|
t.Errorf("expected ID CVE-2021-1234, got %s", decoded.ID)
|
|
|
|
|
}
|
|
|
|
|
if decoded.CVSSScore != 8.5 {
|
|
|
|
|
t.Errorf("expected CVSS 8.5, got %f", decoded.CVSSScore)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOutdatedRequestJSON(t *testing.T) {
|
|
|
|
|
req := &OutdatedRequest{
|
|
|
|
|
Packages: []OutdatedPackage{
|
|
|
|
|
{Ecosystem: "npm", Name: "lodash", Version: "4.17.0"},
|
|
|
|
|
{Ecosystem: "pypi", Name: "requests", Version: "2.25.0"},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := json.Marshal(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var decoded OutdatedRequest
|
|
|
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
|
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(decoded.Packages) != 2 {
|
|
|
|
|
t.Errorf("expected 2 packages, got %d", len(decoded.Packages))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBulkRequestJSON(t *testing.T) {
|
|
|
|
|
req := &BulkRequest{
|
|
|
|
|
PURLs: []string{
|
|
|
|
|
"pkg:npm/lodash@4.17.21",
|
|
|
|
|
"pkg:pypi/requests@2.28.0",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := json.Marshal(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var decoded BulkRequest
|
|
|
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
|
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(decoded.PURLs) != 2 {
|
|
|
|
|
t.Errorf("expected 2 purls, got %d", len(decoded.PURLs))
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-03 22:40:23 +00:00
|
|
|
|
|
|
|
|
func TestHandleSearch_MissingQuery(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
|
|
|
|
h := NewAPIHandler(svc, nil)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest("GET", "/api/search", nil)
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleSearch(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status %d, got %d", http.StatusBadRequest, w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHandleSearch_WithNullValues(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
|
|
|
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
dbPath := filepath.Join(dir, "test.db")
|
|
|
|
|
|
|
|
|
|
db, err := database.Create(dbPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Create failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer func() { _ = db.Close() }()
|
|
|
|
|
|
|
|
|
|
pkg := &database.Package{
|
|
|
|
|
PURL: "pkg:npm/api-test",
|
2026-03-18 10:59:29 +00:00
|
|
|
Ecosystem: testEcosystemNPM,
|
2026-02-03 22:40:23 +00:00
|
|
|
Name: "api-test",
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpsertPackage(pkg); err != nil {
|
|
|
|
|
t.Fatalf("UpsertPackage failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ver := &database.Version{
|
|
|
|
|
PURL: "pkg:npm/api-test@1.0.0",
|
|
|
|
|
PackagePURL: pkg.PURL,
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpsertVersion(ver); err != nil {
|
|
|
|
|
t.Fatalf("UpsertVersion failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
artifact := &database.Artifact{
|
|
|
|
|
VersionPURL: ver.PURL,
|
|
|
|
|
Filename: "api-test-1.0.0.tgz",
|
|
|
|
|
UpstreamURL: "https://registry.npmjs.org/api-test/-/api-test-1.0.0.tgz",
|
|
|
|
|
StoragePath: sql.NullString{String: "./cache/test.tgz", Valid: true},
|
|
|
|
|
HitCount: 3,
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpsertArtifact(artifact); err != nil {
|
|
|
|
|
t.Fatalf("UpsertArtifact failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h := NewAPIHandler(svc, db)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest("GET", "/api/search?q=api-test", nil)
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.HandleSearch(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resp SearchResponse
|
|
|
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
|
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(resp.Results) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 result, got %d", len(resp.Results))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := resp.Results[0]
|
|
|
|
|
if result.Name != "api-test" {
|
|
|
|
|
t.Errorf("expected name api-test, got %s", result.Name)
|
|
|
|
|
}
|
|
|
|
|
if result.LatestVersion != "" {
|
|
|
|
|
t.Errorf("expected empty LatestVersion, got %s", result.LatestVersion)
|
|
|
|
|
}
|
|
|
|
|
if result.License != "" {
|
|
|
|
|
t.Errorf("expected empty License, got %s", result.License)
|
|
|
|
|
}
|
|
|
|
|
if result.Hits != 3 {
|
|
|
|
|
t.Errorf("expected 3 hits, got %d", result.Hits)
|
|
|
|
|
}
|
|
|
|
|
}
|
Add handler download flow and server utility tests
Covers HTTP download paths for gem, hex, go, conda, cran, and maven
handlers with cache hit, invalid input, and upstream proxy scenarios.
Adds server tests for formatTimeAgo, formatSize, categorizeLicense,
LoggerMiddleware, search/pagination, and API packages list endpoint.
2026-03-17 20:31:54 +00:00
|
|
|
|
|
|
|
|
func TestHandlePackagesListAPI(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
|
|
|
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
dbPath := filepath.Join(dir, "test.db")
|
|
|
|
|
|
|
|
|
|
db, err := database.Create(dbPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Create failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer func() { _ = db.Close() }()
|
|
|
|
|
|
|
|
|
|
// Seed two packages
|
|
|
|
|
for _, name := range []string{"api-list-one", "api-list-two"} {
|
|
|
|
|
pkg := &database.Package{
|
|
|
|
|
PURL: "pkg:npm/" + name,
|
2026-03-18 10:59:29 +00:00
|
|
|
Ecosystem: testEcosystemNPM,
|
Add handler download flow and server utility tests
Covers HTTP download paths for gem, hex, go, conda, cran, and maven
handlers with cache hit, invalid input, and upstream proxy scenarios.
Adds server tests for formatTimeAgo, formatSize, categorizeLicense,
LoggerMiddleware, search/pagination, and API packages list endpoint.
2026-03-17 20:31:54 +00:00
|
|
|
Name: name,
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpsertPackage(pkg); err != nil {
|
|
|
|
|
t.Fatalf("UpsertPackage failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
ver := &database.Version{
|
|
|
|
|
PURL: "pkg:npm/" + name + "@1.0.0",
|
|
|
|
|
PackagePURL: pkg.PURL,
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpsertVersion(ver); err != nil {
|
|
|
|
|
t.Fatalf("UpsertVersion failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
art := &database.Artifact{
|
|
|
|
|
VersionPURL: ver.PURL,
|
|
|
|
|
Filename: name + "-1.0.0.tgz",
|
|
|
|
|
UpstreamURL: "https://registry.npmjs.org/" + name + "/-/" + name + "-1.0.0.tgz",
|
|
|
|
|
StoragePath: sql.NullString{String: "/tmp/test.tgz", Valid: true},
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpsertArtifact(art); err != nil {
|
|
|
|
|
t.Fatalf("UpsertArtifact failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h := NewAPIHandler(svc, db)
|
|
|
|
|
|
|
|
|
|
r := chi.NewRouter()
|
|
|
|
|
r.Get("/api/packages", h.HandlePackagesList)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest("GET", "/api/packages", nil)
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
r.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resp PackagesListResponse
|
|
|
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
|
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(resp.Results) < 2 {
|
|
|
|
|
t.Fatalf("expected at least 2 results, got %d", len(resp.Results))
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 10:59:29 +00:00
|
|
|
if resp.SortBy != defaultSortBy {
|
Add handler download flow and server utility tests
Covers HTTP download paths for gem, hex, go, conda, cran, and maven
handlers with cache hit, invalid input, and upstream proxy scenarios.
Adds server tests for formatTimeAgo, formatSize, categorizeLicense,
LoggerMiddleware, search/pagination, and API packages list endpoint.
2026-03-17 20:31:54 +00:00
|
|
|
t.Errorf("expected default sort by hits, got %q", resp.SortBy)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
found := false
|
|
|
|
|
for _, pkg := range resp.Results {
|
|
|
|
|
if pkg.Name == "api-list-one" || pkg.Name == "api-list-two" {
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
t.Error("expected seeded packages in results")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHandlePackagesListAPI_InvalidSort(t *testing.T) {
|
|
|
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
|
|
|
svc := enrichment.New(logger)
|
|
|
|
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
dbPath := filepath.Join(dir, "test.db")
|
|
|
|
|
|
|
|
|
|
db, err := database.Create(dbPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Create failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
defer func() { _ = db.Close() }()
|
|
|
|
|
|
|
|
|
|
h := NewAPIHandler(svc, db)
|
|
|
|
|
|
|
|
|
|
r := chi.NewRouter()
|
|
|
|
|
r.Get("/api/packages", h.HandlePackagesList)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest("GET", "/api/packages?sort=invalid", nil)
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
r.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected status 400 for invalid sort, got %d", w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|