187 lines
5.7 KiB
Go
187 lines
5.7 KiB
Go
package rpm
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.unkin.net/unkin/artifactapi/internal/provider"
|
|
"git.unkin.net/unkin/artifactapi/pkg/models"
|
|
)
|
|
|
|
// fpStore adds a fingerprint and a full-read counter to fakeStore.
|
|
type fpStore struct {
|
|
*fakeStore
|
|
lists int
|
|
seq int64
|
|
}
|
|
|
|
func (s *fpStore) ListRPMMetadataEntries(ctx context.Context, name string) ([]provider.RPMMetadata, error) {
|
|
s.mu.Lock()
|
|
s.lists++
|
|
s.mu.Unlock()
|
|
return s.fakeStore.ListRPMMetadataEntries(ctx, name)
|
|
}
|
|
|
|
func (s *fpStore) InsertRPMMetadata(ctx context.Context, m *provider.RPMMetadata) error {
|
|
s.mu.Lock()
|
|
_, exists := s.rows[m.FilePath]
|
|
s.mu.Unlock()
|
|
if err := s.fakeStore.InsertRPMMetadata(ctx, m); err != nil || exists {
|
|
return err // ON CONFLICT DO NOTHING leaves max(id) unchanged
|
|
}
|
|
s.mu.Lock()
|
|
s.seq++
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *fpStore) RPMMetadataFingerprint(_ context.Context, _ string) (int64, int64, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return int64(len(s.rows)), s.seq, nil
|
|
}
|
|
|
|
func (s *fpStore) listCount() int {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.lists
|
|
}
|
|
|
|
func newCacheFixture(t *testing.T, cache bool) (*GitHubProvider, *fpStore, models.Remote) {
|
|
t.Helper()
|
|
remote := models.Remote{Name: "cached-rpm", PackageType: models.PackageGitHubRPM, MutableTTL: 3600, CacheRepodata: cache}
|
|
p := newGitHubProvider()
|
|
p.lastScan[remote.Name] = time.Now()
|
|
store := &fpStore{fakeStore: newFakeStore()}
|
|
addPkg(t, store, "alpha")
|
|
return p, store, remote
|
|
}
|
|
|
|
func addPkg(t *testing.T, store *fpStore, name string) {
|
|
t.Helper()
|
|
if err := store.InsertRPMMetadata(context.Background(), &provider.RPMMetadata{
|
|
RepoName: "cached-rpm", FilePath: name + "-1-1.noarch.rpm", ContentHash: "sha256:" + name,
|
|
Name: name, Version: "1", Release: "1", Arch: "noarch", Group: "Unspecified",
|
|
Files: []provider.RPMFile{{Path: "/usr/bin/" + name}},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func serve(t *testing.T, p *GitHubProvider, remote models.Remote, store *fpStore, path string) []byte {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/x", nil)
|
|
if !p.ServeRemote(rec, req, remote, path, "https://proxy.example", store) {
|
|
t.Fatalf("%s not handled", path)
|
|
}
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("%s: code %d: %s", path, rec.Code, rec.Body.String())
|
|
}
|
|
return rec.Body.Bytes()
|
|
}
|
|
|
|
var repodataPaths = []string{
|
|
"repodata/repomd.xml",
|
|
"repodata/x-primary.xml.gz",
|
|
"repodata/x-filelists.xml.gz",
|
|
"repodata/x-other.xml.gz",
|
|
}
|
|
|
|
func TestCachedRepodataHitSkipsRegeneration(t *testing.T) {
|
|
p, store, remote := newCacheFixture(t, true)
|
|
|
|
repomd := serve(t, p, remote, store, "repodata/repomd.xml")
|
|
primary := serve(t, p, remote, store, "repodata/x-primary.xml.gz")
|
|
for _, path := range repodataPaths {
|
|
serve(t, p, remote, store, path)
|
|
}
|
|
if n := store.listCount(); n != 1 {
|
|
t.Fatalf("full metadata reads = %d, want 1", n)
|
|
}
|
|
if !strings.Contains(string(repomd), sha256Hex(primary)+"-primary.xml.gz") {
|
|
t.Fatalf("cached repomd does not reference cached primary:\n%s", repomd)
|
|
}
|
|
}
|
|
|
|
func TestCachedRepodataRebuildsOnDataChange(t *testing.T) {
|
|
p, store, remote := newCacheFixture(t, true)
|
|
before := serve(t, p, remote, store, "repodata/repomd.xml")
|
|
|
|
addPkg(t, store, "beta")
|
|
after := serve(t, p, remote, store, "repodata/repomd.xml")
|
|
primary := serve(t, p, remote, store, "repodata/x-primary.xml.gz")
|
|
|
|
if string(before) == string(after) {
|
|
t.Fatal("repomd unchanged after metadata change")
|
|
}
|
|
if !strings.Contains(string(after), sha256Hex(primary)+"-primary.xml.gz") {
|
|
t.Fatal("rebuilt repomd does not reference rebuilt primary")
|
|
}
|
|
if !strings.Contains(gunzip(t, primary), "<name>beta</name>") {
|
|
t.Fatal("rebuilt primary missing new package")
|
|
}
|
|
if n := store.listCount(); n != 2 {
|
|
t.Fatalf("full metadata reads = %d, want 2", n)
|
|
}
|
|
}
|
|
|
|
func TestUncachedRepodataMatchesCachedAndReadsEveryTime(t *testing.T) {
|
|
p, store, remote := newCacheFixture(t, true)
|
|
cached := map[string]string{}
|
|
for _, path := range repodataPaths {
|
|
cached[path] = string(serve(t, p, remote, store, path))
|
|
}
|
|
|
|
remote.CacheRepodata = false
|
|
reads := store.listCount()
|
|
for _, path := range repodataPaths {
|
|
if got := string(serve(t, p, remote, store, path)); got != cached[path] {
|
|
t.Fatalf("%s differs between cached and uncached", path)
|
|
}
|
|
}
|
|
if n := store.listCount() - reads; n != len(repodataPaths) {
|
|
t.Fatalf("uncached full reads = %d, want %d", n, len(repodataPaths))
|
|
}
|
|
if _, ok := p.repodataCache[remote.Name]; ok {
|
|
t.Fatal("disabling cache_repodata should drop the cached index")
|
|
}
|
|
}
|
|
|
|
func TestCachedRepodataRebuildsOnSameCountReinsert(t *testing.T) {
|
|
p, store, remote := newCacheFixture(t, true)
|
|
addPkg(t, store, "alpha") // conflicting insert: fingerprint must not move
|
|
serve(t, p, remote, store, "repodata/repomd.xml")
|
|
serve(t, p, remote, store, "repodata/repomd.xml")
|
|
if n := store.listCount(); n != 1 {
|
|
t.Fatalf("full reads after conflicting insert = %d, want 1", n)
|
|
}
|
|
|
|
if err := store.DeleteRPMMetadata(context.Background(), remote.Name, "alpha-1-1.noarch.rpm"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
addPkg(t, store, "alpha")
|
|
serve(t, p, remote, store, "repodata/repomd.xml")
|
|
if n := store.listCount(); n != 2 {
|
|
t.Fatalf("full reads after same-count reinsert = %d, want 2", n)
|
|
}
|
|
}
|
|
|
|
func TestCachedRepodataEvictsIdleRemotes(t *testing.T) {
|
|
p, store, remote := newCacheFixture(t, true)
|
|
serve(t, p, remote, store, "repodata/repomd.xml")
|
|
p.repodataCache["gone"] = cachedRepodata{used: time.Now().Add(-2 * repodataIdleTTL), repodata: &repodata{}}
|
|
|
|
serve(t, p, remote, store, "repodata/repomd.xml")
|
|
if _, ok := p.repodataCache["gone"]; ok {
|
|
t.Fatal("idle remote's index not evicted")
|
|
}
|
|
if _, ok := p.repodataCache[remote.Name]; !ok {
|
|
t.Fatal("active remote's index evicted")
|
|
}
|
|
}
|