#!/usr/bin/env python3 """Tests de seo_estado.py — los puntos ciegos del vigilante. Cada test de aquí es la cicatriz de un fallo REAL de la semana del 2026-07-29. Se ejercitan las dos cosas que hacen creíble a un vigilante: 1. que DETECTA la avería (no basta con que no dé error) 2. que un chequeo roto no deja el informe limpio por accidente python3 -m pytest test_seo_estado.py -q python3 test_seo_estado.py # sin pytest """ import json import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import seo_estado as E def _tags_falsos(tags): """Sustituye la llamada al CLI por una lista de tags de mentira.""" payload = json.dumps({"tags": tags}) return lambda cmd, timeout=180: type( "R", (), {"returncode": 0, "stdout": payload, "stderr": ""})() # ─────────────────────── tags: el bug que vivió 3 semanas ─────────────────────── def test_detecta_el_duplicado_de_ghost(): """`Casos Militares` + `casos-militares` → Ghost crea `casos-militares-2`. Es la firma exacta de researchowl mandando el slug en el campo `name`.""" orig = E.sh E.sh = _tags_falsos([ {"slug": "casos-militares", "name": "Casos Militares", "visibility": "public"}, {"slug": "casos-militares-2", "name": "casos-militares", "visibility": "public"}, ]) try: salida = E.check_tags("es", E.SITES["es"], [ {"slug": "p", "status": "published", "tags": [{"slug": "casos-militares-2", "visibility": "public"}]}]) finally: E.sh = orig assert any("DUPLICADOS" in l for l in salida), salida def test_no_inventa_duplicados_donde_no_los_hay(): orig = E.sh E.sh = _tags_falsos([ {"slug": "casos-militares", "name": "Casos Militares", "visibility": "public"}, {"slug": "casos-espana", "name": "Casos España", "visibility": "public"}, ]) try: salida = E.check_tags("es", E.SITES["es"], [ {"slug": "p", "status": "published", "tags": [ {"slug": "casos-militares", "visibility": "public"}, {"slug": "casos-espana", "visibility": "public"}]}]) finally: E.sh = orig assert not any("DUPLICADOS" in l for l in salida), salida def test_ve_los_programados_sin_categoria(): """Los 9 de agosto salían con solo el tag interno #agosto-2026 y se habrían publicado sin ninguna categoría. `check_rules` de seo_watch los salta.""" orig = E.sh E.sh = _tags_falsos([{"slug": "uap", "name": "UAP", "visibility": "public"}]) try: salida = E.check_tags("es", E.SITES["es"], [ {"slug": "agosto-1", "status": "scheduled", "tags": [{"slug": "hash-agosto-2026", "visibility": "internal"}]}, {"slug": "vivo", "status": "published", "tags": [{"slug": "uap", "visibility": "public"}]}]) finally: E.sh = orig assert any("scheduled SIN CATEGORÍA" in l for l in salida), salida # ────────────────────────────── robots: la deriva ────────────────────────────── ROBOTS_EN = """ User-agent: * Content-Signal: search=yes,ai-train=no,use=reference Allow: / User-agent: GPTBot User-agent: CCBot Disallow: / User-agent: * Sitemap: https://x/sitemap.xml Disallow: /ghost/ """ def test_parser_de_robots_agrupa_agentes_consecutivos(): p = E.politica_robots(ROBOTS_EN) assert p["bloqueados"] == ["CCBot", "GPTBot"], p assert p["content_signal"] == "search=yes,ai-train=no,use=reference", p def test_disallow_parcial_no_cuenta_como_bloqueo(): """`Disallow: /ghost/` NO es bloquear el sitio. Confundirlos daría por bloqueado a todo bot con una regla de directorio.""" assert E.politica_robots(ROBOTS_EN)["bloqueados"] == ["CCBot", "GPTBot"] def test_comentarios_no_confunden_al_parser(): txt = "User-agent: GPTBot # el de OpenAI\nDisallow: / # todo\n" assert E.politica_robots(txt)["bloqueados"] == ["GPTBot"] # ───────────────────── resiliencia: un chequeo roto se OYE ───────────────────── def test_un_chequeo_que_revienta_no_deja_el_informe_limpio(): """Lo peligroso de un vigilante no es que falle: es que falle y parezca que todo está bien.""" orig = E.CHEQUEOS def explota(s, c, p): raise RuntimeError("boom") E.CHEQUEOS = (("explosivo", explota),) try: r = E.revisa("en", []) finally: E.CHEQUEOS = orig assert "explosivo" in r and "ha fallado" in r["explosivo"][0], r # ───────────────── páginas: no fabricar hallazgos inatendibles ───────────────── def test_las_reglas_de_post_que_no_aplican_a_paginas_quedan_fuera(): """og_*/twitter_* vacíos en una PÁGINA no son un fallo: Ghost los deriva de las metas al renderizar (verificado sobre el HTML público del hub ES).""" for regla in ("og_title.empty", "twitter_description.empty", "feature_image.missing", "internal_links.too_few"): assert E._fuera_de_paginas(regla), regla for regla in ("meta_title.missing", "meta_description.too_long"): assert not E._fuera_de_paginas(regla), regla # ───────── archivos de tag: lo que el lector ve y lo que ve Google ───────── def test_detecta_el_nombre_de_tag_que_es_el_slug(): """El H1 del archivo sale del `name`. En el EN los siete se llamaban como su slug: /tag/military-cases/ enseñaba «military-cases» al lector.""" orig = E.sh E.sh = _tags_falsos([ {"slug": "military-cases", "name": "military-cases", "visibility": "public"}, ]) try: salida = E.check_tags("en", E.SITES["en"], [ {"slug": "p", "status": "published", "tags": [{"slug": "military-cases", "visibility": "public"}]}]) finally: E.sh = orig assert any("NOMBRE es el slug" in l for l in salida), salida def test_un_acronimo_de_una_palabra_no_cuenta_como_slug_crudo(): """`uap` llamándose «uap» no es el mismo problema que «military-cases»: sin guion no se lee como slug. Exigir el guion evita el falso positivo.""" orig = E.sh E.sh = _tags_falsos([{"slug": "uap", "name": "uap", "visibility": "public"}]) try: salida = E.check_tags("en", E.SITES["en"], [ {"slug": "p", "status": "published", "tags": [{"slug": "uap", "visibility": "public"}]}]) finally: E.sh = orig assert not any("NOMBRE es el slug" in l for l in salida), salida def test_detecta_dos_archivos_de_tag_con_el_mismo_title(): """Salió al renombrar: `uap` e `investigation` compartían meta_title, o sea dos páginas indexadas compitiendo con el mismo titular.""" orig = E.sh E.sh = _tags_falsos([ {"slug": "uap", "name": "UAP", "visibility": "public", "meta_title": "UAP Investigation — The Exclusion Zone"}, {"slug": "investigation", "name": "Investigation", "visibility": "public", "meta_title": "UAP Investigation — The Exclusion Zone"}, ]) try: salida = E.check_tags("en", E.SITES["en"], [ {"slug": "p", "status": "published", "tags": [ {"slug": "uap", "visibility": "public"}, {"slug": "investigation", "visibility": "public"}]}]) finally: E.sh = orig assert any("mismo