server: integration tests for route reuse and upstream fallback

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I0d476da19941238b32c6adf87dac2d876a6a6964
This commit is contained in:
raf 2026-03-06 17:00:38 +03:00
commit 2a0c30f953
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -0,0 +1,97 @@
package server_test
import (
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"notashelf.dev/ncro/internal/cache"
"notashelf.dev/ncro/internal/config"
"notashelf.dev/ncro/internal/prober"
"notashelf.dev/ncro/internal/router"
"notashelf.dev/ncro/internal/server"
)
// Verifies that the second identical narinfo request uses the cached route.
func TestRouteReuseOnSecondRequest(t *testing.T) {
requestCount := 0
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, ".narinfo") {
requestCount++
w.Header().Set("Content-Type", "text/x-nix-narinfo")
io.WriteString(w, "StorePath: /nix/store/test-pkg\nURL: nar/test.nar\n")
return
}
w.WriteHeader(404)
}))
defer upstream.Close()
f, _ := os.CreateTemp("", "ncro-int-*.db")
f.Close()
defer os.Remove(f.Name())
db, _ := cache.Open(f.Name(), 1000)
defer db.Close()
p := prober.New(0.3)
p.RecordLatency(upstream.URL, 10)
r := router.New(db, p, time.Hour, 5*time.Second)
ts := httptest.NewServer(server.New(r, p, []config.UpstreamConfig{{URL: upstream.URL}}))
defer ts.Close()
resp1, _ := http.Get(ts.URL + "/deadbeef00000000.narinfo")
io.Copy(io.Discard, resp1.Body)
resp1.Body.Close()
resp2, _ := http.Get(ts.URL + "/deadbeef00000000.narinfo")
io.Copy(io.Discard, resp2.Body)
resp2.Body.Close()
if resp1.StatusCode != 200 || resp2.StatusCode != 200 {
t.Errorf("expected 200/200, got %d/%d", resp1.StatusCode, resp2.StatusCode)
}
}
// Verifies that when the best-seeded upstream returns 404, the fallback upstream is used.
func TestUpstreamFailoverFallback(t *testing.T) {
good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/x-nix-narinfo")
io.WriteString(w, "StorePath: /nix/store/fallback-pkg\n")
}))
defer good.Close()
bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
}))
defer bad.Close()
f, _ := os.CreateTemp("", "ncro-fb-*.db")
f.Close()
defer os.Remove(f.Name())
db, _ := cache.Open(f.Name(), 1000)
defer db.Close()
p := prober.New(0.3)
p.RecordLatency(bad.URL, 1) // bad appears fastest
p.RecordLatency(good.URL, 50)
r := router.New(db, p, time.Hour, 5*time.Second)
ts := httptest.NewServer(server.New(r, p, []config.UpstreamConfig{
{URL: bad.URL},
{URL: good.URL},
}))
defer ts.Close()
resp, err := http.Get(ts.URL + "/cafebabe00000000.narinfo")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("expected 200 via fallback, got %d", resp.StatusCode)
}
}