package engine import ( "errors" "testing" ) // TestNewFallbackTracker_Default 测试默认配置创建 func TestNewFallbackTracker_Default(t *testing.T) { tracker := NewFallbackTracker(nil, "claude-sonnet-4-6") if tracker.CurrentModel() != "claude-sonnet-4-6" { t.Errorf("初始模型应为 claude-sonnet-4-6, 实际: %s", tracker.CurrentModel()) } if tracker.WasFallback() { t.Error("初始状态不应该有降级") } if tracker.FallbackCount() != 0 { t.Errorf("初始降级次数应为 0, 实际: %d", tracker.FallbackCount()) } if tracker.OriginalModel() != "claude-sonnet-4-6" { t.Errorf("原始模型应为 claude-sonnet-4-6, 实际: %s", tracker.OriginalModel()) } } // TestFallbackTracker_ShouldFallback_Matching 测试匹配触发降级 func TestFallbackTracker_ShouldFallback_Matching(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 2, FallbackOnErrors: []string{"model_not_found", "quota_exceeded"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") // 匹配的错误应触发降级 model, should := tracker.ShouldFallback(errors.New("error: model_not_found")) if !should { t.Error("model_not_found 错误应触发降级") } if model != "claude-haiku-3" { t.Errorf("备用模型应为 claude-haiku-3, 实际: %s", model) } } // TestFallbackTracker_ShouldFallback_NotMatching 测试不匹配的错误 func TestFallbackTracker_ShouldFallback_NotMatching(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 1, FallbackOnErrors: []string{"model_not_found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") // 不匹配的错误不应触发降级 _, should := tracker.ShouldFallback(errors.New("error: rate_limit")) if should { t.Error("rate_limit 错误不应触发降级") } } // TestFallbackTracker_ShouldFallback_NoFallbackModel 测试未配置备用模型 func TestFallbackTracker_ShouldFallback_NoFallbackModel(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "", MaxFallbacks: 1, FallbackOnErrors: []string{"model_not_found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") _, should := tracker.ShouldFallback(errors.New("error: model_not_found")) if should { t.Error("未配置备用模型时不应触发降级") } } // TestFallbackTracker_ShouldFallback_NilError 测试 nil 错误 func TestFallbackTracker_ShouldFallback_NilError(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 1, FallbackOnErrors: []string{"model_not_found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") _, should := tracker.ShouldFallback(nil) if should { t.Error("nil 错误不应触发降级") } } // TestFallbackTracker_MaxFallbacks 测试最大降级次数限制 func TestFallbackTracker_MaxFallbacks(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 1, FallbackOnErrors: []string{"model_not_found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") // 第一次降级 _, should := tracker.ShouldFallback(errors.New("model_not_found")) if !should { t.Error("第一次降级应该被允许") } tracker.RecordFallback() // 第二次降级应被拒绝(超过 MaxFallbacks=1) _, should = tracker.ShouldFallback(errors.New("model_not_found")) if should { t.Error("超过最大降级次数后不应继续降级") } } // TestFallbackTracker_RecordFallback 测试记录降级后的状态变化 func TestFallbackTracker_RecordFallback(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 2, FallbackOnErrors: []string{"model_not_found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") tracker.RecordFallback() if !tracker.WasFallback() { t.Error("记录降级后 WasFallback 应为 true") } if tracker.CurrentModel() != "claude-haiku-3" { t.Errorf("降级后当前模型应为 claude-haiku-3, 实际: %s", tracker.CurrentModel()) } if tracker.FallbackCount() != 1 { t.Errorf("降级次数应为 1, 实际: %d", tracker.FallbackCount()) } if tracker.OriginalModel() != "claude-sonnet-4-6" { t.Errorf("原始模型应保持不变: %s", tracker.OriginalModel()) } } // TestFallbackTracker_CaseInsensitive 测试错误匹配大小写不敏感 func TestFallbackTracker_CaseInsensitive(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 1, FallbackOnErrors: []string{"Model_Not_Found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") _, should := tracker.ShouldFallback(errors.New("error: model_not_found occurred")) if !should { t.Error("错误匹配应该大小写不敏感") } } // TestDefaultFallbackConfig 测试默认配置值 func TestDefaultFallbackConfig(t *testing.T) { cfg := DefaultFallbackConfig() if cfg.FallbackModel != "" { t.Errorf("默认备用模型应为空, 实际: %s", cfg.FallbackModel) } if cfg.MaxFallbacks != 1 { t.Errorf("默认最大降级次数应为 1, 实际: %d", cfg.MaxFallbacks) } if len(cfg.FallbackOnErrors) != 3 { t.Errorf("默认触发错误列表应有 3 项, 实际: %d", len(cfg.FallbackOnErrors)) } } // TestFallbackTracker_DefaultMaxFallbacks 测试 MaxFallbacks <= 0 使用默认值 func TestFallbackTracker_DefaultMaxFallbacks(t *testing.T) { cfg := &FallbackConfig{ FallbackModel: "claude-haiku-3", MaxFallbacks: 0, FallbackOnErrors: []string{"model_not_found"}, } tracker := NewFallbackTracker(cfg, "claude-sonnet-4-6") // MaxFallbacks=0 应使用默认值 1 _, should := tracker.ShouldFallback(errors.New("model_not_found")) if !should { t.Error("MaxFallbacks=0 应使用默认值 1,第一次降级应允许") } tracker.RecordFallback() _, should = tracker.ShouldFallback(errors.New("model_not_found")) if should { t.Error("达到默认最大次数后不应继续降级") } }