"""Tests para discover_local_services.""" import os import socket import sys import yaml # El modulo hoja se importa por su nombre directo; aseguramos que su directorio # esta en sys.path para poder correr el test desde cualquier cwd. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from discover_local_services import discover_local_services NORMALIZED_KEYS = { "name", "subdomain", "port", "health_path", "title", "icon", "category", "rewrite_host", "up", } def _write_manifest(tmp_path, services): manifest = { "dashboard_subdomain": "home", "glance_port": 8585, "services": services, } path = tmp_path / "local_services.yaml" path.write_text(yaml.safe_dump(manifest), encoding="utf-8") return str(path) def test_golden_service_up_with_all_keys(tmp_path): # Abrimos un socket real en un puerto efímero para simular un servicio vivo. listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listener.bind(("127.0.0.1", 0)) listener.listen(1) port = listener.getsockname()[1] try: manifest = _write_manifest(tmp_path, [ { "name": "metabase", "subdomain": "metabase", "port": port, "health_path": "/api/health", "title": "Metabase", "icon": "si:metabase", "category": "Datos", }, ]) result = discover_local_services(manifest, include_registry=False) assert len(result) == 1 svc = result[0] # Todas las claves normalizadas presentes. assert set(svc.keys()) == NORMALIZED_KEYS assert svc["up"] is True assert svc["name"] == "metabase" assert svc["port"] == port assert svc["health_path"] == "/api/health" finally: listener.close() def test_edge_closed_port_is_down(tmp_path): # Tomamos un puerto efímero y lo cerramos inmediatamente -> debe estar down. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) closed_port = s.getsockname()[1] s.close() manifest = _write_manifest(tmp_path, [ { "name": "ghost", "subdomain": "ghost", "port": closed_port, "health_path": "/", "title": "Ghost", "icon": "", "category": "Otros", }, ]) result = discover_local_services(manifest, include_registry=False) assert len(result) == 1 assert result[0]["up"] is False def test_defaults_derived_for_missing_fields(tmp_path): # Servicio mínimo: solo name + port. El resto debe derivarse con defaults. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 0)) closed_port = s.getsockname()[1] s.close() manifest = _write_manifest(tmp_path, [ {"name": "barebones", "port": closed_port}, ]) result = discover_local_services(manifest, include_registry=False) svc = result[0] assert set(svc.keys()) == NORMALIZED_KEYS assert svc["title"] == "barebones" # derivado de name assert svc["icon"] == "" # default assert svc["category"] == "Otros" # default assert svc["health_path"] == "/" # default assert svc["subdomain"] == "barebones" # derivado de name assert svc["up"] is False def test_empty_manifest_returns_empty_list(tmp_path): manifest = _write_manifest(tmp_path, []) result = discover_local_services(manifest, include_registry=False) assert result == [] def test_rewrite_host_passthrough_desde_manifiesto(tmp_path): # Un servicio con rewrite_host: true en el manifiesto debe propagar # rewrite_host == True; uno sin la clave debe dar rewrite_host == False. listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listener.bind(("127.0.0.1", 0)) listener.listen(1) port = listener.getsockname()[1] try: manifest = _write_manifest(tmp_path, [ { "name": "jupyter", "subdomain": "jupyter", "port": port, "rewrite_host": True, }, { "name": "metabase", "subdomain": "metabase", "port": port, # sin clave rewrite_host -> default False }, ]) result = discover_local_services(manifest, include_registry=False) by_name = {s["name"]: s for s in result} assert by_name["jupyter"]["rewrite_host"] is True assert by_name["metabase"]["rewrite_host"] is False finally: listener.close()