Files
fn_registry/python/functions/pipelines/refresh_local_hub_test.py
T
egutierrez 32c7336bf6 feat(infra): auto-commit con 56 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-21 14:22:55 +02:00

168 lines
5.8 KiB
Python

"""Tests para refresh_local_hub.
No recarga Caddy ni reinicia Glance de verdad: mockea `subprocess.run`. Escribe las
configs generadas en un `tmp_path` parcheando las rutas de salida. Mockea
`discover_local_services` para devolver 2 servicios fijos (no depende de puertos reales).
"""
from __future__ import annotations
import os
import yaml
import pytest
from pipelines import refresh_local_hub as rlh_module
from pipelines.refresh_local_hub import refresh_local_hub
FAKE_SERVICES = [
{
"name": "metabase",
"subdomain": "metabase",
"port": 3030,
"health_path": "/api/health",
"title": "Metabase",
"icon": "si:metabase",
"category": "Datos",
"up": True,
},
{
"name": "portainer",
"subdomain": "portainer",
"port": 9000,
"health_path": "/",
"title": "Portainer",
"icon": "si:portainer",
"category": "Infra",
"up": False,
},
]
def _write_manifest(tmp_path) -> str:
"""Crea un manifiesto YAML mínimo y devuelve su ruta."""
manifest = {
"dashboard_subdomain": "home",
"glance_port": 8585,
"services": [],
}
path = os.path.join(str(tmp_path), "local_services.yaml")
with open(path, "w", encoding="utf-8") as fh:
yaml.safe_dump(manifest, fh)
return path
@pytest.fixture
def patched_env(tmp_path, monkeypatch):
"""Parchea discover_local_services, las rutas de salida y subprocess.run."""
# discover_local_services devuelve 2 servicios fijos.
monkeypatch.setattr(rlh_module, "discover_local_services", lambda *a, **k: list(FAKE_SERVICES))
# Rutas de salida hacia tmp_path.
caddy_path = os.path.join(str(tmp_path), "local_hub.caddy")
monkeypatch.setattr(rlh_module, "CADDY_FRAGMENT_PATH", caddy_path)
monkeypatch.setattr(rlh_module, "_registry_root", lambda: str(tmp_path))
# subprocess.run mockeado: registra las llamadas y devuelve un objeto con returncode 0.
calls: list[list[str]] = []
class _FakeProc:
def __init__(self, rc: int = 0) -> None:
self.returncode = rc
self.stdout = ""
self.stderr = ""
def _fake_run(cmd, *args, **kwargs):
calls.append(list(cmd))
return _FakeProc(0)
monkeypatch.setattr(rlh_module.subprocess, "run", _fake_run)
manifest_path = _write_manifest(tmp_path)
return {
"manifest_path": manifest_path,
"caddy_path": caddy_path,
"glance_path": os.path.join(str(tmp_path), "apps", "local_hub", "glance", "glance.yml"),
"calls": calls,
"tmp_path": str(tmp_path),
}
def test_compone_las_tres_funciones_y_escribe_configs(patched_env):
"""compone las tres funciones del registry y escribe ambas configs"""
result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)
# Caddyfile escrito con un bloque por servicio + dashboard.
assert os.path.exists(patched_env["caddy_path"])
caddy_text = open(patched_env["caddy_path"], encoding="utf-8").read()
assert "metabase.localhost" in caddy_text
assert "portainer.localhost" in caddy_text
assert "home.localhost" in caddy_text # bloque del dashboard
assert "reverse_proxy 127.0.0.1:3030" in caddy_text
# Glance escrito con el bloque servidor fijo + la salida de render_glance_config.
assert os.path.exists(result["glance_path"])
glance_text = open(result["glance_path"], encoding="utf-8").read()
assert "host: 127.0.0.1" in glance_text
assert "port: 8585" in glance_text
assert "primary-color: 210 90 70" in glance_text
assert "type: monitor" in glance_text
def test_glance_full_es_yaml_parseable(patched_env):
"""la config completa de Glance generada es YAML parseable"""
result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)
glance_text = open(result["glance_path"], encoding="utf-8").read()
parsed = yaml.safe_load(glance_text)
assert isinstance(parsed, dict)
assert parsed["server"]["host"] == "127.0.0.1"
assert parsed["server"]["port"] == 8585
assert "theme" in parsed
assert "pages" in parsed # la salida de render_glance_config
def test_reload_false_no_llama_subprocess(patched_env):
"""con reload=False no se llama a subprocess.run"""
result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)
assert patched_env["calls"] == []
assert result["reloaded"] is False
assert result["caddy_reload_rc"] is None
assert result["glance_restart_rc"] is None
def test_reload_true_recarga_caddy_y_reinicia_glance(patched_env):
"""con reload=True se invoca caddy reload y systemctl --user restart glance"""
result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=True)
cmds = patched_env["calls"]
assert len(cmds) == 2
assert cmds[0][0] == "caddy" and "reload" in cmds[0]
assert cmds[1] == ["systemctl", "--user", "restart", "glance"]
assert result["reloaded"] is True
assert result["caddy_reload_rc"] == 0
assert result["glance_restart_rc"] == 0
def test_dict_retorno_tiene_claves_esperadas(patched_env):
"""el dict de retorno tiene todas las claves del contrato"""
result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)
expected_keys = {
"total", "up", "down", "caddy_path", "glance_path",
"reloaded", "caddy_reload_rc", "glance_restart_rc", "services",
}
assert expected_keys <= set(result.keys())
assert result["total"] == 2
assert result["up"] == 1
assert result["down"] == 1
assert len(result["services"]) == 2
assert result["services"][0]["subdomain"] == "metabase"
assert {"name", "subdomain", "port", "up"} <= set(result["services"][0].keys())
if __name__ == "__main__":
pytest.main([__file__, "-v"])