import sys import time import tkinter as tk from unittest.mock import MagicMock import pytest from knoe.ui.screens.cfg import ConfigMixin class _DummyNamespaceApp(ConfigMixin): """Minimal surface to exercise `ConfigMixin._propagate_namespace_change`.""" def __init__(self, root: tk.Tk): self.root = root # Keep the test fast and deterministic. # Use a debounce long enough that the scheduled `after()` callback will not # run between the simulated keystrokes (which call `root.update()`). self._namespace_debounce_ms = 100 self.db_namespace = tk.StringVar(master=root, value="old-db") self.service_namespace = tk.StringVar(master=root, value="knoe-system") # Independent namespaces that must not be rewritten. self.gitops_namespace = tk.StringVar(master=root, value="gitea") self.argocd_namespace = tk.StringVar(master=root, value="argocd") self._last_synced_ns = self.db_namespace.get() # Inputs dict is used by the sweep logic; include a service namespace entry # to ensure it is not touched. self.inputs = { "init_cluster.service_namespace": self.service_namespace.get(), } self.save_calls = 0 self.updated_namespace = None def _update_env_namespace(self, namespace: str): self.updated_namespace = namespace def _save_prole_cfg(self): self.save_calls += 1 def _drain_tk_events(root: tk.Tk, timeout_s: float = 0.2) -> None: end = time.monotonic() + timeout_s while time.monotonic() < end: root.update() time.sleep(0.001) def test_namespace_typing_does_not_corrupt_service_namespace(): # Some test modules replace tkinter with MagicMocks at import-time. if isinstance(sys.modules.get("tkinter"), MagicMock): pytest.skip("tkinter is mocked in this test run") root = tk.Tk() root.withdraw() try: app = _DummyNamespaceApp(root) app.db_namespace.trace_add("write", app._propagate_namespace_change) # Simulate typing a new namespace in the UI entry box. for partial in [ "k", "kn", "kno", "knoe", "knoe-", "knoe-d", "knoe-db", ]: app.db_namespace.set(partial) root.update() _drain_tk_events(root, timeout_s=0.3) # Regression: SERVICE_NAMESPACE must remain unchanged. assert app.service_namespace.get() == "knoe-system" # Debounced propagation should result in a single save. assert app.save_calls == 1 finally: try: root.destroy() except Exception: pass