822f356881
## Why Switching a remote's backend (`base_url`) left artifactapi serving the previously-cached mutable metadata (repodata / Release / APKINDEX) until TTL expiry, so requests could keep pointing at the old upstream. `cache.FlushRemote` already existed but was wired to nothing. ## How - Inject a `MetadataFlusher` (satisfied by `*cache.Redis`) into `RemotesHandler` via `NewRemotesHandler`; `server.go` passes `s.cache`. - On update, read the existing remote first, then after a successful DB update flush the remote's cached metadata when `base_url` changed, so the next request re-fetches fresh from the new upstream. - Keep scope to `base_url` (upstream identity); a flush failure is logged as a warning and does not fail the request since the DB update already landed. - Add tests: a `base_url` change flushes exactly once, an unchanged `base_url` does not flush, and a flush error still returns 200. Reviewed-on: #120 Co-authored-by: unkin-agent <unkin-agent@unkin.net> Co-committed-by: unkin-agent <unkin-agent@unkin.net>
97 lines
2.8 KiB
Go
97 lines
2.8 KiB
Go
package v2
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.unkin.net/unkin/artifactapi/internal/database"
|
|
"git.unkin.net/unkin/artifactapi/pkg/models"
|
|
)
|
|
|
|
// fakeFlusher records FlushRemote calls so a test can assert whether — and how
|
|
// often — a remote's cached metadata was purged.
|
|
type fakeFlusher struct {
|
|
calls []string
|
|
err error
|
|
}
|
|
|
|
func (f *fakeFlusher) FlushRemote(_ context.Context, remote string) error {
|
|
f.calls = append(f.calls, remote)
|
|
return f.err
|
|
}
|
|
|
|
func seedRemote(t *testing.T, db *database.DB, name, baseURL string) {
|
|
t.Helper()
|
|
err := db.CreateRemote(context.Background(), &models.Remote{
|
|
Name: name,
|
|
PackageType: models.PackageRPM,
|
|
RepoType: models.RepoTypeRemote,
|
|
BaseURL: baseURL,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("seed remote: %v", err)
|
|
}
|
|
}
|
|
|
|
// A base_url change must flush the remote's cached metadata exactly once, while
|
|
// an update that leaves base_url untouched must not flush at all.
|
|
func TestUpdateFlushesCacheOnBaseURLChange(t *testing.T) {
|
|
if testDSN == "" {
|
|
t.Skip("Docker unavailable")
|
|
}
|
|
db, err := database.New(testDSN)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
const name = "rpm-flush-change"
|
|
seedRemote(t, db, name, "https://old.example.com/repo")
|
|
|
|
ff := &fakeFlusher{}
|
|
h := NewRemotesHandler(db, ff, nil).Routes()
|
|
|
|
if c := do(t, h, "PUT", "/"+name, `{"package_type":"rpm","repo_type":"remote","base_url":"https://new.example.com/repo"}`); c != 200 {
|
|
t.Fatalf("update (backend change) = %d, want 200", c)
|
|
}
|
|
if len(ff.calls) != 1 || ff.calls[0] != name {
|
|
t.Fatalf("flush calls = %v, want exactly one flush of %q", ff.calls, name)
|
|
}
|
|
|
|
// Re-updating with the same (now current) base_url must not flush again.
|
|
ff.calls = nil
|
|
if c := do(t, h, "PUT", "/"+name, `{"package_type":"rpm","repo_type":"remote","base_url":"https://new.example.com/repo"}`); c != 200 {
|
|
t.Fatalf("update (no backend change) = %d, want 200", c)
|
|
}
|
|
if len(ff.calls) != 0 {
|
|
t.Fatalf("flush calls = %v, want no flush when base_url is unchanged", ff.calls)
|
|
}
|
|
}
|
|
|
|
// A flush error must be swallowed: the DB update already succeeded, so the
|
|
// request still returns 200.
|
|
func TestUpdateFlushFailureStillSucceeds(t *testing.T) {
|
|
if testDSN == "" {
|
|
t.Skip("Docker unavailable")
|
|
}
|
|
db, err := database.New(testDSN)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
|
|
const name = "rpm-flush-error"
|
|
seedRemote(t, db, name, "https://old.example.com/repo")
|
|
|
|
ff := &fakeFlusher{err: errors.New("redis down")}
|
|
h := NewRemotesHandler(db, ff, nil).Routes()
|
|
|
|
if c := do(t, h, "PUT", "/"+name, `{"package_type":"rpm","repo_type":"remote","base_url":"https://new.example.com/repo"}`); c != 200 {
|
|
t.Fatalf("update with failing flush = %d, want 200", c)
|
|
}
|
|
if len(ff.calls) != 1 {
|
|
t.Fatalf("flush calls = %v, want exactly one attempted flush", ff.calls)
|
|
}
|
|
}
|