Files
team-api/relaykit/dispatch/classify_test.go
T
Qian 2ef5a40c26 新的调度方式 (#14)
重构模型渠道调度系统
2026-08-01 22:55:08 +08:00

102 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dispatch
import (
"context"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/qianfree/team-api/relaykit/types"
)
func TestClassify_状态码全表(t *testing.T) {
tests := []struct {
code int
want ErrorClass
}{
{400, ErrClassClient},
{401, ErrClassCredential},
{402, ErrClassChannelFatal},
{403, ErrClassCredential},
{404, ErrClassChannelFatal},
{408, ErrClassTimeout},
{409, ErrClassClient},
{413, ErrClassClient},
{422, ErrClassClient},
{429, ErrClassRateLimit},
{500, ErrClassTransient},
{502, ErrClassTransient},
{503, ErrClassTransient},
{504, ErrClassTimeout}, // 修订 R3: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_NewAPIError错误码(t *testing.T) {
tests := []struct {
name string
code types.ErrorCode
want ErrorClass
}{
{"invalid_key 归凭证", types.ErrorCodeChannelInvalidKey, ErrClassCredential},
{"no_available_key 归渠道级", types.ErrorCodeChannelNoAvailableKey, ErrClassChannelFatal},
{"model_not_found 归渠道级", types.ErrorCodeModelNotFound, ErrClassChannelFatal},
{"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_SkipRetry语义(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_NewAPIError状态码兜底(t *testing.T) {
// 错误码不能定类时用其携带的状态码
e := types.NewErrorWithStatusCode(errors.New("x"), types.ErrorCodeBadResponse, 429)
assert.Equal(t, ErrClassRateLimit, Classify(0, e, DeliveryResponseReceived))
}
func TestClassify_上下文错误(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_纯网络错误(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_无错误(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, "none", ErrClassNone.String())
}