32c7336bf6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
5.8 KiB
Python
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"])
|