package retry import ( "net/http" "testing" "time" ) func TestCompositeRetryPolicy_FirstDecisionWins(t *testing.T) { p := NewCompositeRetryPolicy( &stubPolicy{decision: nil}, // 不表态 &stubPolicy{decision: &RetryDecision{Retry: true, Reason: "second wins"}}, &stubPolicy{decision: &RetryDecision{Retry: false, Reason: "third"}}, ) d := p.ShouldRetry(makeErr("server_overload"), 1, defaultCtx()) if d == nil || !d.Retry || d.Reason != "second wins" { t.Errorf("expected second policy to win, got %+v", d) } } func TestCompositeRetryPolicy_NoPolicyMatched(t *testing.T) { p := NewCompositeRetryPolicy( &stubPolicy{decision: nil}, &stubPolicy{decision: nil}, ) d := p.ShouldRetry(makeErr("unknown"), 1, defaultCtx()) if d == nil || d.Retry { t.Error("expected no-retry when no policy matched") } } func TestCompositeRetryPolicy_Add(t *testing.T) { p := NewCompositeRetryPolicy() p.Add(&stubPolicy{decision: &RetryDecision{Retry: true, Reason: "added"}}) d := p.ShouldRetry(makeErr("api_timeout"), 1, defaultCtx()) if d == nil || !d.Retry { t.Error("added policy should work") } } // ============================================================ // 辅助 // ============================================================ type stubPolicy struct { decision *RetryDecision } func (s *stubPolicy) ShouldRetry(err RetryError, attempt int, ctx *RetryContext) *RetryDecision { return s.decision } // mockRetryError 是 RetryError 的测试用实现,避免 retry 包测试 import api 包(循环依赖). // 精妙之处(CLEVER): retry 包通过 RetryError 接口与 api 包解耦, // 测试用 mock 替代真实 APIError,circular import 问题彻底消失. type mockRetryError struct { category string retryable bool delay time.Duration headers http.Header retryInfo *RetryInfo } func (m *mockRetryError) Error() string { return "mock: " + m.category } func (m *mockRetryError) Category() string { return m.category } func (m *mockRetryError) IsRetryable() bool { return m.retryable } func (m *mockRetryError) RetryDelay() time.Duration { if m.retryInfo != nil { return m.retryInfo.After } return m.delay } func (m *mockRetryError) Message() string { return m.category } func (m *mockRetryError) Headers() http.Header { return m.headers } func (m *mockRetryError) RetryInfo() *RetryInfo { return m.retryInfo } // makeErr 创建给定类别的可重试 mock 错误. func makeErr(category string) *mockRetryError { retryable := false switch category { case "api_timeout", "server_overload", "server_error", "connection_error": retryable = true } return &mockRetryError{ category: category, retryable: retryable, retryInfo: &RetryInfo{Retryable: retryable}, } } func defaultCtx() *RetryContext { return &RetryContext{ IsForeground: true, StartTime: time.Now(), ConsecutiveCounts: make(map[string]int), } }