package identityruntime import ( "context" "errors" "net/http" "testing" "time" "github.com/easyai/easyai-ai-gateway/apps/api/internal/identity" "github.com/easyai/easyai-ai-gateway/apps/api/internal/securityevents" ) type runtimeRepositoryFake struct { revisions map[string]identity.Revision active identity.Revision activateCalled bool activeLookupErr error markValidatedErr error markValidatedErrAfterApply bool activateErr error activateErrAfterApply bool disableErr error disableErrAfterApply bool } func (f *runtimeRepositoryFake) IdentityConfigurationRevision(_ context.Context, id string) (identity.Revision, error) { revision, ok := f.revisions[id] if !ok { return identity.Revision{}, identity.ErrRevisionNotFound } return revision, nil } func (f *runtimeRepositoryFake) ActiveIdentityConfigurationRevision(context.Context) (identity.Revision, error) { if f.activeLookupErr != nil { return identity.Revision{}, f.activeLookupErr } if f.active.ID == "" { return identity.Revision{}, identity.ErrRevisionNotFound } return f.active, nil } func (f *runtimeRepositoryFake) MarkIdentityRevisionValidated(_ context.Context, id string, expected int64, _, _ string) (identity.Revision, error) { if f.markValidatedErr != nil && !f.markValidatedErrAfterApply { return identity.Revision{}, f.markValidatedErr } revision := f.revisions[id] if revision.Version != expected { return identity.Revision{}, identity.ErrRevisionConflict } revision.State, revision.Version = identity.RevisionValidated, revision.Version+1 f.revisions[id] = revision if f.markValidatedErr != nil { return identity.Revision{}, f.markValidatedErr } return revision, nil } func (f *runtimeRepositoryFake) RevalidateActiveIdentityRevision(_ context.Context, id string, expected int64, traceID, auditID string) (identity.Revision, error) { revision := f.revisions[id] if revision.Version != expected || revision.State != identity.RevisionActive { return identity.Revision{}, identity.ErrRevisionConflict } revision.Version, revision.LastTraceID, revision.LastAuditID = revision.Version+1, traceID, auditID f.revisions[id], f.active = revision, revision return revision, nil } func (f *runtimeRepositoryFake) MarkIdentityRevisionFailed(_ context.Context, id string, expected int64, category, _, _ string) (identity.Revision, error) { revision := f.revisions[id] if revision.Version != expected { return identity.Revision{}, identity.ErrRevisionConflict } revision.State, revision.Version, revision.LastErrorCategory = identity.RevisionFailed, revision.Version+1, category f.revisions[id] = revision return revision, nil } func (f *runtimeRepositoryFake) ActivateIdentityRevision(_ context.Context, id string, expected int64, traceID, auditID string) (identity.Revision, bool, error) { f.activateCalled = true if f.activateErr != nil && !f.activateErrAfterApply { return identity.Revision{}, false, f.activateErr } revision := f.revisions[id] if revision.Version != expected || revision.State != identity.RevisionValidated { return identity.Revision{}, false, identity.ErrRevisionConflict } if f.active.ID != "" { old := f.active old.State = identity.RevisionSuperseded f.revisions[old.ID] = old } revision.State, revision.Version, revision.LastTraceID, revision.LastAuditID = identity.RevisionActive, revision.Version+1, traceID, auditID f.active, f.revisions[id] = revision, revision if f.activateErr != nil { return identity.Revision{}, false, f.activateErr } return revision, true, nil } func (f *runtimeRepositoryFake) DisableActiveIdentityRevision(_ context.Context, expected int64, traceID, auditID string) (identity.Revision, error) { if f.disableErr != nil && !f.disableErrAfterApply { return identity.Revision{}, f.disableErr } if f.active.Version != expected { return identity.Revision{}, identity.ErrRevisionConflict } disabled := f.active disabled.State, disabled.Version, disabled.LastTraceID, disabled.LastAuditID = identity.RevisionSuperseded, disabled.Version+1, traceID, auditID f.revisions[disabled.ID] = disabled f.active = identity.Revision{} if f.disableErr != nil { return identity.Revision{}, f.disableErr } return disabled, nil } type runtimeBuilderFake struct { err error builtIDs []string buildStarted chan struct{} buildRelease chan struct{} prepareStarted chan struct{} prepareRelease chan struct{} prepareCalls int cleanupCalledFor string adoptedRevision string preparedCancel context.CancelFunc preparedManager *securityevents.ConnectionManager preparedReceiver http.Handler recoveredSecurityEventDisconnector SecurityEventDisconnector recoverSecurityEventErr error } type runtimeSecurityEventDisconnector struct { lifecycle string err error calls int } func (f *runtimeSecurityEventDisconnector) Disconnect(context.Context) (securityevents.ConnectionView, error) { f.calls++ return securityevents.ConnectionView{LifecycleStatus: f.lifecycle}, f.err } func (f *runtimeBuilderFake) Build(_ context.Context, revision identity.Revision) (*Runtime, error) { f.builtIDs = append(f.builtIDs, revision.ID) if f.buildStarted != nil { close(f.buildStarted) } if f.buildRelease != nil { <-f.buildRelease } if f.err != nil { return nil, f.err } return &Runtime{Revision: revision}, nil } func (f *runtimeBuilderFake) PrepareSecurityEvents(_ context.Context, _ identity.Revision, _ []byte) error { f.prepareCalls++ if f.prepareStarted != nil { close(f.prepareStarted) } if f.prepareRelease != nil { <-f.prepareRelease } return f.err } func (f *runtimeBuilderFake) CleanupPreparedSecurityEvents(_ context.Context, revision identity.Revision) error { f.cleanupCalledFor = revision.ID return f.err } func (f *runtimeBuilderFake) AdoptPreparedSecurityEvents(revisionID string) context.CancelFunc { f.adoptedRevision = revisionID return f.preparedCancel } func (f *runtimeBuilderFake) PreparedSecurityEventManager() *securityevents.ConnectionManager { return f.preparedManager } func (f *runtimeBuilderFake) PreparedSecurityEventReceiver() http.Handler { return f.preparedReceiver } func (f *runtimeBuilderFake) RecoverSecurityEventDisconnector(context.Context, identity.Revision) (SecurityEventDisconnector, error) { return f.recoveredSecurityEventDisconnector, f.recoverSecurityEventErr } func TestValidationFailureKeepsCurrentRuntimeAndDoesNotActivate(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4} draft := identity.Revision{ID: "draft", State: identity.RevisionDraft, Version: 1} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"active": active, "draft": draft}, active: active} builder := &runtimeBuilderFake{err: errors.New("discovery failed")} manager := NewManager(repository, builder) manager.current.Store(&Runtime{Revision: active}) if _, err := manager.Validate(context.Background(), draft.ID, draft.Version, "trace", "audit"); err == nil { t.Fatal("validation failure was ignored") } if manager.Current().Revision.ID != active.ID || repository.activateCalled { t.Fatal("validation failure changed current runtime or activated the draft") } if repository.revisions[draft.ID].State != identity.RevisionFailed { t.Fatal("failed draft was not marked failed") } } func TestLoadActiveMarksTransientFailureForAutomaticReconciliation(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4} repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"active": active}, active: active, activeLookupErr: errors.New("database temporarily unavailable"), } builder := &runtimeBuilderFake{} manager := NewManager(repository, builder) if err := manager.LoadActive(context.Background()); err == nil { t.Fatal("transient active lookup failure was ignored") } if !manager.ReconciliationRequired() || manager.Current() != nil { t.Fatal("failed startup load was not left fail-closed and retryable") } repository.activeLookupErr = nil builder.err = errors.New("SecretStore temporarily unavailable") if err := manager.ReconcileActive(context.Background()); err == nil { t.Fatal("transient runtime build failure was ignored") } if !manager.ReconciliationRequired() { t.Fatal("failed runtime build cleared automatic reconciliation") } builder.err = nil if err := manager.ReconcileActive(context.Background()); err != nil { t.Fatal(err) } if manager.ReconciliationRequired() || manager.Current() == nil || manager.Current().Revision.ID != active.ID { t.Fatalf("startup runtime was not recovered: current=%#v required=%v", manager.Current(), manager.ReconciliationRequired()) } } func TestActivationSwapsRuntimeOnlyAfterRepositoryActivation(t *testing.T) { active := identity.Revision{ID: "old", State: identity.RevisionActive, Version: 2} candidate := identity.Revision{ID: "new", State: identity.RevisionValidated, Version: 3} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"old": active, "new": candidate}, active: active} manager := NewManager(repository, &runtimeBuilderFake{}) manager.current.Store(&Runtime{Revision: active}) activated, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit") if err != nil { t.Fatal(err) } if !repository.activateCalled || activated.State != identity.RevisionActive || manager.Current().Revision.ID != candidate.ID { t.Fatalf("activation order failed: activated=%#v current=%#v", activated, manager.Current()) } } func TestActivationReconcilesCommitAppliedThenResponseLost(t *testing.T) { active := identity.Revision{ID: "old", State: identity.RevisionActive, Version: 2} candidate := identity.Revision{ID: "new", State: identity.RevisionValidated, Version: 3} repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"old": active, "new": candidate}, active: active, activateErr: errors.New("commit response lost"), activateErrAfterApply: true, } manager := NewManager(repository, &runtimeBuilderFake{}) manager.current.Store(&Runtime{Revision: active}) activated, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit") if err != nil { t.Fatal(err) } if activated.State != identity.RevisionActive || manager.Current() == nil || manager.Current().Revision.ID != candidate.ID { t.Fatalf("committed activation was not reconciled: activated=%#v current=%#v", activated, manager.Current()) } } func TestActivationKeepsCurrentRuntimeWhenMutationWasNotApplied(t *testing.T) { active := identity.Revision{ID: "old", State: identity.RevisionActive, Version: 2} candidate := identity.Revision{ID: "new", State: identity.RevisionValidated, Version: 3} mutationErr := errors.New("activation rejected before commit") repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"old": active, "new": candidate}, active: active, activateErr: mutationErr, } manager := NewManager(repository, &runtimeBuilderFake{}) original := &Runtime{Revision: active} manager.current.Store(original) if _, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit"); !errors.Is(err, mutationErr) { t.Fatalf("activation error = %v, want %v", err, mutationErr) } if manager.Current() != original { t.Fatal("definitely uncommitted activation replaced the current runtime") } } func TestActivationFailsClosedWhenCommitOutcomeCannotBeReconciled(t *testing.T) { active := identity.Revision{ID: "old", State: identity.RevisionActive, Version: 2} candidate := identity.Revision{ID: "new", State: identity.RevisionValidated, Version: 3} repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"old": active, "new": candidate}, active: active, activateErr: errors.New("commit outcome unknown"), activeLookupErr: errors.New("database unavailable during reconciliation"), } builder := &runtimeBuilderFake{} manager := NewManager(repository, builder) manager.current.Store(&Runtime{Revision: active}) if _, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit"); err == nil { t.Fatal("unknown activation outcome was reported as successful") } if manager.Current() != nil { t.Fatal("unknown activation outcome did not fail OIDC closed") } if !manager.ReconciliationRequired() { t.Fatal("unknown activation outcome did not request background reconciliation") } repository.activeLookupErr = nil builder.err = errors.New("runtime dependency temporarily unavailable") if err := manager.ReconcileActive(context.Background()); err == nil { t.Fatal("runtime reconciliation unexpectedly ignored build failure") } if !manager.ReconciliationRequired() || manager.Current() != nil { t.Fatal("failed runtime rebuild cleared fail-closed reconciliation state") } builder.err = nil if err := manager.ReconcileActive(context.Background()); err != nil { t.Fatal(err) } if manager.ReconciliationRequired() || manager.Current() == nil || manager.Current().Revision.ID != active.ID { t.Fatalf("runtime did not recover after reconciliation: current=%#v required=%v", manager.Current(), manager.ReconciliationRequired()) } } func TestRollbackRejectsOIDCOnlyRevisionBeforeRuntimeBuild(t *testing.T) { active := identity.Revision{ID: "current", State: identity.RevisionActive, Version: 5} previous := identity.Revision{ID: "previous", State: identity.RevisionSuperseded, Version: 3} repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"current": active, "previous": previous}, active: active, } builder := &runtimeBuilderFake{} manager := NewManager(repository, builder) manager.current.Store(&Runtime{Revision: active}) if _, err := manager.Rollback(context.Background(), previous.ID, previous.Version, "trace", "audit"); !errors.Is(err, identity.ErrRollbackConfigurationHandoffRequired) { t.Fatalf("rollback error=%v, want remote resource handoff required", err) } if len(builder.builtIDs) != 0 || repository.activateCalled || manager.Current() == nil || manager.Current().Revision.ID != active.ID { t.Fatalf("unsafe rollback changed runtime: built=%v activate=%t current=%#v", builder.builtIDs, repository.activateCalled, manager.Current()) } } func TestRollbackRejectsSupersededMachineCredentialBeforeRuntimeBuild(t *testing.T) { active := identity.Revision{ID: "current", State: identity.RevisionActive, Version: 5} previous := identity.Revision{ ID: "previous", State: identity.RevisionSuperseded, Version: 3, MachineCredentialRef: "identity-machine-previous", TokenIntrospection: true, } repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"current": active, "previous": previous}, active: active, } builder := &runtimeBuilderFake{} manager := NewManager(repository, builder) manager.current.Store(&Runtime{Revision: active}) if _, err := manager.Rollback(context.Background(), previous.ID, previous.Version, "trace", "audit"); !errors.Is(err, identity.ErrRollbackConfigurationHandoffRequired) { t.Fatalf("rollback error=%v, want credential handoff required", err) } if len(builder.builtIDs) != 0 || repository.activateCalled { t.Fatal("unsafe rollback reached runtime build or activation") } } func TestValidateRejectsSupersededRevisionBeforeRuntimeBuild(t *testing.T) { previous := identity.Revision{ID: "previous", State: identity.RevisionSuperseded, Version: 3} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"previous": previous}} builder := &runtimeBuilderFake{} manager := NewManager(repository, builder) if _, err := manager.Validate(context.Background(), previous.ID, previous.Version, "trace", "audit"); !errors.Is(err, identity.ErrRevisionConflict) { t.Fatalf("validate error=%v, want revision conflict", err) } if len(builder.builtIDs) != 0 || repository.revisions[previous.ID].State != identity.RevisionSuperseded { t.Fatalf("superseded revision reached runtime validation: built=%v revision=%#v", builder.builtIDs, repository.revisions[previous.ID]) } } func TestDisableKeepsActiveRevisionUntilSecurityEventStreamCanRetire(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4, SessionRevocation: true} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"active": active}, active: active} disconnector := &runtimeSecurityEventDisconnector{lifecycle: "disconnect_pending"} manager := NewManager(repository, &runtimeBuilderFake{}) original := &Runtime{Revision: active, securityEventDisconnector: disconnector} manager.current.Store(original) if _, err := manager.Disable(context.Background(), active.Version, "trace", "audit"); !errors.Is(err, identity.ErrSecurityEventRetirementPending) { t.Fatalf("disable error=%v, want security event retirement pending", err) } if disconnector.calls != 1 || repository.active.ID != active.ID || manager.Current() != original { t.Fatalf("pending retirement changed Active state: calls=%d repository=%#v current=%#v", disconnector.calls, repository.active, manager.Current()) } } func TestDisableRecoversSSFWithoutBuildingBrokenFullIdentityRuntime(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4, SessionRevocation: true} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"active": active}, active: active} disconnector := &runtimeSecurityEventDisconnector{lifecycle: "retiring"} builder := &runtimeBuilderFake{ err: errors.New("OIDC discovery unavailable"), recoveredSecurityEventDisconnector: disconnector, } manager := NewManager(repository, builder) disabled, err := manager.Disable(context.Background(), active.Version, "trace", "audit") if err != nil { t.Fatal(err) } if disabled.State != identity.RevisionSuperseded || disconnector.calls != 1 || len(builder.builtIDs) != 0 || manager.Current() != nil { t.Fatalf("SSF-only recovery did not disable safely: disabled=%#v calls=%d built=%v current=%#v", disabled, disconnector.calls, builder.builtIDs, manager.Current()) } } func TestDisableRetiresSecurityEventStreamBeforeSupersedingActiveRevision(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4, SessionRevocation: true} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"active": active}, active: active} disconnector := &runtimeSecurityEventDisconnector{lifecycle: "retiring"} manager := NewManager(repository, &runtimeBuilderFake{}) manager.current.Store(&Runtime{Revision: active, securityEventDisconnector: disconnector}) disabled, err := manager.Disable(context.Background(), active.Version, "trace", "audit") if err != nil { t.Fatal(err) } if disconnector.calls != 1 || disabled.State != identity.RevisionSuperseded || repository.active.ID != "" || manager.Current() != nil { t.Fatalf("safe disable state: calls=%d disabled=%#v repository=%#v current=%#v", disconnector.calls, disabled, repository.active, manager.Current()) } } func TestDisableReconcilesCommitAppliedThenResponseLost(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4} repository := &runtimeRepositoryFake{ revisions: map[string]identity.Revision{"active": active}, active: active, disableErr: errors.New("commit response lost"), disableErrAfterApply: true, } manager := NewManager(repository, &runtimeBuilderFake{}) manager.current.Store(&Runtime{Revision: active}) disabled, err := manager.Disable(context.Background(), active.Version, "trace", "audit") if err != nil { t.Fatal(err) } if disabled.State != identity.RevisionSuperseded || manager.Current() != nil { t.Fatalf("committed disable was not reconciled: disabled=%#v current=%#v", disabled, manager.Current()) } } func TestActiveRevalidationSwapsOnlyAfterSuccessfulValidation(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"active": active}, active: active} builder := &runtimeBuilderFake{} manager := NewManager(repository, builder) original := &Runtime{Revision: active} manager.current.Store(original) revalidated, err := manager.Validate(context.Background(), active.ID, active.Version, "trace-new", "audit-new") if err != nil { t.Fatal(err) } if revalidated.State != identity.RevisionActive || revalidated.Version != 5 || manager.Current() == original || manager.Current().Revision.LastAuditID != "audit-new" { t.Fatalf("active runtime was not safely revalidated: %#v", revalidated) } } func TestLoadFailureKeepsPersistedActiveWebOriginForBreakGlassRecovery(t *testing.T) { active := identity.Revision{ ID: "active", State: identity.RevisionActive, Version: 4, WebBaseURL: "https://gateway.example.com", } repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"active": active}, active: active} manager := NewManager(repository, &runtimeBuilderFake{err: errors.New("OIDC discovery unavailable")}) if err := manager.LoadActive(context.Background()); err == nil { t.Fatal("active runtime load unexpectedly succeeded") } if manager.Current() != nil || manager.TrustedWebBaseURL() != active.WebBaseURL { t.Fatalf("failed Active load lost recovery origin: current=%#v origin=%q", manager.Current(), manager.TrustedWebBaseURL()) } repository.active = identity.Revision{} if err := manager.ReconcileActive(context.Background()); err != nil { t.Fatal(err) } if manager.TrustedWebBaseURL() != "" { t.Fatalf("confirmed disable retained stale recovery origin %q", manager.TrustedWebBaseURL()) } } func TestActivationFinishingFirstPreventsStalePreparedSecurityEventRestore(t *testing.T) { candidate := identity.Revision{ID: "candidate", State: identity.RevisionValidated, Version: 3} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"candidate": candidate}} builder := &runtimeBuilderFake{buildStarted: make(chan struct{}), buildRelease: make(chan struct{})} manager := NewManager(repository, builder) activationDone := make(chan error, 1) go func() { _, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit") activationDone <- err }() <-builder.buildStarted restoreDone := make(chan error, 1) go func() { restoreDone <- manager.PrepareSecurityEvents(context.Background(), candidate, []byte("secret")) }() close(builder.buildRelease) if err := <-activationDone; err != nil { t.Fatal(err) } if err := <-restoreDone; err != nil { t.Fatal(err) } if builder.prepareCalls != 0 || manager.Current() == nil || manager.Current().Revision.ID != candidate.ID { t.Fatalf("stale restore survived activation: prepare_calls=%d current=%#v", builder.prepareCalls, manager.Current()) } } func TestPreparedSecurityEventRestoreFinishingFirstIsAdoptedByActivation(t *testing.T) { candidate := identity.Revision{ID: "candidate", State: identity.RevisionValidated, Version: 3} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"candidate": candidate}} builder := &runtimeBuilderFake{ buildStarted: make(chan struct{}), buildRelease: make(chan struct{}), prepareStarted: make(chan struct{}), prepareRelease: make(chan struct{}), } manager := NewManager(repository, builder) restoreDone := make(chan error, 1) go func() { restoreDone <- manager.PrepareSecurityEvents(context.Background(), candidate, []byte("secret")) }() <-builder.prepareStarted activationDone := make(chan error, 1) go func() { _, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit") activationDone <- err }() select { case <-builder.buildStarted: t.Fatal("activation did not wait for prepared Receiver restoration") case <-time.After(50 * time.Millisecond): } close(builder.prepareRelease) if err := <-restoreDone; err != nil { t.Fatal(err) } <-builder.buildStarted close(builder.buildRelease) if err := <-activationDone; err != nil { t.Fatal(err) } if builder.prepareCalls != 1 || builder.adoptedRevision != candidate.ID { t.Fatalf("prepared restore was not adopted: prepare_calls=%d adopted=%q", builder.prepareCalls, builder.adoptedRevision) } } func TestManagerDelegatesPreparedSecurityEventCleanupWithoutChangingActiveRuntime(t *testing.T) { active := identity.Revision{ID: "active", State: identity.RevisionActive, Version: 4} draft := identity.Revision{ID: "draft", State: identity.RevisionFailed, Version: 2} builder := &runtimeBuilderFake{} manager := NewManager(&runtimeRepositoryFake{}, builder) manager.current.Store(&Runtime{Revision: active}) if err := manager.CleanupPreparedSecurityEvents(context.Background(), draft); err != nil { t.Fatal(err) } if builder.cleanupCalledFor != draft.ID || manager.Current().Revision.ID != active.ID { t.Fatalf("cleanup changed active runtime or skipped the draft: called=%q current=%#v", builder.cleanupCalledFor, manager.Current()) } } func TestActivationAdoptsPreparedSecurityEventLifetime(t *testing.T) { candidate := identity.Revision{ID: "new", State: identity.RevisionValidated, Version: 3} repository := &runtimeRepositoryFake{revisions: map[string]identity.Revision{"new": candidate}} preparedContext, preparedCancel := context.WithCancel(context.Background()) builder := &runtimeBuilderFake{preparedCancel: preparedCancel} manager := NewManager(repository, builder) if _, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit"); err != nil { t.Fatal(err) } if builder.adoptedRevision != candidate.ID { t.Fatalf("prepared security events were not adopted: %q", builder.adoptedRevision) } manager.Current().Close() select { case <-preparedContext.Done(): default: t.Fatal("closing the active runtime did not release its prepared security event manager") } } func TestPreparedSecurityEventReceiverRemainsAvailableUntilAdoption(t *testing.T) { cancelled := false builder := &RuntimeBuilder{prepared: map[string]preparedSecurityRuntime{ "draft": {manager: &securityevents.ConnectionManager{}, cancel: func() { cancelled = true }, receiverReady: true}, }} if builder.PreparedSecurityEventReceiver() == nil { t.Fatal("prepared receiver was unavailable before activation") } cancel := builder.AdoptPreparedSecurityEvents("draft") if cancel == nil || builder.PreparedSecurityEventReceiver() != nil { t.Fatal("prepared receiver ownership was not transferred exactly once") } cancel() if !cancelled { t.Fatal("adopted receiver lifetime could not be released") } } func TestSecurityEventManagerExposesPreparedRecoveryManagerWithoutActiveRuntime(t *testing.T) { prepared := &securityevents.ConnectionManager{} manager := NewManager(&runtimeRepositoryFake{}, &runtimeBuilderFake{preparedManager: prepared}) if manager.SecurityEventManager() != prepared || manager.SecurityEventReceiver() != prepared { t.Fatal("prepared security event manager was unavailable to recovery handlers") } } func TestSecurityEventManagerPrefersActiveRuntime(t *testing.T) { active := &securityevents.ConnectionManager{} prepared := &securityevents.ConnectionManager{} manager := NewManager(&runtimeRepositoryFake{}, &runtimeBuilderFake{preparedManager: prepared}) manager.current.Store(&Runtime{SecurityEvents: active}) if manager.SecurityEventManager() != active { t.Fatal("prepared recovery manager replaced the active runtime manager") } } func TestSecurityEventReceiverPrefersReadyDraftDuringRepairing(t *testing.T) { active := &securityevents.ConnectionManager{} prepared := &securityevents.ConnectionManager{} manager := NewManager(&runtimeRepositoryFake{}, &runtimeBuilderFake{preparedManager: prepared, preparedReceiver: prepared}) manager.current.Store(&Runtime{SecurityEvents: active}) if manager.SecurityEventReceiver() != prepared { t.Fatal("verification callback was not routed to the ready draft receiver") } } func TestConflictingPreparedManagerIsRecoverableButNotUsedAsReceiver(t *testing.T) { prepared := &securityevents.ConnectionManager{} builder := &RuntimeBuilder{prepared: map[string]preparedSecurityRuntime{ "draft": {manager: prepared}, }} if builder.PreparedSecurityEventManager() != prepared { t.Fatal("conflicting connection manager was unavailable for retirement") } if builder.PreparedSecurityEventReceiver() != nil { t.Fatal("unprepared conflict manager was exposed as a verification receiver") } }