From 5d52c9ba945fc7000e75fd309512f7858fd7b17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 15 May 2026 07:11:48 +0400 Subject: [PATCH] test: add integration test for NSWatcher with fake Kubernetes client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestStartManagedNamespaceWatcherIntegration проверяет полный маршрут горячей регистрации namespace без real cluster: 1. RunManagedNamespaceWatcher запускается с k8sfake.NewSimpleClientset() 2. В fake client создаётся Namespace с label fission.io/managed=true 3. Kubernetes informer детектирует событие (без polling, через Watch) 4. SubscriberFuncs.OnNamespaceAdd вызывается 5. NamespaceManager содержит запись со статусом Active Тест доказывает, что вся цепочка fake k8s event → informer → AddFunc → subscriber → manager работает корректно без rolling restart процесса. Также добавлен import metav1 в test file (требовался для CreateOptions). --- pkg/utils/namespace_manager_test.go | 71 +++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/pkg/utils/namespace_manager_test.go b/pkg/utils/namespace_manager_test.go index 0d8e1275..98e0c53b 100644 --- a/pkg/utils/namespace_manager_test.go +++ b/pkg/utils/namespace_manager_test.go @@ -10,6 +10,7 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sfake "k8s.io/client-go/kubernetes/fake" k8sCache "k8s.io/client-go/tools/cache" @@ -818,6 +819,76 @@ func TestNamespaceManagerDispatchRemoveFailure(t *testing.T) { } } +// TestStartManagedNamespaceWatcherIntegration проверяет полный маршрут: +// fake k8s client → информер → AddFunc → subscriber.OnNamespaceAdd. +// Это интеграционный тест без real cluster — использует k8sfake. +func TestStartManagedNamespaceWatcherIntegration(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + fakeClient := k8sfake.NewSimpleClientset() + mgr := managerPkg.New() + router := &testNamespaceSubscriber{name: "router"} + + // Запускаем watcher — не Bootstrap, только informer. + // config.Namespaces пусто, чтобы не было pre-loaded namespace-ов. + manager, err := RunManagedNamespaceWatcher(ctx, zap.NewNop(), fakeClient, mgr, ManagedNamespaceWatcherConfig{ + Component: "router.NSWatcher", + Namespaces: nil, + RemovalStrategy: NamespaceRemovalStrategyTrackOnly, + Subscriber: router, + }) + if err != nil { + t.Fatalf("RunManagedNamespaceWatcher failed: %v", err) + } + + // Создаём namespace с managed label в fake client. + ns := &corev1.Namespace{} + ns.Name = "tenant-integration-a" + ns.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} + if _, err := fakeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}); err != nil { + t.Fatalf("failed to create test namespace in fake client: %v", err) + } + + // Ждём, пока informer обработает event и subscriber получит вызов. + // Timeout намеренно короткий (3 секунды) — fake client синхронный. + deadline := time.Now().Add(3 * time.Second) + for time.Now().Before(deadline) { + if router.addCalls >= 1 { + break + } + time.Sleep(10 * time.Millisecond) + } + + if router.addCalls == 0 { + t.Fatalf("expected subscriber.OnNamespaceAdd to be called after namespace creation, got 0 calls") + } + if router.addCalls != 1 { + t.Fatalf("expected exactly 1 add call (namespace is new), got %d", router.addCalls) + } + + // Namespace должен быть в manager snapshot. + snapshot := manager.Snapshot() + found := false + for _, name := range snapshot { + if name == ns.Name { + found = true + break + } + } + if !found { + t.Fatalf("expected %s in manager snapshot after informer event, got %v", ns.Name, snapshot) + } + + record, ok := manager.Get(ns.Name) + if !ok { + t.Fatalf("expected record for %s in manager", ns.Name) + } + if record.Phase != NamespacePhaseActive { + t.Fatalf("expected active phase after informer add, got %s", record.Phase) + } +} + func TestNamespaceSubscriberFuncs(t *testing.T) { addCalls := 0 removeCalls := 0