estado: comparar twitter:site contra el handle real, no contra @ghost
El chequeo tenía una lista negra de un solo elemento (el default de Ghost) y por eso se le colaba el caso peor: el sitio ES apuntaba a @ZonaDeExclusion, un handle que no existe. Un handle ajeno o abandonado es más grave que el default, porque cualquiera puede reclamarlo y quedarse con la atribución de las tarjetas de X. Hay UNA sola cuenta para los dos blogs (@ExclusionZoneEN), así que se declara como constante y se compara contra ella. También avisa si el meta desaparece del todo: vaciar el ajuste en Ghost lo quita, y el silencio no es lo mismo que estar bien. Cuatro tests, y verificado que el chequeo se ve fallar con el handle esperado falseado — no es una comparación vacua. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+16
-3
@@ -53,6 +53,10 @@ SITES = {
|
|||||||
}
|
}
|
||||||
AQUI = os.path.dirname(os.path.abspath(__file__))
|
AQUI = os.path.dirname(os.path.abspath(__file__))
|
||||||
ROBOTS_SNAP = os.path.join(AQUI, "robots-esperado.json")
|
ROBOTS_SNAP = os.path.join(AQUI, "robots-esperado.json")
|
||||||
|
|
||||||
|
# Hay UNA sola cuenta de X para los dos blogs, y es la inglesa (confirmado por
|
||||||
|
# Jose el 2026-07-30). El autopost del RSS inglés y el CLI `ze` tiran de ella.
|
||||||
|
HANDLE_X = "@ExclusionZoneEN"
|
||||||
UA = "seo-estado/1.0 (+https://chemavx.xyz)"
|
UA = "seo-estado/1.0 (+https://chemavx.xyz)"
|
||||||
TIMEOUT = 25
|
TIMEOUT = 25
|
||||||
|
|
||||||
@@ -221,10 +225,19 @@ def check_head(site, cfg, posts):
|
|||||||
out.append("SCHEMA DUPLICADO: " + " + ".join(
|
out.append("SCHEMA DUPLICADO: " + " + ".join(
|
||||||
f"{t}«{hd}»" for t, hd in arts) + " — Google elige cuál usa")
|
f"{t}«{hd}»" for t, hd in arts) + " — Google elige cuál usa")
|
||||||
|
|
||||||
|
# Comparar contra el handle REAL, no contra la lista de valores malos que se
|
||||||
|
# nos ocurran. Este chequeo solo miraba el default de Ghost (@ghost), y por
|
||||||
|
# eso se le pasó lo peor de los dos: el ES apuntaba a @ZonaDeExclusion, un
|
||||||
|
# handle que no existe. Un handle ajeno o abandonado es más grave que el
|
||||||
|
# default, porque cualquiera puede reclamarlo y quedarse con la atribución
|
||||||
|
# de las tarjetas. Solo hay UNA cuenta de X para los dos blogs.
|
||||||
tw = re.findall(r'name="twitter:site" content="([^"]*)"', h)
|
tw = re.findall(r'name="twitter:site" content="([^"]*)"', h)
|
||||||
if tw and tw[0].lower() in ("@ghost", "ghost"):
|
if not tw:
|
||||||
out.append(f"twitter:site sigue en el default de Ghost ({tw[0]}): "
|
out.append("no hay twitter:site: las tarjetas de X salen sin atribuir")
|
||||||
"atribuye las tarjetas a la cuenta de Ghost")
|
elif tw[0].lower() != HANDLE_X.lower():
|
||||||
|
que = (" (el default de Ghost, atribuye las tarjetas a la cuenta de Ghost)"
|
||||||
|
if tw[0].lower() in ("@ghost", "ghost") else "")
|
||||||
|
out.append(f"twitter:site es {tw[0]}{que} y debería ser {HANDLE_X}")
|
||||||
pub_fb = re.findall(r'property="article:publisher" content="([^"]*)"', h)
|
pub_fb = re.findall(r'property="article:publisher" content="([^"]*)"', h)
|
||||||
if pub_fb and "facebook.com/ghost" in pub_fb[0]:
|
if pub_fb and "facebook.com/ghost" in pub_fb[0]:
|
||||||
out.append("article:publisher sigue en el default de Ghost "
|
out.append("article:publisher sigue en el default de Ghost "
|
||||||
|
|||||||
@@ -193,6 +193,51 @@ def test_detecta_dos_archivos_de_tag_con_el_mismo_title():
|
|||||||
assert any("mismo <title>" in l for l in salida), salida
|
assert any("mismo <title>" in l for l in salida), salida
|
||||||
|
|
||||||
|
|
||||||
|
# ───────── twitter:site: el handle bueno, no «cualquiera menos @ghost» ─────────
|
||||||
|
|
||||||
|
def _head_falso(html):
|
||||||
|
"""Sustituye la descarga del HTML público por una cabecera de mentira."""
|
||||||
|
return lambda url: html
|
||||||
|
|
||||||
|
|
||||||
|
POST_MIN = [{"slug": "p", "status": "published"}]
|
||||||
|
|
||||||
|
|
||||||
|
def _check_head_con(html):
|
||||||
|
orig = E.trae
|
||||||
|
E.trae = _head_falso(html)
|
||||||
|
try:
|
||||||
|
return E.check_head("es", E.SITES["es"], POST_MIN)
|
||||||
|
finally:
|
||||||
|
E.trae = orig
|
||||||
|
|
||||||
|
|
||||||
|
def test_un_handle_ajeno_no_pasa_por_bueno():
|
||||||
|
"""El chequeo solo miraba el default de Ghost, así que se le colaba lo PEOR
|
||||||
|
de los dos: el ES apuntaba a @ZonaDeExclusion, un handle que no existe. Un
|
||||||
|
handle abandonado lo puede reclamar cualquiera y quedarse la atribución."""
|
||||||
|
salida = _check_head_con('<meta name="twitter:site" content="@ZonaDeExclusion">')
|
||||||
|
assert any("@ZonaDeExclusion" in l and "debería ser" in l for l in salida), salida
|
||||||
|
|
||||||
|
|
||||||
|
def test_el_handle_bueno_no_genera_hallazgo():
|
||||||
|
salida = _check_head_con(f'<meta name="twitter:site" content="{E.HANDLE_X}">')
|
||||||
|
assert not any("twitter:site" in l for l in salida), salida
|
||||||
|
|
||||||
|
|
||||||
|
def test_el_default_de_ghost_sigue_diciendo_que_es_el_default():
|
||||||
|
"""Regresión: al pasar a comparar contra el handle real, el caso @ghost tiene
|
||||||
|
que seguir explicando POR QUÉ está mal, no solo que no coincide."""
|
||||||
|
salida = _check_head_con('<meta name="twitter:site" content="@ghost">')
|
||||||
|
assert any("default de Ghost" in l for l in salida), salida
|
||||||
|
|
||||||
|
|
||||||
|
def test_sin_twitter_site_tambien_avisa():
|
||||||
|
"""Vaciar el ajuste en Ghost quita el meta entero. Silencio no es OK."""
|
||||||
|
salida = _check_head_con("<title>nada</title>")
|
||||||
|
assert any("no hay twitter:site" in l for l in salida), salida
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
fallos = 0
|
fallos = 0
|
||||||
for nombre, fn in sorted(globals().items()):
|
for nombre, fn in sorted(globals().items()):
|
||||||
|
|||||||
Reference in New Issue
Block a user