Volver a la wiki

Test de contrato de lectura/escritura de métricas

Test de contrato de lectura/escritura de métricas

Propósito

Archivo: tests/monitoring/test_metrics_read_write_contract.py

Valida el contrato entre:

Garantiza que ninguna consulta del lector pregunte por una métrica que nadie escribe — evita errores de dedo, métricas retiradas olvidadas, y falsos negativos silenciosos.

Origen

Descubierto durante la ronda de mantenimiento del 06-08-2026 (task #213): el cálculo de disponibilidad (get_uptime) consultaba ping_reachable, pero esa métrica nunca llegaba a PROD porque el mapa de ingest del Agente no la produce. El test tradicional del CI no lo cazó (llevaba meses en verde con el fallo dentro).

A46 fase 1 (auditoría #261, PR #441, commit d48b681a, v1.85.4, 2026-08-27): hasta aquí el mapa (METRIC_TYPE_MAP) vivía inline en terminal/api/sentinel_ingest.py y este test lo leía con regex sobre el código fuente — el mismo patrón de fallo que dejó pasar el #259 y el #213 (“una capa escribe un nombre y otra lee otro”, detectado tarde porque nadie compara ambas capas contra una fuente única). Ahora el mapa vive en monitoring/metric_names.py y este test importa la tabla directamente en vez de rascarla con regex.

Componentes

1. _metricas_leidas() → set[str]

Extrae por regex los nombres literales que MetricsReader consulta en VictoriaMetrics:

def _metricas_leidas() -> set[str]:
    return set(re.findall(r"([a-z_][a-z0-9_]*)\{\{tenant_id", READER.read_text(...)))

Busca patrones como ping_packet_loss_percent{{tenant_id=... en el cuerpo de metrics_reader.py.

2. _metricas_escritas() → set[str]

Extrae de dos vías de escritura:

  1. MetricsWriter (servidor):

    _create_metric("<nombre>", ...)  # Busca `_create_metric("ping_latency_ms", ...)`
  2. Contrato del ingest del Agente (monitoring/metric_names.py, desde A46 — antes se leía con regex desde terminal/api/sentinel_ingest.py):

    from monitoring.metric_names import vm_names_written
    
    escritas |= vm_names_written()  # union de AGENT_TO_VM.values() + IF_VM_NAMES.values()

Devuelve la unión de ambas.

3. Tests parametrizados

test_toda_metrica_leida_la_escribe_alguien()

def test_toda_metrica_leida_la_escribe_alguien():
    huerfanas = _metricas_leidas() - _metricas_escritas()
    assert not huerfanas, f"Métricas sin escritor: {sorted(huerfanas)}"

Si el lector consulta algo que nadie escribe → FALLA.

test_el_lector_consulta_algo()

Guarda anti-fallo-mudo: si el regex de extracción se rompe (porque cambió la forma de construir las consultas), el test de arriba pasaría vacío en silencio. Este verifica que _metricas_leidas() devuelva al menos 5 resultados.

test_disponibilidad_sale_de_una_metrica_viva(metrica)

Parametrizado para ping_packet_loss_percent. Específicamente verifica:

  1. get_uptime sí contiene esa métrica en su cuerpo.
  2. El contrato de monitoring/metric_names.py sí la produce (metrica in AGENT_TO_VM.values()).
@pytest.mark.parametrize("metrica", ["ping_packet_loss_percent"])
def test_disponibilidad_sale_de_una_metrica_viva(metrica):
    cuerpo = READER.read_text(...)
    get_uptime = cuerpo[...índices de get_uptime...]
    assert metrica in get_uptime, "get_uptime ya no usa ping_packet_loss_percent"

    from monitoring.metric_names import AGENT_TO_VM
    assert metrica in AGENT_TO_VM.values(), f"{metrica} ya no la produce el ingest"

Test hermano: contrato Agente → ingest (A18/A27)

tests/monitoring/test_contrato_agente_cadencia.py (23 tests, mismo PR #441) verifica el lado complementario: que todo metric_type que el Agente emite de verdad (leído por regex de terminal/agent/sentinel/*.py) lo acepta el ingest vía monitoring.metric_names.vm_name_for(). Este test de contrato (lector↔escritor) verifica que lo que se escribe tiene lector; el hermano (Agente↔ingest) verifica que lo que el Agente manda de verdad el ingest lo entiende — cierran el círculo completo Agente → ingest → VictoriaMetrics → lector.

Límite honesto (QUÉ SÍ y QUÉ NO detecta)

✅ SÍ detecta:

❌ NO detecta:

Conclusión: es una red barata que atrapa errores de configuración, pero no sustituye el monitoreo de datos reales.

Fuentes

Véase también

Subir