账号首次 401 仅临时不可调度,给予 token 刷新窗口;若恢复后再次 401 说明凭证确实失效,直接升级为错误状态以避免反复无效调度。 - 缓存中 reason 为空时从 DB 回退读取,防止升级判断失效 - ClearError 同时清除临时不可调度状态,管理员恢复后重新给予一次机会 - 管理后台账号列表添加"临时不可调度"状态筛选 - 补充 DB 回退场景单元测试
120 lines
3.9 KiB
Go
120 lines
3.9 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// dbFallbackRepoStub extends errorPolicyRepoStub with a configurable DB account
|
|
// returned by GetByID, simulating cache miss + DB fallback.
|
|
type dbFallbackRepoStub struct {
|
|
errorPolicyRepoStub
|
|
dbAccount *Account // returned by GetByID when non-nil
|
|
}
|
|
|
|
func (r *dbFallbackRepoStub) GetByID(ctx context.Context, id int64) (*Account, error) {
|
|
if r.dbAccount != nil && r.dbAccount.ID == id {
|
|
return r.dbAccount, nil
|
|
}
|
|
return nil, nil // not found, no error
|
|
}
|
|
|
|
func TestCheckErrorPolicy_401_DBFallback_Escalates(t *testing.T) {
|
|
// Scenario: cache account has empty TempUnschedulableReason (cache miss),
|
|
// but DB account has a previous 401 record → should escalate to ErrorPolicyNone.
|
|
repo := &dbFallbackRepoStub{
|
|
dbAccount: &Account{
|
|
ID: 20,
|
|
TempUnschedulableReason: `{"status_code":401,"until_unix":1735689600}`,
|
|
},
|
|
}
|
|
svc := NewRateLimitService(repo, nil, &config.Config{}, nil, nil)
|
|
|
|
account := &Account{
|
|
ID: 20,
|
|
Type: AccountTypeOAuth,
|
|
Platform: PlatformAntigravity,
|
|
TempUnschedulableReason: "", // cache miss — reason is empty
|
|
Credentials: map[string]any{
|
|
"temp_unschedulable_enabled": true,
|
|
"temp_unschedulable_rules": []any{
|
|
map[string]any{
|
|
"error_code": float64(401),
|
|
"keywords": []any{"unauthorized"},
|
|
"duration_minutes": float64(10),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := svc.CheckErrorPolicy(context.Background(), account, http.StatusUnauthorized, []byte(`unauthorized`))
|
|
require.Equal(t, ErrorPolicyNone, result, "401 with DB fallback showing previous 401 should escalate to ErrorPolicyNone")
|
|
}
|
|
|
|
func TestCheckErrorPolicy_401_DBFallback_NoDBRecord_FirstHit(t *testing.T) {
|
|
// Scenario: cache account has empty TempUnschedulableReason,
|
|
// DB also has no previous 401 record → should NOT escalate (first hit → temp unscheduled).
|
|
repo := &dbFallbackRepoStub{
|
|
dbAccount: &Account{
|
|
ID: 21,
|
|
TempUnschedulableReason: "", // DB also empty
|
|
},
|
|
}
|
|
svc := NewRateLimitService(repo, nil, &config.Config{}, nil, nil)
|
|
|
|
account := &Account{
|
|
ID: 21,
|
|
Type: AccountTypeOAuth,
|
|
Platform: PlatformAntigravity,
|
|
TempUnschedulableReason: "",
|
|
Credentials: map[string]any{
|
|
"temp_unschedulable_enabled": true,
|
|
"temp_unschedulable_rules": []any{
|
|
map[string]any{
|
|
"error_code": float64(401),
|
|
"keywords": []any{"unauthorized"},
|
|
"duration_minutes": float64(10),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := svc.CheckErrorPolicy(context.Background(), account, http.StatusUnauthorized, []byte(`unauthorized`))
|
|
require.Equal(t, ErrorPolicyTempUnscheduled, result, "401 first hit with no DB record should temp-unschedule")
|
|
}
|
|
|
|
func TestCheckErrorPolicy_401_DBFallback_DBError_FirstHit(t *testing.T) {
|
|
// Scenario: cache account has empty TempUnschedulableReason,
|
|
// DB lookup returns nil (not found) → should treat as first hit → temp unscheduled.
|
|
repo := &dbFallbackRepoStub{
|
|
dbAccount: nil, // GetByID returns nil, nil
|
|
}
|
|
svc := NewRateLimitService(repo, nil, &config.Config{}, nil, nil)
|
|
|
|
account := &Account{
|
|
ID: 22,
|
|
Type: AccountTypeOAuth,
|
|
Platform: PlatformAntigravity,
|
|
TempUnschedulableReason: "",
|
|
Credentials: map[string]any{
|
|
"temp_unschedulable_enabled": true,
|
|
"temp_unschedulable_rules": []any{
|
|
map[string]any{
|
|
"error_code": float64(401),
|
|
"keywords": []any{"unauthorized"},
|
|
"duration_minutes": float64(10),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := svc.CheckErrorPolicy(context.Background(), account, http.StatusUnauthorized, []byte(`unauthorized`))
|
|
require.Equal(t, ErrorPolicyTempUnscheduled, result, "401 first hit with DB not found should temp-unschedule")
|
|
}
|