from __future__ import annotations import importlib.util from argparse import Namespace from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] _RENDER_PATH = REPO_ROOT / "supabase" / "helm" / "render_supabase.py" def _load_render_module(): spec = importlib.util.spec_from_file_location("prole_render_supabase", _RENDER_PATH) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module _render_mod = _load_render_module() _build_overlay = _render_mod._build_overlay _read_cfg = _render_mod._read_cfg def _write_cfg(path: Path, content: str) -> None: path.write_text(content.strip() + "\n", encoding="utf-8") def test_render_supabase_uses_supabase_hostname_for_public_urls(tmp_path: Path, monkeypatch): # Ensure env doesn't interfere with config-driven behavior. monkeypatch.delenv("SUPABASE_HOST", raising=False) monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False) monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False) monkeypatch.delenv("KNOE_DB_SERVICE", raising=False) cfg_path = tmp_path / "prole.cfg" _write_cfg( cfg_path, """ [User] supabase_hostname = db.prole.org [Inputs] init_password.db_password = test-password init_password.db_host_port = 5432 [Global] NAMESPACE = test-ns STORAGE_BACKEND = local """, ) cfg = _read_cfg(cfg_path) args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s")) overlay, _meta = _build_overlay(cfg, args) assert overlay["ingress"]["hosts"][0]["host"] == "db.prole.org" assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "https://db.prole.org" assert overlay["environment"]["auth"]["GOTRUE_SITE_URL"] == "https://db.prole.org" assert overlay["environment"]["studio"]["SUPABASE_PUBLIC_URL"] == "https://db.prole.org" def test_render_supabase_allows_scheme_in_supabase_hostname(tmp_path: Path, monkeypatch): monkeypatch.delenv("SUPABASE_HOST", raising=False) monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False) monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False) monkeypatch.delenv("KNOE_DB_SERVICE", raising=False) cfg_path = tmp_path / "prole.cfg" _write_cfg( cfg_path, """ [User] supabase_hostname = http://db.prole.org [Inputs] init_password.db_password = test-password init_password.db_host_port = 5432 [Global] NAMESPACE = test-ns STORAGE_BACKEND = local """, ) cfg = _read_cfg(cfg_path) args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s")) overlay, _meta = _build_overlay(cfg, args) assert overlay["ingress"]["hosts"][0]["host"] == "db.prole.org" assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "http://db.prole.org" assert overlay["environment"]["studio"]["SUPABASE_PUBLIC_URL"] == "http://db.prole.org" def test_render_supabase_splits_api_and_studio_hosts_for_k8s(tmp_path: Path, monkeypatch): monkeypatch.delenv("SUPABASE_HOST", raising=False) monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False) monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False) monkeypatch.delenv("KNOE_DB_SERVICE", raising=False) cfg_path = tmp_path / "prole.cfg" _write_cfg( cfg_path, """ [Inputs] init_password.db_password = test-password init_password.db_host_port = 5432 [Global] NAMESPACE = test-ns DEPLOYMENT_MODE = k8s SUPABASE_API_HOSTNAME = api.knoe.dev SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev STORAGE_BACKEND = local """, ) cfg = _read_cfg(cfg_path) args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s")) overlay, _meta = _build_overlay(cfg, args) assert overlay["ingress"]["hosts"][0]["host"] == "api.knoe.dev" assert overlay["studioIngress"]["hosts"][0]["host"] == "db.0.knoe.dev" assert overlay["ingress"]["className"] == "gce" assert overlay["studioIngress"]["className"] == "gce" assert overlay["environment"]["auth"]["API_EXTERNAL_URL"] == "https://api.knoe.dev" assert overlay["environment"]["auth"]["GOTRUE_SITE_URL"] == "https://db.0.knoe.dev" assert overlay["environment"]["studio"]["SUPABASE_PUBLIC_URL"] == "https://db.0.knoe.dev" assert overlay["environment"]["auth"]["GOTRUE_URI_ALLOW_LIST"] == ( "https://db.0.knoe.dev/**,https://api.knoe.dev/**" ) def test_render_supabase_rejects_duplicate_api_and_studio_host_claims(tmp_path: Path, monkeypatch): monkeypatch.delenv("SUPABASE_HOST", raising=False) monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False) monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False) monkeypatch.delenv("KNOE_DB_SERVICE", raising=False) cfg_path = tmp_path / "prole.cfg" _write_cfg( cfg_path, """ [Inputs] init_password.db_password = test-password init_password.db_host_port = 5432 [Global] NAMESPACE = test-ns DEPLOYMENT_MODE = k8s SUPABASE_API_HOSTNAME = db.0.knoe.dev SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev STORAGE_BACKEND = local """, ) cfg = _read_cfg(cfg_path) args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s")) try: _build_overlay(cfg, args) except SystemExit as exc: assert "Duplicate ingress host/path claim detected" in str(exc) else: raise AssertionError("Expected duplicate host/path guardrail to abort") def test_render_supabase_rejects_db_cluster_context_for_public_ingress(tmp_path: Path, monkeypatch): monkeypatch.delenv("SUPABASE_HOST", raising=False) monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False) monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False) monkeypatch.delenv("KNOE_DB_SERVICE", raising=False) monkeypatch.setenv("KUBECONTEXT", "knoe-dev-cnpg-0") cfg_path = tmp_path / "prole.cfg" _write_cfg( cfg_path, """ [Inputs] init_password.db_password = test-password init_password.db_host_port = 5432 init_cluster.app_cluster_kubecontext = knoe-dev-0 init_cluster.db_cluster_kubecontext = knoe-dev-cnpg-0 [Global] NAMESPACE = test-ns DEPLOYMENT_MODE = k8s SUPABASE_API_HOSTNAME = api.knoe.dev SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev STORAGE_BACKEND = local """, ) cfg = _read_cfg(cfg_path) args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s")) try: _build_overlay(cfg, args) except SystemExit as exc: assert "Refusing to render Supabase public ingress for DB cluster context" in str(exc) else: raise AssertionError("Expected DB-cluster targeting guardrail to abort") def test_render_supabase_rejects_traefik_class_in_k8s_without_opt_in(tmp_path: Path, monkeypatch): monkeypatch.delenv("SUPABASE_HOST", raising=False) monkeypatch.delenv("SUPABASE_HOSTNAME", raising=False) monkeypatch.delenv("KNOE_DB_NAMESPACE", raising=False) monkeypatch.delenv("KNOE_DB_SERVICE", raising=False) cfg_path = tmp_path / "prole.cfg" _write_cfg( cfg_path, """ [Inputs] init_password.db_password = test-password init_password.db_host_port = 5432 [Global] NAMESPACE = test-ns DEPLOYMENT_MODE = k8s SUPABASE_API_HOSTNAME = api.knoe.dev SUPABASE_STUDIO_HOSTNAME = db.0.knoe.dev SUPABASE_INGRESS_CLASS = traefik STORAGE_BACKEND = local """, ) cfg = _read_cfg(cfg_path) args = Namespace(output_dir=str(tmp_path / "gen"), manifests_dir=str(tmp_path / "k8s")) try: _build_overlay(cfg, args) except SystemExit as exc: assert "incompatible with k8s mode" in str(exc) else: raise AssertionError("Expected ingress-class guardrail to abort")