mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-13 09:44:51 +00:00
When an auth file is deleted and re-created with the same path/ID, the new auth could inherit stale ModelStates (cooldown/backoff) from the previously disabled entry, preventing it from being routed. Gate runtime state inheritance (ModelStates, LastRefreshedAt, NextRefreshAfter) on both existing and incoming auth being non-disabled in Manager.Update and Service.applyCoreAuthAddOrUpdate. Closes #2061
205 lines
5.5 KiB
Go
205 lines
5.5 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
func TestManager_Update_PreservesModelStates(t *testing.T) {
|
|
m := NewManager(nil, nil, nil)
|
|
|
|
model := "test-model"
|
|
backoffLevel := 7
|
|
|
|
if _, errRegister := m.Register(context.Background(), &Auth{
|
|
ID: "auth-1",
|
|
Provider: "claude",
|
|
Metadata: map[string]any{"k": "v"},
|
|
ModelStates: map[string]*ModelState{
|
|
model: {
|
|
Quota: QuotaState{BackoffLevel: backoffLevel},
|
|
},
|
|
},
|
|
}); errRegister != nil {
|
|
t.Fatalf("register auth: %v", errRegister)
|
|
}
|
|
|
|
if _, errUpdate := m.Update(context.Background(), &Auth{
|
|
ID: "auth-1",
|
|
Provider: "claude",
|
|
Metadata: map[string]any{"k": "v2"},
|
|
}); errUpdate != nil {
|
|
t.Fatalf("update auth: %v", errUpdate)
|
|
}
|
|
|
|
updated, ok := m.GetByID("auth-1")
|
|
if !ok || updated == nil {
|
|
t.Fatalf("expected auth to be present")
|
|
}
|
|
if len(updated.ModelStates) == 0 {
|
|
t.Fatalf("expected ModelStates to be preserved")
|
|
}
|
|
state := updated.ModelStates[model]
|
|
if state == nil {
|
|
t.Fatalf("expected model state to be present")
|
|
}
|
|
if state.Quota.BackoffLevel != backoffLevel {
|
|
t.Fatalf("expected BackoffLevel to be %d, got %d", backoffLevel, state.Quota.BackoffLevel)
|
|
}
|
|
}
|
|
|
|
func TestManager_Update_DisabledExistingDoesNotInheritModelStates(t *testing.T) {
|
|
m := NewManager(nil, nil, nil)
|
|
|
|
// Register a disabled auth with existing ModelStates.
|
|
if _, err := m.Register(context.Background(), &Auth{
|
|
ID: "auth-disabled",
|
|
Provider: "claude",
|
|
Disabled: true,
|
|
Status: StatusDisabled,
|
|
ModelStates: map[string]*ModelState{
|
|
"stale-model": {
|
|
Quota: QuotaState{BackoffLevel: 5},
|
|
},
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("register auth: %v", err)
|
|
}
|
|
|
|
// Update with empty ModelStates — should NOT inherit stale states.
|
|
if _, err := m.Update(context.Background(), &Auth{
|
|
ID: "auth-disabled",
|
|
Provider: "claude",
|
|
Disabled: true,
|
|
Status: StatusDisabled,
|
|
}); err != nil {
|
|
t.Fatalf("update auth: %v", err)
|
|
}
|
|
|
|
updated, ok := m.GetByID("auth-disabled")
|
|
if !ok || updated == nil {
|
|
t.Fatalf("expected auth to be present")
|
|
}
|
|
if len(updated.ModelStates) != 0 {
|
|
t.Fatalf("expected disabled auth NOT to inherit ModelStates, got %d entries", len(updated.ModelStates))
|
|
}
|
|
}
|
|
|
|
func TestManager_Update_ActiveToDisabledDoesNotInheritModelStates(t *testing.T) {
|
|
m := NewManager(nil, nil, nil)
|
|
|
|
// Register an active auth with ModelStates (simulates existing live auth).
|
|
if _, err := m.Register(context.Background(), &Auth{
|
|
ID: "auth-a2d",
|
|
Provider: "claude",
|
|
Status: StatusActive,
|
|
ModelStates: map[string]*ModelState{
|
|
"stale-model": {
|
|
Quota: QuotaState{BackoffLevel: 9},
|
|
},
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("register auth: %v", err)
|
|
}
|
|
|
|
// File watcher deletes config → synthesizes Disabled=true auth → Update.
|
|
// Even though existing is active, incoming auth is disabled → skip inheritance.
|
|
if _, err := m.Update(context.Background(), &Auth{
|
|
ID: "auth-a2d",
|
|
Provider: "claude",
|
|
Disabled: true,
|
|
Status: StatusDisabled,
|
|
}); err != nil {
|
|
t.Fatalf("update auth: %v", err)
|
|
}
|
|
|
|
updated, ok := m.GetByID("auth-a2d")
|
|
if !ok || updated == nil {
|
|
t.Fatalf("expected auth to be present")
|
|
}
|
|
if len(updated.ModelStates) != 0 {
|
|
t.Fatalf("expected active→disabled transition NOT to inherit ModelStates, got %d entries", len(updated.ModelStates))
|
|
}
|
|
}
|
|
|
|
func TestManager_Update_DisabledToActiveDoesNotInheritStaleModelStates(t *testing.T) {
|
|
m := NewManager(nil, nil, nil)
|
|
|
|
// Register a disabled auth with stale ModelStates.
|
|
if _, err := m.Register(context.Background(), &Auth{
|
|
ID: "auth-d2a",
|
|
Provider: "claude",
|
|
Disabled: true,
|
|
Status: StatusDisabled,
|
|
ModelStates: map[string]*ModelState{
|
|
"stale-model": {
|
|
Quota: QuotaState{BackoffLevel: 4},
|
|
},
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("register auth: %v", err)
|
|
}
|
|
|
|
// Re-enable: incoming auth is active, existing is disabled → skip inheritance.
|
|
if _, err := m.Update(context.Background(), &Auth{
|
|
ID: "auth-d2a",
|
|
Provider: "claude",
|
|
Status: StatusActive,
|
|
}); err != nil {
|
|
t.Fatalf("update auth: %v", err)
|
|
}
|
|
|
|
updated, ok := m.GetByID("auth-d2a")
|
|
if !ok || updated == nil {
|
|
t.Fatalf("expected auth to be present")
|
|
}
|
|
if len(updated.ModelStates) != 0 {
|
|
t.Fatalf("expected disabled→active transition NOT to inherit stale ModelStates, got %d entries", len(updated.ModelStates))
|
|
}
|
|
}
|
|
|
|
func TestManager_Update_ActiveInheritsModelStates(t *testing.T) {
|
|
m := NewManager(nil, nil, nil)
|
|
|
|
model := "active-model"
|
|
backoffLevel := 3
|
|
|
|
// Register an active auth with ModelStates.
|
|
if _, err := m.Register(context.Background(), &Auth{
|
|
ID: "auth-active",
|
|
Provider: "claude",
|
|
Status: StatusActive,
|
|
ModelStates: map[string]*ModelState{
|
|
model: {
|
|
Quota: QuotaState{BackoffLevel: backoffLevel},
|
|
},
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("register auth: %v", err)
|
|
}
|
|
|
|
// Update with empty ModelStates — both sides active → SHOULD inherit.
|
|
if _, err := m.Update(context.Background(), &Auth{
|
|
ID: "auth-active",
|
|
Provider: "claude",
|
|
Status: StatusActive,
|
|
}); err != nil {
|
|
t.Fatalf("update auth: %v", err)
|
|
}
|
|
|
|
updated, ok := m.GetByID("auth-active")
|
|
if !ok || updated == nil {
|
|
t.Fatalf("expected auth to be present")
|
|
}
|
|
if len(updated.ModelStates) == 0 {
|
|
t.Fatalf("expected active auth to inherit ModelStates")
|
|
}
|
|
state := updated.ModelStates[model]
|
|
if state == nil {
|
|
t.Fatalf("expected model state to be present")
|
|
}
|
|
if state.Quota.BackoffLevel != backoffLevel {
|
|
t.Fatalf("expected BackoffLevel to be %d, got %d", backoffLevel, state.Quota.BackoffLevel)
|
|
}
|
|
}
|