package utils import ( "context" "errors" "reflect" "testing" "time" ) type testNamespaceSubscriber struct { name string addErr error resyncErr error addCalls 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 { return nil } 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) } } 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 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 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") } }