From e166e56249f7d42d9d05823bd2e6cf20a1667fa5 Mon Sep 17 00:00:00 2001 From: destinoantagonista-wq Date: Fri, 13 Mar 2026 19:41:49 +0000 Subject: [PATCH 1/3] Reconcile registry model states on auth changes Add Manager.ReconcileRegistryModelStates to clear stale per-model runtime failures for models currently registered in the global model registry. The method finds models supported for an auth, resets non-clean ModelState entries, updates aggregated availability, persists changes, and pushes a snapshot to the scheduler. Introduce modelStateIsClean helper to determine when a model state needs resetting. Call ReconcileRegistryModelStates from Service paths that register/refresh models (applyCoreAuthAddOrUpdate and refreshModelRegistrationForAuth) to keep the scheduler and global registry aligned after model re-registration. --- sdk/cliproxy/auth/conductor.go | 91 ++++++++++++++++++++++++++++++++++ sdk/cliproxy/service.go | 3 ++ 2 files changed, 94 insertions(+) diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index b29e04db..9fc65274 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -233,6 +233,81 @@ func (m *Manager) RefreshSchedulerEntry(authID string) { m.scheduler.upsertAuth(snapshot) } +// ReconcileRegistryModelStates clears stale per-model runtime failures for +// models that are currently registered for the auth in the global model registry. +// +// This keeps the scheduler and the global registry aligned after model +// re-registration. Without this reconciliation, a model can reappear in +// /v1/models after registry refresh while the scheduler still blocks it because +// auth.ModelStates retained an older failure such as not_found or quota. +func (m *Manager) ReconcileRegistryModelStates(ctx context.Context, authID string) { + if m == nil || authID == "" { + return + } + + supportedModels := registry.GetGlobalRegistry().GetModelsForClient(authID) + if len(supportedModels) == 0 { + return + } + + supported := make(map[string]struct{}, len(supportedModels)) + for _, model := range supportedModels { + if model == nil { + continue + } + modelKey := canonicalModelKey(model.ID) + if modelKey == "" { + continue + } + supported[modelKey] = struct{}{} + } + if len(supported) == 0 { + return + } + + var snapshot *Auth + now := time.Now() + + m.mu.Lock() + auth, ok := m.auths[authID] + if ok && auth != nil && len(auth.ModelStates) > 0 { + changed := false + for modelKey, state := range auth.ModelStates { + if state == nil { + continue + } + baseModel := canonicalModelKey(modelKey) + if baseModel == "" { + baseModel = strings.TrimSpace(modelKey) + } + if _, supportedModel := supported[baseModel]; !supportedModel { + continue + } + if modelStateIsClean(state) { + continue + } + resetModelState(state, now) + changed = true + } + if changed { + updateAggregatedAvailability(auth, now) + if !hasModelError(auth, now) { + auth.LastError = nil + auth.StatusMessage = "" + auth.Status = StatusActive + } + auth.UpdatedAt = now + _ = m.persist(ctx, auth) + snapshot = auth.Clone() + } + } + m.mu.Unlock() + + if m.scheduler != nil && snapshot != nil { + m.scheduler.upsertAuth(snapshot) + } +} + func (m *Manager) SetSelector(selector Selector) { if m == nil { return @@ -1735,6 +1810,22 @@ func resetModelState(state *ModelState, now time.Time) { state.UpdatedAt = now } +func modelStateIsClean(state *ModelState) bool { + if state == nil { + return true + } + if state.Status != StatusActive { + return false + } + if state.Unavailable || state.StatusMessage != "" || !state.NextRetryAfter.IsZero() || state.LastError != nil { + return false + } + if state.Quota.Exceeded || state.Quota.Reason != "" || !state.Quota.NextRecoverAt.IsZero() || state.Quota.BackoffLevel != 0 { + return false + } + return true +} + func updateAggregatedAvailability(auth *Auth, now time.Time) { if auth == nil || len(auth.ModelStates) == 0 { return diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index abe1deed..a562cfb3 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -310,6 +310,7 @@ func (s *Service) applyCoreAuthAddOrUpdate(ctx context.Context, auth *coreauth.A // This operation may block on network calls, but the auth configuration // is already effective at this point. s.registerModelsForAuth(auth) + s.coreManager.ReconcileRegistryModelStates(ctx, auth.ID) // Refresh the scheduler entry so that the auth's supportedModelSet is rebuilt // from the now-populated global model registry. Without this, newly added auths @@ -1019,6 +1020,7 @@ func (s *Service) refreshModelRegistrationForAuth(current *coreauth.Auth) bool { s.ensureExecutorsForAuth(current) } s.registerModelsForAuth(current) + s.coreManager.ReconcileRegistryModelStates(context.Background(), current.ID) latest, ok := s.latestAuthForModelRegistration(current.ID) if !ok || latest.Disabled { @@ -1032,6 +1034,7 @@ func (s *Service) refreshModelRegistrationForAuth(current *coreauth.Auth) bool { // no auth fields changed, but keeps the refresh path simple and correct. s.ensureExecutorsForAuth(latest) s.registerModelsForAuth(latest) + s.coreManager.ReconcileRegistryModelStates(context.Background(), latest.ID) s.coreManager.RefreshSchedulerEntry(current.ID) return true } From f09ed25fd365a37e4469414f0d2754c19789ef60 Mon Sep 17 00:00:00 2001 From: destinoantagonista-wq Date: Sat, 14 Mar 2026 14:40:06 +0000 Subject: [PATCH 2/3] fix(auth): tighten registry model reconciliation --- sdk/cliproxy/auth/conductor.go | 58 ++++-- .../auth/conductor_registry_reconcile_test.go | 182 ++++++++++++++++++ 2 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 sdk/cliproxy/auth/conductor_registry_reconcile_test.go diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index 9fc65274..1152bca0 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -233,23 +233,19 @@ func (m *Manager) RefreshSchedulerEntry(authID string) { m.scheduler.upsertAuth(snapshot) } -// ReconcileRegistryModelStates clears stale per-model runtime failures for -// models that are currently registered for the auth in the global model registry. +// ReconcileRegistryModelStates aligns per-model runtime state with the current +// registry snapshot for one auth. // -// This keeps the scheduler and the global registry aligned after model -// re-registration. Without this reconciliation, a model can reappear in -// /v1/models after registry refresh while the scheduler still blocks it because -// auth.ModelStates retained an older failure such as not_found or quota. +// Supported models are reset to a clean state because re-registration already +// cleared the registry-side cooldown/suspension snapshot. ModelStates for +// models that are no longer present in the registry are pruned entirely so +// renamed/removed models cannot keep auth-level status stale. func (m *Manager) ReconcileRegistryModelStates(ctx context.Context, authID string) { if m == nil || authID == "" { return } supportedModels := registry.GetGlobalRegistry().GetModelsForClient(authID) - if len(supportedModels) == 0 { - return - } - supported := make(map[string]struct{}, len(supportedModels)) for _, model := range supportedModels { if model == nil { @@ -261,9 +257,6 @@ func (m *Manager) ReconcileRegistryModelStates(ctx context.Context, authID strin } supported[modelKey] = struct{}{} } - if len(supported) == 0 { - return - } var snapshot *Auth now := time.Now() @@ -273,14 +266,19 @@ func (m *Manager) ReconcileRegistryModelStates(ctx context.Context, authID strin if ok && auth != nil && len(auth.ModelStates) > 0 { changed := false for modelKey, state := range auth.ModelStates { - if state == nil { - continue - } baseModel := canonicalModelKey(modelKey) if baseModel == "" { baseModel = strings.TrimSpace(modelKey) } if _, supportedModel := supported[baseModel]; !supportedModel { + // Drop state for models that disappeared from the current registry + // snapshot. Keeping them around leaks stale errors into auth-level + // status, management output, and websocket fallback checks. + delete(auth.ModelStates, modelKey) + changed = true + continue + } + if state == nil { continue } if modelStateIsClean(state) { @@ -289,6 +287,9 @@ func (m *Manager) ReconcileRegistryModelStates(ctx context.Context, authID strin resetModelState(state, now) changed = true } + if len(auth.ModelStates) == 0 { + auth.ModelStates = nil + } if changed { updateAggregatedAvailability(auth, now) if !hasModelError(auth, now) { @@ -297,7 +298,9 @@ func (m *Manager) ReconcileRegistryModelStates(ctx context.Context, authID strin auth.Status = StatusActive } auth.UpdatedAt = now - _ = m.persist(ctx, auth) + if errPersist := m.persist(ctx, auth); errPersist != nil { + logEntryWithRequestID(ctx).WithField("auth_id", auth.ID).Warnf("failed to persist auth changes during model state reconciliation: %v", errPersist) + } snapshot = auth.Clone() } } @@ -1827,7 +1830,11 @@ func modelStateIsClean(state *ModelState) bool { } func updateAggregatedAvailability(auth *Auth, now time.Time) { - if auth == nil || len(auth.ModelStates) == 0 { + if auth == nil { + return + } + if len(auth.ModelStates) == 0 { + clearAggregatedAvailability(auth) return } allUnavailable := true @@ -1835,10 +1842,12 @@ func updateAggregatedAvailability(auth *Auth, now time.Time) { quotaExceeded := false quotaRecover := time.Time{} maxBackoffLevel := 0 + hasState := false for _, state := range auth.ModelStates { if state == nil { continue } + hasState = true stateUnavailable := false if state.Status == StatusDisabled { stateUnavailable = true @@ -1868,6 +1877,10 @@ func updateAggregatedAvailability(auth *Auth, now time.Time) { } } } + if !hasState { + clearAggregatedAvailability(auth) + return + } auth.Unavailable = allUnavailable if allUnavailable { auth.NextRetryAfter = earliestRetry @@ -1887,6 +1900,15 @@ func updateAggregatedAvailability(auth *Auth, now time.Time) { } } +func clearAggregatedAvailability(auth *Auth) { + if auth == nil { + return + } + auth.Unavailable = false + auth.NextRetryAfter = time.Time{} + auth.Quota = QuotaState{} +} + func hasModelError(auth *Auth, now time.Time) bool { if auth == nil || len(auth.ModelStates) == 0 { return false diff --git a/sdk/cliproxy/auth/conductor_registry_reconcile_test.go b/sdk/cliproxy/auth/conductor_registry_reconcile_test.go new file mode 100644 index 00000000..dc4b95a9 --- /dev/null +++ b/sdk/cliproxy/auth/conductor_registry_reconcile_test.go @@ -0,0 +1,182 @@ +package auth + +import ( + "context" + "errors" + "net/http" + "testing" + "time" + + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" +) + +func TestManager_ReconcileRegistryModelStates_ClearsStaleSupportedModelErrors(t *testing.T) { + ctx := context.Background() + manager := NewManager(nil, &RoundRobinSelector{}, nil) + + auth := &Auth{ + ID: "reconcile-auth", + Provider: "codex", + ModelStates: map[string]*ModelState{ + "gpt-5.4": { + Status: StatusError, + StatusMessage: "not_found", + Unavailable: true, + NextRetryAfter: time.Now().Add(12 * time.Hour), + LastError: &Error{HTTPStatus: http.StatusNotFound, Message: "not_found"}, + }, + }, + } + if _, errRegister := manager.Register(ctx, auth); errRegister != nil { + t.Fatalf("register auth: %v", errRegister) + } + + registerSchedulerModels(t, "codex", "gpt-5.4", auth.ID) + manager.RefreshSchedulerEntry(auth.ID) + + got, errPick := manager.scheduler.pickSingle(ctx, "codex", "gpt-5.4", cliproxyexecutor.Options{}, nil) + var authErr *Error + if !errors.As(errPick, &authErr) || authErr == nil { + t.Fatalf("pickSingle() before reconcile error = %v, want auth_unavailable", errPick) + } + if authErr.Code != "auth_unavailable" { + t.Fatalf("pickSingle() before reconcile code = %q, want %q", authErr.Code, "auth_unavailable") + } + if got != nil { + t.Fatalf("pickSingle() before reconcile auth = %v, want nil", got) + } + + manager.ReconcileRegistryModelStates(ctx, auth.ID) + + got, errPick = manager.scheduler.pickSingle(ctx, "codex", "gpt-5.4", cliproxyexecutor.Options{}, nil) + if errPick != nil { + t.Fatalf("pickSingle() after reconcile error = %v", errPick) + } + if got == nil || got.ID != auth.ID { + t.Fatalf("pickSingle() after reconcile auth = %v, want %q", got, auth.ID) + } + + reconciled, ok := manager.GetByID(auth.ID) + if !ok || reconciled == nil { + t.Fatalf("expected auth to still exist") + } + state := reconciled.ModelStates["gpt-5.4"] + if state == nil { + t.Fatalf("expected reconciled model state to exist") + } + if state.Unavailable { + t.Fatalf("state.Unavailable = true, want false") + } + if state.Status != StatusActive { + t.Fatalf("state.Status = %q, want %q", state.Status, StatusActive) + } + if !state.NextRetryAfter.IsZero() { + t.Fatalf("state.NextRetryAfter = %v, want zero", state.NextRetryAfter) + } + if state.LastError != nil { + t.Fatalf("state.LastError = %v, want nil", state.LastError) + } +} + +func TestManager_ReconcileRegistryModelStates_PrunesUnsupportedModelStates(t *testing.T) { + ctx := context.Background() + manager := NewManager(nil, &RoundRobinSelector{}, nil) + + nextRetry := time.Now().Add(30 * time.Minute) + auth := &Auth{ + ID: "reconcile-unsupported-auth", + Provider: "codex", + Status: StatusError, + Unavailable: true, + StatusMessage: "payment_required", + LastError: &Error{HTTPStatus: http.StatusPaymentRequired, Message: "payment_required"}, + ModelStates: map[string]*ModelState{ + "gpt-5.4": { + Status: StatusError, + StatusMessage: "payment_required", + Unavailable: true, + NextRetryAfter: nextRetry, + }, + }, + } + if _, errRegister := manager.Register(ctx, auth); errRegister != nil { + t.Fatalf("register auth: %v", errRegister) + } + + registerSchedulerModels(t, "codex", "gpt-5.5", auth.ID) + manager.ReconcileRegistryModelStates(ctx, auth.ID) + + reconciled, ok := manager.GetByID(auth.ID) + if !ok || reconciled == nil { + t.Fatalf("expected auth to still exist") + } + if len(reconciled.ModelStates) != 0 { + t.Fatalf("expected stale unsupported model state to be pruned, got %+v", reconciled.ModelStates) + } + if reconciled.Unavailable { + t.Fatalf("auth.Unavailable = true, want false") + } + if reconciled.Status != StatusActive { + t.Fatalf("auth.Status = %q, want %q", reconciled.Status, StatusActive) + } + if reconciled.StatusMessage != "" { + t.Fatalf("auth.StatusMessage = %q, want empty", reconciled.StatusMessage) + } + if reconciled.LastError != nil { + t.Fatalf("auth.LastError = %v, want nil", reconciled.LastError) + } + if !reconciled.NextRetryAfter.IsZero() { + t.Fatalf("auth.NextRetryAfter = %v, want zero", reconciled.NextRetryAfter) + } +} + +func TestManager_ReconcileRegistryModelStates_ClearsRemovedModelStateWhenRegistryIsEmpty(t *testing.T) { + ctx := context.Background() + manager := NewManager(nil, &RoundRobinSelector{}, nil) + + auth := &Auth{ + ID: "reconcile-empty-registry-auth", + Provider: "codex", + Status: StatusError, + Unavailable: true, + StatusMessage: "not_found", + LastError: &Error{HTTPStatus: http.StatusNotFound, Message: "not_found"}, + ModelStates: map[string]*ModelState{ + "gpt-5.4": { + Status: StatusError, + StatusMessage: "not_found", + Unavailable: true, + NextRetryAfter: time.Now().Add(12 * time.Hour), + LastError: &Error{HTTPStatus: http.StatusNotFound, Message: "not_found"}, + }, + }, + } + if _, errRegister := manager.Register(ctx, auth); errRegister != nil { + t.Fatalf("register auth: %v", errRegister) + } + + manager.ReconcileRegistryModelStates(ctx, auth.ID) + + reconciled, ok := manager.GetByID(auth.ID) + if !ok || reconciled == nil { + t.Fatalf("expected auth to still exist") + } + if len(reconciled.ModelStates) != 0 { + t.Fatalf("expected stale model state to be pruned when registry is empty, got %+v", reconciled.ModelStates) + } + if reconciled.Unavailable { + t.Fatalf("auth.Unavailable = true, want false") + } + if reconciled.Status != StatusActive { + t.Fatalf("auth.Status = %q, want %q", reconciled.Status, StatusActive) + } + if reconciled.StatusMessage != "" { + t.Fatalf("auth.StatusMessage = %q, want empty", reconciled.StatusMessage) + } + if reconciled.LastError != nil { + t.Fatalf("auth.LastError = %v, want nil", reconciled.LastError) + } + if !reconciled.NextRetryAfter.IsZero() { + t.Fatalf("auth.NextRetryAfter = %v, want zero", reconciled.NextRetryAfter) + } +} From e08f68ed7c7bafe4e0291a570d92b7e53b6e1352 Mon Sep 17 00:00:00 2001 From: destinoantagonista-wq Date: Sat, 14 Mar 2026 14:41:26 +0000 Subject: [PATCH 3/3] chore(auth): drop reconcile test file from pr --- .../auth/conductor_registry_reconcile_test.go | 182 ------------------ 1 file changed, 182 deletions(-) delete mode 100644 sdk/cliproxy/auth/conductor_registry_reconcile_test.go diff --git a/sdk/cliproxy/auth/conductor_registry_reconcile_test.go b/sdk/cliproxy/auth/conductor_registry_reconcile_test.go deleted file mode 100644 index dc4b95a9..00000000 --- a/sdk/cliproxy/auth/conductor_registry_reconcile_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package auth - -import ( - "context" - "errors" - "net/http" - "testing" - "time" - - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" -) - -func TestManager_ReconcileRegistryModelStates_ClearsStaleSupportedModelErrors(t *testing.T) { - ctx := context.Background() - manager := NewManager(nil, &RoundRobinSelector{}, nil) - - auth := &Auth{ - ID: "reconcile-auth", - Provider: "codex", - ModelStates: map[string]*ModelState{ - "gpt-5.4": { - Status: StatusError, - StatusMessage: "not_found", - Unavailable: true, - NextRetryAfter: time.Now().Add(12 * time.Hour), - LastError: &Error{HTTPStatus: http.StatusNotFound, Message: "not_found"}, - }, - }, - } - if _, errRegister := manager.Register(ctx, auth); errRegister != nil { - t.Fatalf("register auth: %v", errRegister) - } - - registerSchedulerModels(t, "codex", "gpt-5.4", auth.ID) - manager.RefreshSchedulerEntry(auth.ID) - - got, errPick := manager.scheduler.pickSingle(ctx, "codex", "gpt-5.4", cliproxyexecutor.Options{}, nil) - var authErr *Error - if !errors.As(errPick, &authErr) || authErr == nil { - t.Fatalf("pickSingle() before reconcile error = %v, want auth_unavailable", errPick) - } - if authErr.Code != "auth_unavailable" { - t.Fatalf("pickSingle() before reconcile code = %q, want %q", authErr.Code, "auth_unavailable") - } - if got != nil { - t.Fatalf("pickSingle() before reconcile auth = %v, want nil", got) - } - - manager.ReconcileRegistryModelStates(ctx, auth.ID) - - got, errPick = manager.scheduler.pickSingle(ctx, "codex", "gpt-5.4", cliproxyexecutor.Options{}, nil) - if errPick != nil { - t.Fatalf("pickSingle() after reconcile error = %v", errPick) - } - if got == nil || got.ID != auth.ID { - t.Fatalf("pickSingle() after reconcile auth = %v, want %q", got, auth.ID) - } - - reconciled, ok := manager.GetByID(auth.ID) - if !ok || reconciled == nil { - t.Fatalf("expected auth to still exist") - } - state := reconciled.ModelStates["gpt-5.4"] - if state == nil { - t.Fatalf("expected reconciled model state to exist") - } - if state.Unavailable { - t.Fatalf("state.Unavailable = true, want false") - } - if state.Status != StatusActive { - t.Fatalf("state.Status = %q, want %q", state.Status, StatusActive) - } - if !state.NextRetryAfter.IsZero() { - t.Fatalf("state.NextRetryAfter = %v, want zero", state.NextRetryAfter) - } - if state.LastError != nil { - t.Fatalf("state.LastError = %v, want nil", state.LastError) - } -} - -func TestManager_ReconcileRegistryModelStates_PrunesUnsupportedModelStates(t *testing.T) { - ctx := context.Background() - manager := NewManager(nil, &RoundRobinSelector{}, nil) - - nextRetry := time.Now().Add(30 * time.Minute) - auth := &Auth{ - ID: "reconcile-unsupported-auth", - Provider: "codex", - Status: StatusError, - Unavailable: true, - StatusMessage: "payment_required", - LastError: &Error{HTTPStatus: http.StatusPaymentRequired, Message: "payment_required"}, - ModelStates: map[string]*ModelState{ - "gpt-5.4": { - Status: StatusError, - StatusMessage: "payment_required", - Unavailable: true, - NextRetryAfter: nextRetry, - }, - }, - } - if _, errRegister := manager.Register(ctx, auth); errRegister != nil { - t.Fatalf("register auth: %v", errRegister) - } - - registerSchedulerModels(t, "codex", "gpt-5.5", auth.ID) - manager.ReconcileRegistryModelStates(ctx, auth.ID) - - reconciled, ok := manager.GetByID(auth.ID) - if !ok || reconciled == nil { - t.Fatalf("expected auth to still exist") - } - if len(reconciled.ModelStates) != 0 { - t.Fatalf("expected stale unsupported model state to be pruned, got %+v", reconciled.ModelStates) - } - if reconciled.Unavailable { - t.Fatalf("auth.Unavailable = true, want false") - } - if reconciled.Status != StatusActive { - t.Fatalf("auth.Status = %q, want %q", reconciled.Status, StatusActive) - } - if reconciled.StatusMessage != "" { - t.Fatalf("auth.StatusMessage = %q, want empty", reconciled.StatusMessage) - } - if reconciled.LastError != nil { - t.Fatalf("auth.LastError = %v, want nil", reconciled.LastError) - } - if !reconciled.NextRetryAfter.IsZero() { - t.Fatalf("auth.NextRetryAfter = %v, want zero", reconciled.NextRetryAfter) - } -} - -func TestManager_ReconcileRegistryModelStates_ClearsRemovedModelStateWhenRegistryIsEmpty(t *testing.T) { - ctx := context.Background() - manager := NewManager(nil, &RoundRobinSelector{}, nil) - - auth := &Auth{ - ID: "reconcile-empty-registry-auth", - Provider: "codex", - Status: StatusError, - Unavailable: true, - StatusMessage: "not_found", - LastError: &Error{HTTPStatus: http.StatusNotFound, Message: "not_found"}, - ModelStates: map[string]*ModelState{ - "gpt-5.4": { - Status: StatusError, - StatusMessage: "not_found", - Unavailable: true, - NextRetryAfter: time.Now().Add(12 * time.Hour), - LastError: &Error{HTTPStatus: http.StatusNotFound, Message: "not_found"}, - }, - }, - } - if _, errRegister := manager.Register(ctx, auth); errRegister != nil { - t.Fatalf("register auth: %v", errRegister) - } - - manager.ReconcileRegistryModelStates(ctx, auth.ID) - - reconciled, ok := manager.GetByID(auth.ID) - if !ok || reconciled == nil { - t.Fatalf("expected auth to still exist") - } - if len(reconciled.ModelStates) != 0 { - t.Fatalf("expected stale model state to be pruned when registry is empty, got %+v", reconciled.ModelStates) - } - if reconciled.Unavailable { - t.Fatalf("auth.Unavailable = true, want false") - } - if reconciled.Status != StatusActive { - t.Fatalf("auth.Status = %q, want %q", reconciled.Status, StatusActive) - } - if reconciled.StatusMessage != "" { - t.Fatalf("auth.StatusMessage = %q, want empty", reconciled.StatusMessage) - } - if reconciled.LastError != nil { - t.Fatalf("auth.LastError = %v, want nil", reconciled.LastError) - } - if !reconciled.NextRetryAfter.IsZero() { - t.Fatalf("auth.NextRetryAfter = %v, want zero", reconciled.NextRetryAfter) - } -}