diff --git a/sdk/cliproxy/service_stale_state_test.go b/sdk/cliproxy/service_stale_state_test.go new file mode 100644 index 00000000..db5ce467 --- /dev/null +++ b/sdk/cliproxy/service_stale_state_test.go @@ -0,0 +1,85 @@ +package cliproxy + +import ( + "context" + "testing" + "time" + + "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" +) + +func TestServiceApplyCoreAuthAddOrUpdate_DeleteReAddDoesNotInheritStaleRuntimeState(t *testing.T) { + service := &Service{ + cfg: &config.Config{}, + coreManager: coreauth.NewManager(nil, nil, nil), + } + + authID := "service-stale-state-auth" + modelID := "stale-model" + lastRefreshedAt := time.Date(2026, time.March, 1, 8, 0, 0, 0, time.UTC) + nextRefreshAfter := lastRefreshedAt.Add(30 * time.Minute) + + t.Cleanup(func() { + GlobalModelRegistry().UnregisterClient(authID) + }) + + service.applyCoreAuthAddOrUpdate(context.Background(), &coreauth.Auth{ + ID: authID, + Provider: "claude", + Status: coreauth.StatusActive, + LastRefreshedAt: lastRefreshedAt, + NextRefreshAfter: nextRefreshAfter, + ModelStates: map[string]*coreauth.ModelState{ + modelID: { + Quota: coreauth.QuotaState{BackoffLevel: 7}, + }, + }, + }) + + service.applyCoreAuthRemoval(context.Background(), authID) + + disabled, ok := service.coreManager.GetByID(authID) + if !ok || disabled == nil { + t.Fatalf("expected disabled auth after removal") + } + if !disabled.Disabled || disabled.Status != coreauth.StatusDisabled { + t.Fatalf("expected disabled auth after removal, got disabled=%v status=%v", disabled.Disabled, disabled.Status) + } + if disabled.LastRefreshedAt.IsZero() { + t.Fatalf("expected disabled auth to still carry prior LastRefreshedAt for regression setup") + } + if disabled.NextRefreshAfter.IsZero() { + t.Fatalf("expected disabled auth to still carry prior NextRefreshAfter for regression setup") + } + if len(disabled.ModelStates) == 0 { + t.Fatalf("expected disabled auth to still carry prior ModelStates for regression setup") + } + + service.applyCoreAuthAddOrUpdate(context.Background(), &coreauth.Auth{ + ID: authID, + Provider: "claude", + Status: coreauth.StatusActive, + }) + + updated, ok := service.coreManager.GetByID(authID) + if !ok || updated == nil { + t.Fatalf("expected re-added auth to be present") + } + if updated.Disabled { + t.Fatalf("expected re-added auth to be active") + } + if !updated.LastRefreshedAt.IsZero() { + t.Fatalf("expected LastRefreshedAt to reset on delete -> re-add, got %v", updated.LastRefreshedAt) + } + if !updated.NextRefreshAfter.IsZero() { + t.Fatalf("expected NextRefreshAfter to reset on delete -> re-add, got %v", updated.NextRefreshAfter) + } + if len(updated.ModelStates) != 0 { + t.Fatalf("expected ModelStates to reset on delete -> re-add, got %d entries", len(updated.ModelStates)) + } + if models := registry.GetGlobalRegistry().GetModelsForClient(authID); len(models) == 0 { + t.Fatalf("expected re-added auth to re-register models in global registry") + } +}