105 lines
4.0 KiB
Go
105 lines
4.0 KiB
Go
package dispatch
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/qianfree/team-api/relaykit/types"
|
|
)
|
|
|
|
func TestClassify_StatusCodeTable(t *testing.T) {
|
|
tests := []struct {
|
|
code int
|
|
want ErrorClass
|
|
}{
|
|
{400, ErrClassClient},
|
|
{401, ErrClassCredential},
|
|
{402, ErrClassChannelFatal},
|
|
{403, ErrClassCredential},
|
|
{404, ErrClassModelFatal}, // 模型不存在:单模型信号,喂模型级熔断
|
|
{408, ErrClassTimeout},
|
|
{409, ErrClassClient},
|
|
{413, ErrClassClient},
|
|
{422, ErrClassClient},
|
|
{429, ErrClassRateLimit},
|
|
{500, ErrClassTransient},
|
|
{502, ErrClassTransient},
|
|
{503, ErrClassTransient},
|
|
{504, ErrClassTimeout}, // 504 不再归 TRANSIENT
|
|
{520, ErrClassTransient},
|
|
{527, ErrClassTransient},
|
|
{599, ErrClassTransient},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(fmt.Sprintf("%d", tt.code), func(t *testing.T) {
|
|
assert.Equal(t, tt.want, Classify(tt.code, nil, DeliveryResponseReceived))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClassify_NewAPIErrorCodes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
code types.ErrorCode
|
|
want ErrorClass
|
|
}{
|
|
{"invalid_key 归凭证", types.ErrorCodeChannelInvalidKey, ErrClassCredential},
|
|
{"no_available_key 归渠道级", types.ErrorCodeChannelNoAvailableKey, ErrClassChannelFatal},
|
|
{"protocol_mismatch 归渠道级(驱动换渠道)", types.ErrorCodeChannelProtocolMismatch, ErrClassChannelFatal},
|
|
{"model_not_found 归模型级", types.ErrorCodeModelNotFound, ErrClassModelFatal},
|
|
{"model_mapped_error 归模型级", types.ErrorCodeChannelModelMappedError, ErrClassModelFatal},
|
|
{"response_time_exceeded 归超时", types.ErrorCodeChannelResponseTimeExceeded, ErrClassTimeout},
|
|
{"aws_client_error 归渠道级", types.ErrorCodeChannelAwsClientError, ErrClassChannelFatal},
|
|
{"prompt_blocked 归客户端", types.ErrorCodePromptBlocked, ErrClassClient},
|
|
{"insufficient_user_quota 归客户端", types.ErrorCodeInsufficientUserQuota, ErrClassClient},
|
|
{"convert_request_failed 归客户端", types.ErrorCodeConvertRequestFailed, ErrClassClient},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
e := types.NewError(errors.New("x"), tt.code)
|
|
assert.Equal(t, tt.want, Classify(0, e, DeliveryResponseReceived))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClassify_SkipRetrySemantics(t *testing.T) {
|
|
// SkipRetry 标记优先于一切:即使状态码本可重试也归客户端类(不重试)
|
|
e := types.NewErrorWithStatusCode(errors.New("x"), types.ErrorCodeBadResponse, 502, types.ErrOptionWithSkipRetry())
|
|
assert.Equal(t, ErrClassClient, Classify(502, e, DeliveryResponseReceived))
|
|
}
|
|
|
|
func TestClassify_NewAPIErrorStatusCodeFallback(t *testing.T) {
|
|
// 错误码不能定类时用其携带的状态码
|
|
e := types.NewErrorWithStatusCode(errors.New("x"), types.ErrorCodeBadResponse, 429)
|
|
assert.Equal(t, ErrClassRateLimit, Classify(0, e, DeliveryResponseReceived))
|
|
}
|
|
|
|
func TestClassify_ContextErrors(t *testing.T) {
|
|
assert.Equal(t, ErrClassTimeout, Classify(0, context.DeadlineExceeded, DeliveryMaybeSent))
|
|
assert.Equal(t, ErrClassClient, Classify(0, context.Canceled, DeliveryMaybeSent), "客户端取消不重试不计健康")
|
|
wrapped := fmt.Errorf("do request: %w", context.DeadlineExceeded)
|
|
assert.Equal(t, ErrClassTimeout, Classify(0, wrapped, DeliveryNotSent))
|
|
}
|
|
|
|
func TestClassify_PureNetworkError(t *testing.T) {
|
|
err := errors.New("connection refused")
|
|
assert.Equal(t, ErrClassTransient, Classify(0, err, DeliveryNotSent))
|
|
assert.Equal(t, ErrClassTransient, Classify(0, err, DeliveryMaybeSent), "MaybeSent 仍归 TRANSIENT,禁止原地由 FSM 硬规则处理")
|
|
}
|
|
|
|
func TestClassify_NoError(t *testing.T) {
|
|
assert.Equal(t, ErrClassNone, Classify(0, nil, DeliveryNotSent))
|
|
assert.Equal(t, ErrClassNone, Classify(200, nil, DeliveryResponseReceived))
|
|
}
|
|
|
|
func TestErrorClass_String(t *testing.T) {
|
|
assert.Equal(t, "credential", ErrClassCredential.String())
|
|
assert.Equal(t, "timeout", ErrClassTimeout.String())
|
|
assert.Equal(t, "model_fatal", ErrClassModelFatal.String())
|
|
assert.Equal(t, "none", ErrClassNone.String())
|
|
}
|