mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-12 13:04:57 +00:00
378 lines
12 KiB
Go
378 lines
12 KiB
Go
package providers
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestClassifyHTTP429RateLimit(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(errors.New("wrapped"), 429, "Rate limit exceeded")
|
|
if result.Reason != FailoverRateLimit {
|
|
t.Errorf("expected FailoverRateLimit, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP402Billing(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 402, "Payment required")
|
|
if result.Reason != FailoverBilling {
|
|
t.Errorf("expected FailoverBilling, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP401RevokedAuthPermanent(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 401, "API key has been revoked")
|
|
if result.Reason != FailoverAuthPermanent {
|
|
t.Errorf("expected FailoverAuthPermanent, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP401WithoutRevokedAuth(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 401, "Invalid API key")
|
|
if result.Reason != FailoverAuth {
|
|
t.Errorf("expected FailoverAuth, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP403DeletedAuthPermanent(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 403, "Account has been deleted")
|
|
if result.Reason != FailoverAuthPermanent {
|
|
t.Errorf("expected FailoverAuthPermanent, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP403DisabledAuthPermanent(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 403, "API key is disabled")
|
|
if result.Reason != FailoverAuthPermanent {
|
|
t.Errorf("expected FailoverAuthPermanent, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP404ModelNotFound(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 404, "Model not found")
|
|
if result.Reason != FailoverModelNotFound {
|
|
t.Errorf("expected FailoverModelNotFound, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP404WithModelKeyword(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 404, "Requested model does not exist")
|
|
if result.Reason != FailoverModelNotFound {
|
|
t.Errorf("expected FailoverModelNotFound, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP529Overloaded(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 529, "Service overloaded")
|
|
if result.Reason != FailoverOverloaded {
|
|
t.Errorf("expected FailoverOverloaded, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP500WithOverload(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 500, "Server overload detected")
|
|
if result.Reason != FailoverOverloaded {
|
|
t.Errorf("expected FailoverOverloaded, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP500WithCapacity(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 500, "Insufficient capacity")
|
|
if result.Reason != FailoverOverloaded {
|
|
t.Errorf("expected FailoverOverloaded, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP502BadGateway(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 502, "Bad gateway")
|
|
if result.Reason != FailoverServerError {
|
|
t.Errorf("expected FailoverServerError for 502, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyContextWindowExceeded(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "Context length exceeded")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow kind, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyContextWindowEnglish(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "error: maximum context length reached")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyContextWindowChinese(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "错误: 超出最大长度限制")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyPromptTooLong(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "Prompt is too long")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyTooManyTokens(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "Too many tokens in request")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyNetworkTimeoutError(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
timeoutErr := &net.DNSError{
|
|
Err: "timeout",
|
|
Name: "example.com",
|
|
IsTimeout: true,
|
|
}
|
|
result := classifier.Classify(timeoutErr, 0, "")
|
|
if result.Reason != FailoverTimeout {
|
|
t.Errorf("expected FailoverTimeout, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyNetworkConnectionReset(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
err := errors.New("connection reset by peer")
|
|
result := classifier.Classify(err, 0, "")
|
|
if result.Reason != FailoverTimeout {
|
|
t.Errorf("expected FailoverTimeout, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyNetworkBrokenPipe(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
err := errors.New("broken pipe")
|
|
result := classifier.Classify(err, 0, "")
|
|
if result.Reason != FailoverTimeout {
|
|
t.Errorf("expected FailoverTimeout, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyNetworkEOF(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
err := errors.New("EOF")
|
|
result := classifier.Classify(err, 0, "")
|
|
if result.Reason != FailoverTimeout {
|
|
t.Errorf("expected FailoverTimeout, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestRegisterOpenAIPatterns(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
RegisterOpenAIPatterns(classifier)
|
|
|
|
result := classifier.Classify(nil, 0, "model_is_deactivated")
|
|
if result.Reason != FailoverModelNotFound {
|
|
t.Errorf("expected FailoverModelNotFound for deactivated model, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestRegisterOpenAIPatternsModelNotFound(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
RegisterOpenAIPatterns(classifier)
|
|
|
|
result := classifier.Classify(nil, 0, "The model 'gpt-999' does not exist")
|
|
if result.Reason != FailoverModelNotFound {
|
|
t.Errorf("expected FailoverModelNotFound, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestRegisterAnthropicPatterns(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
RegisterAnthropicPatterns(classifier)
|
|
|
|
result := classifier.Classify(nil, 0, "API is overloaded")
|
|
if result.Reason != FailoverOverloaded {
|
|
t.Errorf("expected FailoverOverloaded, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestRegisterAnthropicPatternsBilling(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
RegisterAnthropicPatterns(classifier)
|
|
|
|
result := classifier.Classify(nil, 0, "Insufficient credit balance")
|
|
if result.Reason != FailoverBilling {
|
|
t.Errorf("expected FailoverBilling, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTPErrorConvenience(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
httpErr := &HTTPError{
|
|
Status: 429,
|
|
Body: "Rate limit exceeded",
|
|
RetryAfter: 60 * time.Second,
|
|
}
|
|
result := ClassifyHTTPError(classifier, httpErr)
|
|
if result.Reason != FailoverRateLimit {
|
|
t.Errorf("expected FailoverRateLimit, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTPErrorNonHTTPError(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
err := errors.New("connection reset by peer")
|
|
result := ClassifyHTTPError(classifier, err)
|
|
if result.Reason != FailoverTimeout {
|
|
t.Errorf("expected FailoverTimeout, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTPErrorCodexSafetyRefusalString(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
err := errors.New("codex: response failed: Invalid prompt: we've limited access to this content for safety reasons")
|
|
result := ClassifyHTTPError(classifier, err)
|
|
if result.Reason != FailoverContentPolicy {
|
|
t.Errorf("expected FailoverContentPolicy, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTPErrorNil(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := ClassifyHTTPError(classifier, nil)
|
|
if result.Reason != FailoverUnknown {
|
|
t.Errorf("expected FailoverUnknown for nil error, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyBillingInsufficientQuota(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "insufficient_quota")
|
|
if result.Reason != FailoverBilling {
|
|
t.Errorf("expected FailoverBilling, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyFormatToolCall(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "Invalid tool_call format")
|
|
if result.Reason != FailoverFormat {
|
|
t.Errorf("expected FailoverFormat, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyFormatFunctionCall(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "Invalid function_call in request")
|
|
if result.Reason != FailoverFormat {
|
|
t.Errorf("expected FailoverFormat, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyFormatInvalidRequest(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "invalid_request_error")
|
|
if result.Reason != FailoverFormat {
|
|
t.Errorf("expected FailoverFormat, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyContentPolicyDataInspectionFailed(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, `{"error":{"code":"data_inspection_failed","message":"Input text data may contain inappropriate content."}}`)
|
|
if result.Reason != FailoverContentPolicy {
|
|
t.Errorf("expected FailoverContentPolicy, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyHTTP401WithExpired(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 401, "Token has expired")
|
|
if result.Reason != FailoverAuthPermanent {
|
|
t.Errorf("expected FailoverAuthPermanent for expired token, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyContextOverflowTokenLimit(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "token limit exceeded")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyContextOverflowChineseContext(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "请求的上下文长度超出限制")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s", result.Kind)
|
|
}
|
|
}
|
|
|
|
func TestClassifyLowercaseInsensitive(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 401, "API KEY HAS BEEN REVOKED")
|
|
if result.Reason != FailoverAuthPermanent {
|
|
t.Errorf("expected FailoverAuthPermanent (case-insensitive), got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyUnknownError(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 500, "Unknown server error")
|
|
if result.Reason != FailoverServerError {
|
|
t.Errorf("expected FailoverServerError, got %s", result.Reason)
|
|
}
|
|
}
|
|
|
|
// Issue 958: New context overflow patterns for ZAI/GLM, DashScope, generic
|
|
|
|
func TestClassifyPromptExceedsMaxLength(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, `{"error":{"code":"1261","message":"Prompt exceeds max length"}}`)
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s (reason: %s)", result.Kind, result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyInputTooLong(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "Input is too long for this model")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s (reason: %s)", result.Kind, result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyRequestTooLarge(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "request_too_large: payload exceeds limit")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s (reason: %s)", result.Kind, result.Reason)
|
|
}
|
|
}
|
|
|
|
func TestClassifyChineseInputTooLong(t *testing.T) {
|
|
classifier := NewDefaultClassifier()
|
|
result := classifier.Classify(nil, 400, "请求输入过长")
|
|
if result.Kind != "context_overflow" {
|
|
t.Errorf("expected context_overflow, got %s (reason: %s)", result.Kind, result.Reason)
|
|
}
|
|
}
|