test: circuit breaker states, bearer cache-hit, blob dedup, terraform/storage branches

This commit is contained in:
2026-07-03 13:33:24 +10:00
parent 4ec3d7b59e
commit 2658167346
2 changed files with 80 additions and 1 deletions
+66
View File
@@ -99,6 +99,13 @@ func mockUpstream(w http.ResponseWriter, r *http.Request) {
return
}
w.Write([]byte("protected payload"))
case "/protected2.bin": // same challenge as /protected.bin
if r.Header.Get("Authorization") != "Bearer minted-token" {
w.Header().Set("Www-Authenticate", `Bearer realm="`+upstream.URL+`/token",service="reg",scope="repo:pull"`)
w.WriteHeader(http.StatusUnauthorized)
return
}
w.Write([]byte("protected payload 2"))
case "/token":
w.Write([]byte(`{"token":"minted-token","expires_in":300}`))
default:
@@ -285,6 +292,15 @@ func TestBearerTokenFlow(t *testing.T) {
t.Error("bearer-protected content mismatch")
}
// A second protected path with the same challenge reuses the cached token.
res2, err := testEngine.Fetch(ctx, r, "protected2.bin", p)
if err != nil {
t.Fatalf("second bearer fetch: %v", err)
}
if readAll(t, res2) != "protected payload 2" {
t.Error("second bearer content mismatch")
}
// HEAD path also negotiates a bearer token (uncached).
testCache.FlushRemote(ctx, "eng-bearer")
testDB.DeleteArtifact(ctx, "eng-bearer", "protected.bin")
@@ -359,6 +375,56 @@ func TestUpstreamErrorUnwrap(t *testing.T) {
}
}
func TestImmutableBlobDedup(t *testing.T) {
requireStack(t)
ctx := context.Background()
p := prov(t, models.PackageGeneric)
// Two remotes serving identical content: the second store hits the
// already-exists branch (blob content is deduplicated).
for _, name := range []string{"eng-dedup-a", "eng-dedup-b"} {
r := seed(t, genericRemote(name))
res, err := testEngine.Fetch(ctx, r, "blob.bin", p)
if err != nil {
t.Fatalf("%s fetch: %v", name, err)
}
if readAll(t, res) != "immutable blob" {
t.Errorf("%s content mismatch", name)
}
}
}
func TestCircuitBreakerStates(t *testing.T) {
requireStack(t)
ctx := context.Background()
cb := NewCircuitBreaker(testCache)
const key = "cb-states"
testCache.ResetCircuit(ctx, key)
if cb.IsOpen(ctx, key) {
t.Error("fresh breaker should be closed")
}
if cb.Health(ctx, key).Status != "healthy" {
t.Error("fresh breaker should be healthy")
}
cb.RecordFailure(ctx, key)
if s := cb.Health(ctx, key).Status; s != "degraded" {
t.Errorf("one failure should be degraded, got %q", s)
}
for i := 0; i < 6; i++ {
cb.RecordFailure(ctx, key)
}
if !cb.IsOpen(ctx, key) {
t.Error("breaker should be open after threshold failures")
}
if s := cb.Health(ctx, key).Status; s != "down" {
t.Errorf("open breaker should be down, got %q", s)
}
cb.RecordSuccess(ctx, key)
if cb.IsOpen(ctx, key) {
t.Error("breaker should close after success")
}
}
func asProxyError(err error, target **ProxyError) bool {
pe, ok := err.(*ProxyError)
if ok {
+14 -1
View File
@@ -12,7 +12,10 @@ import (
"git.unkin.net/unkin/artifactapi/internal/testsupport"
)
var testS3 *S3
var (
testS3 *S3
testEndpoint string
)
func TestMain(m *testing.M) {
ctx := context.Background()
@@ -32,6 +35,7 @@ func TestMain(m *testing.M) {
panic(err)
}
testS3 = s3
testEndpoint = conn.Endpoint
code := m.Run()
terminate()
if code != 0 {
@@ -95,6 +99,15 @@ func TestS3RoundTrip(t *testing.T) {
}
}
func TestNewS3ExistingBucket(t *testing.T) {
requireS3(t)
// The bucket already exists from TestMain, so ensureBucket takes the
// "already present" path.
if _, err := NewS3(testEndpoint, "minioadmin", "minioadmin", "test-bucket", false, ""); err != nil {
t.Fatalf("second NewS3: %v", err)
}
}
func TestS3DownloadMissing(t *testing.T) {
requireS3(t)
if _, _, err := testS3.Download(context.Background(), "does/not/exist"); err == nil {