pkg-proxy/internal/handler/gem_test.go

252 lines
6.8 KiB
Go
Raw Permalink Normal View History

2026-01-20 21:52:44 +00:00
package handler
import (
"encoding/json"
"fmt"
2026-01-20 21:52:44 +00:00
"log/slog"
"net/http"
"net/http/httptest"
"strings"
2026-01-20 21:52:44 +00:00
"testing"
"time"
"github.com/git-pkgs/proxy/internal/cooldown"
2026-01-20 21:52:44 +00:00
)
func TestGemParseFilename(t *testing.T) {
h := &GemHandler{proxy: &Proxy{Logger: slog.Default()}}
tests := []struct {
filename string
wantName string
wantVersion string
}{
{"rails-7.1.0.gem", "rails", "7.1.0"},
{"aws-sdk-s3-1.142.0.gem", "aws-sdk-s3", "1.142.0"},
{"nokogiri-1.15.4-x86_64-linux.gem", "nokogiri", "1.15.4-x86_64-linux"},
{"activerecord-7.0.8.gem", "activerecord", "7.0.8"},
{"invalid", "", ""},
}
for _, tt := range tests {
name, version := h.parseGemFilename(tt.filename)
if name != tt.wantName || version != tt.wantVersion {
t.Errorf("parseGemFilename(%q) = (%q, %q), want (%q, %q)",
tt.filename, name, version, tt.wantName, tt.wantVersion)
}
}
}
func TestGemCompactIndexCooldown(t *testing.T) {
now := time.Now()
oldTime := now.Add(-7 * 24 * time.Hour).Format(time.RFC3339)
recentTime := now.Add(-1 * time.Hour).Format(time.RFC3339)
compactIndex := "---\n1.0.0 dep1:>= 1.0|checksum:abc123\n2.0.0 dep1:>= 1.0|checksum:def456\n"
versionsJSON, _ := json.Marshal([]gemVersion{
{Number: "1.0.0", Platform: "ruby", CreatedAt: oldTime},
{Number: "2.0.0", Platform: "ruby", CreatedAt: recentTime},
})
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasPrefix(r.URL.Path, "/info/"):
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte(compactIndex))
case strings.HasPrefix(r.URL.Path, "/api/v1/versions/"):
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(versionsJSON)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer upstream.Close()
proxy := testProxy()
proxy.Cooldown = &cooldown.Config{
Default: "3d",
}
h := &GemHandler{
proxy: proxy,
upstreamURL: upstream.URL,
proxyURL: "http://proxy.local",
}
req := httptest.NewRequest(http.MethodGet, "/info/testgem", nil)
req.SetPathValue("name", "testgem")
w := httptest.NewRecorder()
h.handleCompactIndex(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
body := w.Body.String()
if !strings.Contains(body, "1.0.0") {
t.Error("expected version 1.0.0 to survive filtering")
}
if strings.Contains(body, "2.0.0") {
t.Error("expected version 2.0.0 to be filtered out")
}
if !strings.HasPrefix(body, "---\n") {
t.Error("expected compact index header to be preserved")
}
}
func TestGemCompactIndexCooldownWithPlatformVersion(t *testing.T) {
now := time.Now()
recentTime := now.Add(-1 * time.Hour).Format(time.RFC3339)
compactIndex := "---\n1.0.0 dep:>= 1.0|checksum:abc\n1.0.0-java dep:>= 1.0|checksum:def\n"
versionsJSON, _ := json.Marshal([]gemVersion{
{Number: "1.0.0", Platform: "ruby", CreatedAt: recentTime},
{Number: "1.0.0", Platform: "java", CreatedAt: recentTime},
})
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasPrefix(r.URL.Path, "/info/"):
_, _ = w.Write([]byte(compactIndex))
case strings.HasPrefix(r.URL.Path, "/api/v1/versions/"):
_, _ = w.Write(versionsJSON)
}
}))
defer upstream.Close()
proxy := testProxy()
proxy.Cooldown = &cooldown.Config{
Default: "3d",
}
h := &GemHandler{
proxy: proxy,
upstreamURL: upstream.URL,
proxyURL: "http://proxy.local",
}
req := httptest.NewRequest(http.MethodGet, "/info/testgem", nil)
req.SetPathValue("name", "testgem")
w := httptest.NewRecorder()
h.handleCompactIndex(w, req)
body := w.Body.String()
// Both ruby and java platform versions should be filtered
lines := strings.Split(strings.TrimSpace(body), "\n")
if len(lines) != 1 { // only "---"
t.Errorf("expected only header line, got %d lines: %v", len(lines), lines)
}
}
func TestGemCompactIndexNoCooldown(t *testing.T) {
compactIndex := "---\n1.0.0 dep:>= 1.0|checksum:abc\n"
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(compactIndex))
}))
defer upstream.Close()
h := &GemHandler{
proxy: testProxy(), // no cooldown
upstreamURL: upstream.URL,
proxyURL: "http://proxy.local",
}
req := httptest.NewRequest(http.MethodGet, "/info/testgem", nil)
req.SetPathValue("name", "testgem")
w := httptest.NewRecorder()
h.handleCompactIndex(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
}
func TestGemCompactIndexVersionsAPIFails(t *testing.T) {
compactIndex := "---\n1.0.0 dep:>= 1.0|checksum:abc\n"
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasPrefix(r.URL.Path, "/info/"):
_, _ = w.Write([]byte(compactIndex))
case strings.HasPrefix(r.URL.Path, "/api/v1/versions/"):
w.WriteHeader(http.StatusInternalServerError)
}
}))
defer upstream.Close()
proxy := testProxy()
proxy.Cooldown = &cooldown.Config{
Default: "3d",
}
h := &GemHandler{
proxy: proxy,
upstreamURL: upstream.URL,
proxyURL: "http://proxy.local",
}
req := httptest.NewRequest(http.MethodGet, "/info/testgem", nil)
req.SetPathValue("name", "testgem")
w := httptest.NewRecorder()
h.handleCompactIndex(w, req)
// Should still return OK with unfiltered content
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", w.Code, http.StatusOK)
}
body := w.Body.String()
if !strings.Contains(body, "1.0.0") {
t.Error("expected unfiltered content when versions API fails")
}
}
func TestGemFetchFilteredVersions(t *testing.T) {
now := time.Now()
oldTime := now.Add(-7 * 24 * time.Hour).Format(time.RFC3339)
recentTime := now.Add(-1 * time.Hour).Format(time.RFC3339)
versionsJSON, _ := json.Marshal([]gemVersion{
{Number: "1.0.0", Platform: "ruby", CreatedAt: oldTime},
{Number: "2.0.0", Platform: "ruby", CreatedAt: recentTime},
{Number: "2.0.0", Platform: "java", CreatedAt: recentTime},
})
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(versionsJSON)
}))
defer upstream.Close()
proxy := testProxy()
proxy.Cooldown = &cooldown.Config{
Default: "3d",
}
h := &GemHandler{
proxy: proxy,
upstreamURL: upstream.URL,
proxyURL: "http://proxy.local",
}
req := httptest.NewRequest(http.MethodGet, "/info/testgem", nil)
filtered, err := h.fetchFilteredVersions(req, "testgem")
if err != nil {
t.Fatal(err)
}
if filtered["1.0.0"] {
t.Error("version 1.0.0 should not be filtered (old enough)")
}
if !filtered["2.0.0"] {
t.Error("version 2.0.0 (ruby) should be filtered")
}
if !filtered["2.0.0-java"] {
t.Error("version 2.0.0-java should be filtered")
}
_ = fmt.Sprintf // silence unused import
}