repo-sync/server/classify_test.go
James Coleman 1e6b8ed0ce
Some checks failed
Go package / build (push) Has been cancelled
fix(server): route pacman database signatures through the crawl
- Classify core.db.sig as an Arch entry point so it is gated on the database it signs. Served as a plain file it could be revalidated on its own, and pacman fetching core.db then core.db.sig would receive a rotated signature paired with the database the mirror still holds, failing verification on every client until the next crawl.
- Keep a repository registered when its crawl verified and published the tree but the requested entry point is absent upstream. Deregistering on that miss dropped every member file to the generic path, which refreshes files individually with no checksum or signature check.
- Drop the generic state entry for a file a registered repository now owns instead of evicting the file. The entry is left by a request that predates the registration and nothing refreshes it, so expiry deleted a file out of the verified tree and forced the next request into a synchronous crawl.
- Release 0.1.2.

Claude-Session: https://claude.ai/code/session_01FkruwxzDGY4BoXp1Zzoott
2026-09-11 06:57:58 -05:00

70 lines
3 KiB
Go

package server
import (
"testing"
"time"
"github.com/grmrgecko/repo-sync/state"
)
// TestClassifyRequest verifies request paths map to the right resource
// kinds, crawl paths, and keep-alive roots.
func TestClassifyRequest(t *testing.T) {
cases := []struct {
reqPath string
kind string
path string
root string
}{
{"/almalinux/9/BaseOS/x86_64/os/repodata/repomd.xml", "rpm", "/almalinux/9/BaseOS/x86_64/os", "/almalinux/9/BaseOS/x86_64/os"},
{"/almalinux/9/BaseOS/x86_64/os/repodata/abc-primary.xml.gz", "rpm", "/almalinux/9/BaseOS/x86_64/os", "/almalinux/9/BaseOS/x86_64/os"},
{"/debian/dists/bookworm/InRelease", "deb", "/debian/dists/bookworm", "/debian"},
{"/debian/dists/stable/updates/Release", "deb", "/debian/dists/stable/updates", "/debian"},
{"/flat/Release", "deb", "/flat", "/flat"},
{"/archlinux/core/os/x86_64/core.db", "arch", "/archlinux/core/os/x86_64", "/archlinux/core/os/x86_64"},
{"/archlinux/core/os/x86_64/core.db.sig", "arch", "/archlinux/core/os/x86_64", "/archlinux/core/os/x86_64"},
{"/archlinux/core/os/x86_64/zlib-1.3-1-x86_64.pkg.tar.zst.sig", kindGeneric, "/archlinux/core/os/x86_64/zlib-1.3-1-x86_64.pkg.tar.zst.sig", ""},
{"/alpine/v3.24/main/x86_64/APKINDEX.tar.gz", "apk", "/alpine/v3.24/main/x86_64", "/alpine/v3.24/main/x86_64"},
{"/almalinux/9/BaseOS/x86_64/os/Packages/foo.rpm", kindGeneric, "/almalinux/9/BaseOS/x86_64/os/Packages/foo.rpm", ""},
{"/notes/README.txt", kindGeneric, "/notes/README.txt", ""},
}
for _, c := range cases {
res := classifyRequest(c.reqPath)
if res.Kind != c.kind || res.Path != c.path || res.Root != c.root {
t.Errorf("classifyRequest(%q) = {%s %s %s}, want {%s %s %s}",
c.reqPath, res.Kind, res.Path, res.Root, c.kind, c.path, c.root)
}
}
}
// TestRefreshTier verifies the tier walk and staleness budget.
func TestRefreshTier(t *testing.T) {
schedule := []time.Duration{4 * time.Hour, 12 * time.Hour, 24 * time.Hour}
if interval, stale := refreshTier(time.Hour, schedule); stale || interval != 4*time.Hour {
t.Errorf("fresh resource tier = %v, %v", interval, stale)
}
if interval, stale := refreshTier(10*time.Hour, schedule); stale || interval != 12*time.Hour {
t.Errorf("aging resource tier = %v, %v", interval, stale)
}
if _, stale := refreshTier(41*time.Hour, schedule); !stale {
t.Error("resource past the budget not stale")
}
}
// TestNextCrawlAt verifies crawl gating for fresh, failed, and deferred
// entries.
func TestNextCrawlAt(t *testing.T) {
now := time.Now()
if !nextCrawlAt(state.Entry{}, time.Hour).IsZero() {
t.Error("never-crawled entry not due immediately")
}
failedAt := now.Add(30 * time.Minute)
failed := state.Entry{LastError: "boom", LastCrawled: now, NextCrawl: failedAt}
if got := nextCrawlAt(failed, time.Hour); !got.Equal(failedAt) {
t.Errorf("failed entry gate = %v, want %v", got, failedAt)
}
ok := state.Entry{LastCrawled: now, NextCrawl: now.Add(10 * time.Minute)}
if got := nextCrawlAt(ok, 2*time.Hour); !got.Equal(now.Add(2 * time.Hour)) {
t.Errorf("tier gate = %v, want %v", got, now.Add(2*time.Hour))
}
}