feat: classify extraction store failures

This commit is contained in:
youfak 2026-07-29 15:09:58 +08:00
parent 3da836aeff
commit 1785f4505a
4 changed files with 34 additions and 4 deletions

View File

@ -364,9 +364,13 @@ func TestHandlerExtractMapsErrorsToProblemResponsesWithoutSensitiveLeakage(t *te
name: "extraction unavailable",
requestBody: `{"count":1}`,
contentType: "application/json",
extractErr: controllerExtraction.ErrUnavailable,
wantStatus: http.StatusServiceUnavailable,
wantCode: "SERVICE_UNAVAILABLE",
extractErr: errors.Join(
controllerExtraction.ErrUnavailable,
domainExtraction.ErrStoreUnavailable,
errors.New("redis dial failed: TOKEN"),
),
wantStatus: http.StatusServiceUnavailable,
wantCode: "SERVICE_UNAVAILABLE",
},
}
@ -410,7 +414,8 @@ func TestHandlerExtractMapsErrorsToProblemResponsesWithoutSensitiveLeakage(t *te
if problem.Status != test.wantStatus || problem.Code != test.wantCode {
t.Fatalf("problem = %+v", problem)
}
if body := response.Body.String(); strings.Contains(body, `"password"`) || strings.Contains(body, "secret") {
if body := response.Body.String(); strings.Contains(body, `"password"`) ||
strings.Contains(body, "secret") || strings.Contains(body, "redis") || strings.Contains(body, "TOKEN") {
t.Fatalf("error body leaked sensitive data: %s", body)
}
})

View File

@ -140,6 +140,9 @@ func (s *Service) Extract(ctx context.Context, request Request) (Response, error
Upstreams: append([]string(nil), request.Filters.Upstreams...),
})
if err != nil {
if errors.Is(err, domain.ErrStoreUnavailable) {
return response, errors.Join(ErrUnavailable, err)
}
return response, err
}

View File

@ -145,6 +145,27 @@ func TestServiceUsesSourceIdentityForEphemeralIdempotency(t *testing.T) {
}
}
func TestServiceClassifiesStoreUnavailableAndPreservesCause(t *testing.T) {
t.Parallel()
storeErr := errors.Join(domain.ErrStoreUnavailable, errors.New("redis dial failed: TOKEN"))
service, err := NewService(&recordingStore{err: storeErr}, Policy{
MaxCountPerRequest: 1,
DefaultFulfillment: domain.Partial,
}, allowAllAdmission{}, time.Now)
if err != nil {
t.Fatalf("NewService(): %v", err)
}
_, err = service.Extract(context.Background(), Request{
RequestID: "req-1",
ClientID: "client-1",
Count: 1,
})
if !errors.Is(err, ErrUnavailable) || !errors.Is(err, domain.ErrStoreUnavailable) {
t.Fatalf("Extract() error = %v, want unavailable classification with store cause", err)
}
}
func TestServiceIdempotentReplayKeepsOriginalExtractionTime(t *testing.T) {
firstTime := time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC)
secondTime := firstTime.Add(time.Minute)

View File

@ -24,6 +24,7 @@ var (
ErrInsufficientProxies = errors.New("insufficient proxies")
ErrIdempotencyConflict = errors.New("idempotency key was reused with a different extraction request")
ErrInvalidCommand = errors.New("invalid extraction command")
ErrStoreUnavailable = errors.New("extraction store unavailable")
)
type Candidate struct {