package utils import ( "context" "errors" "reflect" "testing" "time" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" corev1 "k8s.io/api/core/v1" k8sfake "k8s.io/client-go/kubernetes/fake" k8sCache "k8s.io/client-go/tools/cache" managerPkg "github.com/fission/fission/pkg/utils/manager" ) type testNamespaceSubscriber struct { name string addErr error removeErr error resyncErr error addCalls int removeCalls int resyncCalls int } func (s *testNamespaceSubscriber) Name() string { return s.name } func (s *testNamespaceSubscriber) OnNamespaceAdd(ctx context.Context, record NamespaceRecord) error { s.addCalls++ return s.addErr } func (s *testNamespaceSubscriber) OnNamespaceRemove(ctx context.Context, record NamespaceRecord) error { s.removeCalls++ return s.removeErr } func (s *testNamespaceSubscriber) OnNamespaceResync(ctx context.Context, record NamespaceRecord) error { s.resyncCalls++ return s.resyncErr } func TestNamespaceManagerSnapshotAndGet(t *testing.T) { manager := NewNamespaceManager() now := time.Now().UTC() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-b", Source: NamespaceSourceWatcher, ObservedAt: now}) manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher, ObservedAt: now}) expected := []string{"tenant-a", "tenant-b"} if !reflect.DeepEqual(expected, manager.Snapshot()) { t.Fatalf("expected snapshot %v, got %v", expected, manager.Snapshot()) } record, ok := manager.Get("tenant-a") if !ok { t.Fatalf("expected tenant-a to exist") } if record.Name != "tenant-a" || record.Phase != NamespacePhaseDiscovered { t.Fatalf("unexpected record returned: %+v", record) } summary := manager.Summary() if summary.TotalNamespaces != 2 { t.Fatalf("expected summary total namespaces to be 2, got %d", summary.TotalNamespaces) } if summary.LiveNamespaces != 2 { t.Fatalf("expected live namespaces to be 2, got %d", summary.LiveNamespaces) } if summary.SourceCounts[NamespaceSourceWatcher] != 2 { t.Fatalf("expected watcher source count to be 2") } } func TestNamespaceManagerUpsertIncrementsGeneration(t *testing.T) { manager := NewNamespaceManager() now := time.Now().UTC() first := manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher, ObservedAt: now}) second := manager.Upsert(NamespaceEvent{Type: NamespaceEventUpdate, Name: "tenant-a", Source: NamespaceSourceWatcher, ObservedAt: now.Add(time.Second)}) if first.Generation != 1 { t.Fatalf("expected first generation to be 1, got %d", first.Generation) } if second.Generation != 2 { t.Fatalf("expected second generation to be 2, got %d", second.Generation) } } func TestNamespaceManagerMarkPartState(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) record, ok := manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateActive}) if !ok { t.Fatalf("expected mark part state to succeed") } if record.RegisteredParts["router"].State != NamespacePartStateActive { t.Fatalf("expected router part state to be stored") } if record.Phase != NamespacePhaseActive { t.Fatalf("expected phase to become active, got %s", record.Phase) } } func TestNamespaceManagerRemove(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) if !manager.Remove("tenant-a") { t.Fatalf("expected remove to succeed") } if _, ok := manager.Get("tenant-a"); ok { t.Fatalf("expected record to be removed") } if manager.Remove("tenant-a") { t.Fatalf("expected second remove to report false") } } func TestNamespaceManagerSnapshotRecordsReturnsCopies(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{ Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher, Labels: map[string]string{"fission.io/managed": "true"}, }) _, _ = manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateActive}) records := manager.SnapshotRecords() records[0].Labels["fission.io/managed"] = "false" records[0].RegisteredParts["router"] = NamespacePartState{State: "failed"} record, ok := manager.Get("tenant-a") if !ok { t.Fatalf("expected tenant-a to exist") } if record.Labels["fission.io/managed"] != "true" { t.Fatalf("expected snapshot records to be detached copies") } if record.RegisteredParts["router"].State != NamespacePartStateActive { t.Fatalf("expected part states to be detached copies") } } func TestNamespaceManagerSubscribers(t *testing.T) { manager := NewNamespaceManager() manager.Subscribe(&testNamespaceSubscriber{name: "router"}) manager.Subscribe(&testNamespaceSubscriber{name: "buildermgr"}) manager.Subscribe(&testNamespaceSubscriber{name: "router"}) expected := []string{"buildermgr", "router"} if !reflect.DeepEqual(expected, manager.SnapshotSubscribers()) { t.Fatalf("expected subscribers %v, got %v", expected, manager.SnapshotSubscribers()) } } func TestNamespaceManagerMarkPartStateDerivesPhase(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) record, ok := manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateRegistering}) if !ok { t.Fatalf("expected registering state update to succeed") } if record.Phase != NamespacePhaseRegistering { t.Fatalf("expected phase registering, got %s", record.Phase) } record, ok = manager.MarkPartState("tenant-a", "router", NamespacePartState{State: NamespacePartStateFailed}) if !ok { t.Fatalf("expected failed state update to succeed") } if record.Phase != NamespacePhaseFailed { t.Fatalf("expected phase failed, got %s", record.Phase) } } func TestNamespaceManagerBootstrap(t *testing.T) { manager := NewNamespaceManager() now := time.Now().UTC() records := manager.Bootstrap([]string{"tenant-b", "tenant-a"}, NamespaceSourceBackfill, now) if len(records) != 2 { t.Fatalf("expected 2 bootstrap records, got %d", len(records)) } if !reflect.DeepEqual([]string{"tenant-a", "tenant-b"}, manager.Snapshot()) { t.Fatalf("expected manager snapshot to contain bootstrapped namespaces") } record, ok := manager.Get("tenant-a") if !ok { t.Fatalf("expected tenant-a after bootstrap") } if record.Source != NamespaceSourceBackfill { t.Fatalf("expected bootstrap source backfill, got %s", record.Source) } } func TestNamespaceManagerBootstrapAndDispatch(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} builder := &testNamespaceSubscriber{name: "buildermgr"} manager.Subscribe(router) manager.Subscribe(builder) records, err := manager.BootstrapAndDispatch(context.Background(), []string{"tenant-b", "tenant-a"}, NamespaceSourceBackfill, time.Now().UTC()) if err != nil { t.Fatalf("expected bootstrap and dispatch success: %v", err) } if len(records) != 2 { t.Fatalf("expected 2 records, got %d", len(records)) } if router.addCalls != 2 || builder.addCalls != 2 { t.Fatalf("expected both subscribers to be called for each namespace") } if records[0].Phase != NamespacePhaseActive || records[1].Phase != NamespacePhaseActive { t.Fatalf("expected active records after bootstrap dispatch") } } func TestNamespaceManagerBootstrapAndDispatchFailure(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router", addErr: errors.New("router failed")} builder := &testNamespaceSubscriber{name: "buildermgr"} manager.Subscribe(router) manager.Subscribe(builder) records, err := manager.BootstrapAndDispatch(context.Background(), []string{"tenant-a"}, NamespaceSourceBackfill, time.Now().UTC()) if err == nil { t.Fatalf("expected bootstrap dispatch failure") } if len(records) != 1 { t.Fatalf("expected single record result") } if records[0].Phase != NamespacePhaseFailed { t.Fatalf("expected failed phase after subscriber error, got %s", records[0].Phase) } if records[0].RegisteredParts["router"].LastError != "router failed" { t.Fatalf("expected router failure to be stored") } } func TestNamespaceManagerPartStateHelpers(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) record, ok := manager.MarkPartRegistering("tenant-a", "router") if !ok || record.Phase != NamespacePhaseRegistering { t.Fatalf("expected registering helper to set registering phase") } record, ok = manager.MarkPartActive("tenant-a", "router") if !ok || record.Phase != NamespacePhaseActive { t.Fatalf("expected active helper to set active phase") } record, ok = manager.MarkPartFailed("tenant-a", "router", errors.New("boom")) if !ok || record.Phase != NamespacePhaseFailed { t.Fatalf("expected failed helper to set failed phase") } if record.RegisteredParts["router"].LastError != "boom" { t.Fatalf("expected failed helper to persist error") } } func TestNewBootstrappedNamespaceManager(t *testing.T) { resolver := &NamespaceResolver{ FissionResourceNS: map[string]string{ "tenant-b": "tenant-b", "tenant-a": "tenant-a", }, } manager := NewBootstrappedNamespaceManager(resolver, NamespaceSourceEnv, time.Now().UTC()) if !reflect.DeepEqual([]string{"tenant-a", "tenant-b"}, manager.Snapshot()) { t.Fatalf("expected bootstrapped manager snapshot from resolver") } record, ok := manager.Get("tenant-a") if !ok { t.Fatalf("expected tenant-a in bootstrapped manager") } if record.Source != NamespaceSourceEnv { t.Fatalf("expected env source, got %s", record.Source) } } func TestNewWatcherNamespaceManager(t *testing.T) { router := &testNamespaceSubscriber{name: "router"} builder := &testNamespaceSubscriber{name: "buildermgr"} manager, err := NewWatcherNamespaceManager(context.Background(), []string{"tenant-b", "tenant-a"}, NamespaceSourceEnv, time.Now().UTC(), router, builder) if err != nil { t.Fatalf("expected watcher manager bootstrap success: %v", err) } if !reflect.DeepEqual([]string{"tenant-a", "tenant-b"}, manager.Snapshot()) { t.Fatalf("expected watcher manager snapshot to match bootstrapped namespaces") } if router.addCalls != 2 || builder.addCalls != 2 { t.Fatalf("expected all subscribers to receive bootstrap dispatch calls") } } func TestNamespaceBecameUnmanaged(t *testing.T) { oldNamespace := &corev1.Namespace{} oldNamespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} newNamespace := &corev1.Namespace{} newNamespace.Labels = map[string]string{} if !NamespaceBecameUnmanaged(oldNamespace, newNamespace) { t.Fatalf("expected namespace to become unmanaged") } if NamespaceBecameUnmanaged(nil, newNamespace) { t.Fatalf("expected nil old namespace to report false") } } func TestDispatchNamespaceAddAndResync(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} manager.Subscribe(router) namespace := &corev1.Namespace{} namespace.Name = "tenant-a" namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} record, ok, err := DispatchNamespaceAdd(context.Background(), manager, namespace, NamespaceSourceWatcher, time.Now().UTC()) if !ok || err != nil || record.Phase != NamespacePhaseActive { t.Fatalf("expected add dispatch success, ok=%v err=%v phase=%s", ok, err, record.Phase) } record, ok, err = DispatchNamespaceResync(context.Background(), manager, namespace, NamespaceSourceWatcher, time.Now().UTC()) if !ok || err != nil || record.Phase != NamespacePhaseActive { t.Fatalf("expected resync dispatch success, ok=%v err=%v phase=%s", ok, err, record.Phase) } if router.addCalls != 1 || router.resyncCalls != 1 { t.Fatalf("expected add and resync subscriber calls") } } func TestRecordNamespaceRemoval(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) namespace := &corev1.Namespace{} namespace.Name = "tenant-a" record, ok := RecordNamespaceRemoval(manager, k8sCache.DeletedFinalStateUnknown{Obj: namespace}, NamespaceSourceWatcher, time.Now().UTC()) if !ok { t.Fatalf("expected removal bookkeeping to succeed") } if record.Phase != NamespacePhaseRemoved { t.Fatalf("expected removed phase after bookkeeping, got %s", record.Phase) } } func TestHandleWatcherNamespaceAddUpdateDelete(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} manager.Subscribe(router) logger := zap.NewNop() namespace := &corev1.Namespace{} namespace.Name = "tenant-a" namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} HandleWatcherNamespaceAdd(context.Background(), logger, "router.NSWatcher", manager, namespace) if router.addCalls != 1 { t.Fatalf("expected add handler to dispatch add") } HandleWatcherNamespaceUpdate(context.Background(), logger, "router.NSWatcher", manager, namespace, namespace, NamespaceRemovalStrategyTrackOnly) if router.resyncCalls != 1 { t.Fatalf("expected update handler to dispatch resync") } HandleWatcherNamespaceDelete(context.Background(), logger, "router.NSWatcher", manager, k8sCache.DeletedFinalStateUnknown{Obj: namespace}, NamespaceRemovalStrategyTrackOnly) record, ok := manager.Get("tenant-a") if !ok || record.Phase != NamespacePhaseRemoved { t.Fatalf("expected delete handler to mark namespace removed") } } func TestHandleWatcherNamespaceAddLogsSummary(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} manager.Subscribe(router) core, logs := observer.New(zap.InfoLevel) logger := zap.New(core) namespace := &corev1.Namespace{} namespace.Name = "tenant-a" namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} HandleWatcherNamespaceAdd(context.Background(), logger, "router.NSWatcher", manager, namespace) entries := logs.AllUntimed() if len(entries) != 1 { t.Fatalf("expected one summary log entry, got %d", len(entries)) } if entries[0].Message != "router.NSWatcher: namespace manager summary" { t.Fatalf("unexpected log message: %s", entries[0].Message) } fields := entries[0].ContextMap() active, ok := fields["has_active_namespaces"].(bool) if !ok || !active { t.Fatalf("expected summary log to report active namespaces") } if live, ok := fields["live_namespaces"].(int64); !ok || live != 1 { t.Fatalf("expected summary log to report one live namespace, got %v", fields["live_namespaces"]) } } func TestHandleWatcherNamespaceRemovalDispatch(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} manager.Subscribe(router) logger := zap.NewNop() namespace := &corev1.Namespace{} namespace.Name = "tenant-a" manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) HandleWatcherNamespaceRemoval(context.Background(), logger, "router.NSWatcher", manager, namespace, NamespaceRemovalStrategyDispatchRemove) record, ok := manager.Get("tenant-a") if !ok || record.Phase != NamespacePhaseRemoved { t.Fatalf("expected dispatch removal to keep removed record") } if router.removeCalls != 1 { t.Fatalf("expected remove subscriber to be called once") } } func TestHandleWatcherNamespaceUpdateDispatchRemoval(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} manager.Subscribe(router) logger := zap.NewNop() oldNamespace := &corev1.Namespace{} oldNamespace.Name = "tenant-a" oldNamespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} newNamespace := &corev1.Namespace{} newNamespace.Name = "tenant-a" newNamespace.Labels = map[string]string{} manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) HandleWatcherNamespaceUpdate(context.Background(), logger, "router.NSWatcher", manager, oldNamespace, newNamespace, NamespaceRemovalStrategyDispatchRemove) record, ok := manager.Get("tenant-a") if !ok || record.Phase != NamespacePhaseRemoved { t.Fatalf("expected dispatch removal on managed->unmanaged transition") } if router.removeCalls != 1 { t.Fatalf("expected remove subscriber to be called once on label drop") } } func TestNewNamespaceWatcherEventHandlers(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} manager.Subscribe(router) logger := zap.NewNop() handlers := NewNamespaceWatcherEventHandlers(context.Background(), logger, "router.NSWatcher", manager, NamespaceRemovalStrategyTrackOnly) namespace := &corev1.Namespace{} namespace.Name = "tenant-a" namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} handlers.AddFunc(namespace) handlers.UpdateFunc(namespace, namespace) handlers.DeleteFunc(k8sCache.DeletedFinalStateUnknown{Obj: namespace}) record, ok := manager.Get("tenant-a") if !ok || record.Phase != NamespacePhaseRemoved { t.Fatalf("expected watcher event handlers to drive namespace lifecycle") } if router.addCalls != 1 || router.resyncCalls != 1 { t.Fatalf("expected add and resync calls through event handlers") } } func TestPrepareManagedNamespaceWatcher(t *testing.T) { router := &testNamespaceSubscriber{name: "router"} logger := zap.NewNop() manager, handlers, err := PrepareManagedNamespaceWatcher(context.Background(), logger, ManagedNamespaceWatcherConfig{ Component: "router.NSWatcher", Namespaces: []string{"tenant-a"}, RemovalStrategy: NamespaceRemovalStrategyTrackOnly, Subscriber: router, }) if err != nil { t.Fatalf("expected watcher preparation success: %v", err) } if !reflect.DeepEqual([]string{"tenant-a"}, manager.Snapshot()) { t.Fatalf("expected watcher manager snapshot to contain bootstrapped namespace") } namespace := &corev1.Namespace{} namespace.Name = "tenant-a" namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} handlers.UpdateFunc(namespace, namespace) if router.addCalls != 1 || router.resyncCalls != 1 { t.Fatalf("expected bootstrap add and handler resync calls") } } func TestRunManagedNamespaceWatcher(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() router := &testNamespaceSubscriber{name: "router"} logger := zap.NewNop() manager, err := RunManagedNamespaceWatcher(ctx, logger, k8sfake.NewSimpleClientset(), managerPkg.New(), ManagedNamespaceWatcherConfig{ Component: "router.NSWatcher", Namespaces: []string{"tenant-a"}, RemovalStrategy: NamespaceRemovalStrategyTrackOnly, Subscriber: router, }) if err != nil { t.Fatalf("expected managed namespace watcher run success: %v", err) } if !reflect.DeepEqual([]string{"tenant-a"}, manager.Snapshot()) { t.Fatalf("expected watcher manager snapshot to contain bootstrapped namespace") } } func TestRunManagedNamespaceWatcherLogsStartedSummary(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() router := &testNamespaceSubscriber{name: "router"} core, logs := observer.New(zap.InfoLevel) logger := zap.New(core) _, err := RunManagedNamespaceWatcher(ctx, logger, k8sfake.NewSimpleClientset(), managerPkg.New(), ManagedNamespaceWatcherConfig{ Component: "router.NSWatcher", Namespaces: []string{"tenant-a"}, RemovalStrategy: NamespaceRemovalStrategyTrackOnly, Subscriber: router, }) if err != nil { t.Fatalf("expected managed namespace watcher run success: %v", err) } entries := logs.FilterMessage("router.NSWatcher: started namespace watcher").AllUntimed() if len(entries) != 1 { t.Fatalf("expected one started-summary log entry, got %d", len(entries)) } fields := entries[0].ContextMap() if live, ok := fields["live_namespaces"].(int64); !ok || live != 1 { t.Fatalf("expected started summary to report one live namespace, got %v", fields["live_namespaces"]) } if active, ok := fields["has_active_namespaces"].(bool); !ok || !active { t.Fatalf("expected started summary to report active namespaces") } if total, ok := fields["total_namespaces"].(int64); !ok || total != 1 { t.Fatalf("expected started summary to report one total namespace, got %v", fields["total_namespaces"]) } if got := fields["subscribers"]; got == nil || !reflect.DeepEqual(got, []interface{}{"router"}) && !reflect.DeepEqual(got, []string{"router"}) { t.Fatalf("expected started summary to report router subscriber, got %v", got) } if got := fields["phase_counts"]; got == nil { t.Fatalf("expected started summary to report phase counts") } } func TestNewDefaultManagedNamespaceWatcherConfig(t *testing.T) { resolver := DefaultNSResolver() original := resolver.FissionResourceNS resolver.FissionResourceNS = map[string]string{"tenant-a": "tenant-a"} defer func() { resolver.FissionResourceNS = original }() router := &testNamespaceSubscriber{name: "router"} config := NewDefaultManagedNamespaceWatcherConfig("router.NSWatcher", router) if config.Component != "router.NSWatcher" { t.Fatalf("expected component name to be preserved") } if config.RemovalStrategy != NamespaceRemovalStrategyTrackOnly { t.Fatalf("expected default removal strategy track-only") } if !reflect.DeepEqual([]string{"tenant-a"}, config.Namespaces) { t.Fatalf("expected namespaces from resolver snapshot") } if config.Subscriber != router { t.Fatalf("expected subscriber to be preserved") } } func TestPrepareManagedNamespaceWatcherDefaultsToTrackOnly(t *testing.T) { router := &testNamespaceSubscriber{name: "router"} logger := zap.NewNop() manager, handlers, err := PrepareManagedNamespaceWatcher(context.Background(), logger, ManagedNamespaceWatcherConfig{ Component: "router.NSWatcher", Namespaces: []string{"tenant-a"}, Subscriber: router, }) if err != nil { t.Fatalf("expected watcher preparation success: %v", err) } namespace := &corev1.Namespace{} namespace.Name = "tenant-a" namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} handlers.DeleteFunc(k8sCache.DeletedFinalStateUnknown{Obj: namespace}) record, ok := manager.Get("tenant-a") if !ok || record.Phase != NamespacePhaseRemoved { t.Fatalf("expected default strategy to keep track-only removal semantics") } if router.removeCalls != 0 { t.Fatalf("expected default strategy not to dispatch remove") } } func TestNamespaceManagerSummary(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-b", Source: NamespaceSourceWatcher}) _, _ = manager.MarkPartRegistering("tenant-a", "router") manager.Upsert(NamespaceEvent{Type: NamespaceEventRemove, Name: "tenant-b", Source: NamespaceSourceWatcher}) manager.Subscribe(&testNamespaceSubscriber{name: "router"}) summary := manager.Summary() if summary.TotalNamespaces != 2 { t.Fatalf("expected total namespaces 2, got %d", summary.TotalNamespaces) } if summary.LiveNamespaces != 1 { t.Fatalf("expected one live namespace, got %d", summary.LiveNamespaces) } if summary.PhaseCounts[NamespacePhaseRegistering] != 1 { t.Fatalf("expected one registering namespace") } if summary.PhaseCounts[NamespacePhaseRemoved] != 1 { t.Fatalf("expected one removed namespace") } if !reflect.DeepEqual([]string{"router"}, summary.Subscribers) { t.Fatalf("expected router subscriber in summary") } if summary.SourceCounts[NamespaceSourceWatcher] != 2 { t.Fatalf("expected two watcher-sourced namespaces") } } func TestLogNamespaceManagerSummary(t *testing.T) { LogNamespaceManagerSummary(nil, "ignored", NamespaceManagerSummary{}) logger := zap.NewNop() summary := NamespaceManagerSummary{ TotalNamespaces: 2, LiveNamespaces: 1, PhaseCounts: map[NamespacePhase]int{ NamespacePhaseActive: 1, NamespacePhaseRemoved: 1, }, SourceCounts: map[NamespaceSource]int{ NamespaceSourceWatcher: 1, NamespaceSourceEnv: 1, }, Subscribers: []string{"router", "buildermgr"}, } LogNamespaceManagerSummary(logger, "namespace summary", summary) } func TestLogNamespaceManagerSummaryIncludesHasActiveNamespaces(t *testing.T) { core, logs := observer.New(zap.InfoLevel) logger := zap.New(core) LogNamespaceManagerSummary(logger, "namespace summary", NamespaceManagerSummary{ LiveNamespaces: 1, PhaseCounts: map[NamespacePhase]int{}, SourceCounts: map[NamespaceSource]int{}, Subscribers: []string{}, }) entries := logs.AllUntimed() if len(entries) != 1 { t.Fatalf("expected one log entry, got %d", len(entries)) } fields := entries[0].ContextMap() active, ok := fields["has_active_namespaces"].(bool) if !ok { t.Fatalf("expected has_active_namespaces field in log context") } if !active { t.Fatalf("expected has_active_namespaces=true in log context") } } func TestNamespaceManagerSummaryEmptyContract(t *testing.T) { manager := NewNamespaceManager() summary := manager.Summary() if summary.PhaseCounts == nil { t.Fatalf("expected phase counts map to be initialized") } if summary.SourceCounts == nil { t.Fatalf("expected source counts map to be initialized") } if summary.Subscribers == nil { t.Fatalf("expected subscribers slice to be initialized") } if len(summary.Subscribers) != 0 { t.Fatalf("expected no subscribers in empty summary") } } func TestLogNamespaceManagerSummaryWithNilLogger(t *testing.T) { LogNamespaceManagerSummary(nil, "ignored", NamespaceManagerSummary{TotalNamespaces: 1}) } func TestNamespaceManagerDispatchAdd(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) router := &testNamespaceSubscriber{name: "router"} builder := &testNamespaceSubscriber{name: "buildermgr"} manager.Subscribe(router) manager.Subscribe(builder) record, ok, err := manager.DispatchAdd(context.Background(), "tenant-a") if !ok || err != nil { t.Fatalf("expected dispatch add success, ok=%v err=%v", ok, err) } if router.addCalls != 1 || builder.addCalls != 1 { t.Fatalf("expected both subscribers to receive add call") } if record.Phase != NamespacePhaseActive { t.Fatalf("expected active phase after successful dispatch, got %s", record.Phase) } } func TestNamespaceManagerDispatchResyncFailure(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) router := &testNamespaceSubscriber{name: "router"} builder := &testNamespaceSubscriber{name: "buildermgr", resyncErr: errors.New("resync failed")} manager.Subscribe(router) manager.Subscribe(builder) record, ok, err := manager.DispatchResync(context.Background(), "tenant-a") if !ok || err == nil { t.Fatalf("expected dispatch resync failure, ok=%v err=%v", ok, err) } if builder.resyncCalls != 1 { t.Fatalf("expected failing subscriber to receive resync call") } if record.Phase != NamespacePhaseFailed { t.Fatalf("expected failed phase after dispatch error, got %s", record.Phase) } if record.RegisteredParts["buildermgr"].LastError != "resync failed" { t.Fatalf("expected subscriber error to be stored") } } func TestNamespaceManagerDispatchRemove(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) router := &testNamespaceSubscriber{name: "router"} builder := &testNamespaceSubscriber{name: "buildermgr"} manager.Subscribe(router) manager.Subscribe(builder) record, ok, err := manager.DispatchRemove(context.Background(), "tenant-a") if !ok || err != nil { t.Fatalf("expected dispatch remove success, ok=%v err=%v", ok, err) } if router.removeCalls != 1 || builder.removeCalls != 1 { t.Fatalf("expected both subscribers to receive remove call") } if record.Phase != NamespacePhaseRemoved { t.Fatalf("expected removed phase after dispatch remove, got %s", record.Phase) } } func TestNamespaceManagerDispatchRemoveFailure(t *testing.T) { manager := NewNamespaceManager() manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher}) router := &testNamespaceSubscriber{name: "router", removeErr: errors.New("remove failed")} builder := &testNamespaceSubscriber{name: "buildermgr"} manager.Subscribe(router) manager.Subscribe(builder) record, ok, err := manager.DispatchRemove(context.Background(), "tenant-a") if !ok || err == nil { t.Fatalf("expected dispatch remove failure, ok=%v err=%v", ok, err) } if record.Phase != NamespacePhaseRemoved { t.Fatalf("expected removed phase even on dispatch remove error, got %s", record.Phase) } } func TestNamespaceSubscriberFuncs(t *testing.T) { addCalls := 0 removeCalls := 0 resyncCalls := 0 subscriber := NamespaceSubscriberFuncs{ SubscriberName: "router", AddFunc: func(ctx context.Context, record NamespaceRecord) error { addCalls++ return nil }, RemoveFunc: func(ctx context.Context, record NamespaceRecord) error { removeCalls++ return nil }, ResyncFunc: func(ctx context.Context, record NamespaceRecord) error { resyncCalls++ return nil }, } if subscriber.Name() != "router" { t.Fatalf("expected subscriber name router") } _ = subscriber.OnNamespaceAdd(context.Background(), NamespaceRecord{Name: "tenant-a"}) _ = subscriber.OnNamespaceRemove(context.Background(), NamespaceRecord{Name: "tenant-a"}) _ = subscriber.OnNamespaceResync(context.Background(), NamespaceRecord{Name: "tenant-a"}) if addCalls != 1 || removeCalls != 1 || resyncCalls != 1 { t.Fatalf("expected all functional callbacks to run once") } }