Build & Deploy ResearchOwl / build-and-push (push) Successful in 7s
Al publicar el draft EN en Ghost, el título propuesto se compara contra los posts published+scheduled del sitio (corpus vía Admin API — incluye la cola programada, justo el caso del doble Kecksburg del 2026-07-10) con el topic_collision vendorizado. Si colisiona, el notice de Telegram lleva un bloque "🚨 Posible colisión de tema" en las tres rutas (live, dryrun, bare). Nunca bloquea: el draft se crea igual, el humano decide (fusionar, retitular o enlazar a propósito). Solo lang=en (stopwords inglesas). Títulos ajenos saneados de entidades Markdown (regla de _safe_send). Aislamiento: cualquier fallo del check → notice sin bloque y warning en logs, jamás rompe la publicación. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
from src.seo.autofill import ALLOWED_TAGS, DEFAULT_TAG, _coerce, _system_prompt
|
|
|
|
BASE = {
|
|
"meta_title": "t",
|
|
"meta_description": "d",
|
|
"custom_excerpt": "e",
|
|
"image_query": "q",
|
|
"image_context": "c",
|
|
}
|
|
|
|
|
|
def test_coerce_es_drops_invented_tags():
|
|
obj = dict(BASE, tags=["uap", "humanoides", "Desclasificados", "investigacion-2"])
|
|
out = _coerce(obj, "es")
|
|
assert out["tags"] == ["uap", "desclasificados"]
|
|
|
|
|
|
def test_coerce_es_falls_back_to_default():
|
|
obj = dict(BASE, tags=["pentagono", "encuentros-cercanos"])
|
|
out = _coerce(obj, "es")
|
|
assert out["tags"] == [DEFAULT_TAG["es"]]
|
|
|
|
|
|
def test_coerce_en_still_constrained():
|
|
obj = dict(BASE, tags=["uap", "investigacion"])
|
|
out = _coerce(obj, "en")
|
|
assert out["tags"] == ["uap"]
|
|
|
|
|
|
def test_system_prompt_lists_allowed_tags_per_lang():
|
|
es = _system_prompt("es")
|
|
en = _system_prompt("en")
|
|
for tag in ALLOWED_TAGS["es"]:
|
|
assert tag in es
|
|
# La regla anti-legacy 'never use "investigacion"' es solo para EN: en ES
|
|
# "investigacion" es el tag canónico del allow-list.
|
|
assert 'never use "investigacion"' in en
|
|
assert 'never use "investigacion"' not in es
|
|
assert "ONLY from this exact list" in es
|
|
|
|
|
|
# ─── topic collision ─────────────────────────────────────────────────────────
|
|
|
|
from src.seo.autofill import collision_notice, _slugify_title
|
|
|
|
CORPUS = [
|
|
{"id": "1", "status": "published", "slug": "kecksburg-1965-acorn-ufo-missing-nasa-files",
|
|
"title": 'Kecksburg 1965: The Acorn-Shaped Object, the Missing NASA Files, and "Pennsylvania\'s Roswell"'},
|
|
{"id": "2", "status": "scheduled", "slug": "uss-russell-2019-pyramid-uap-channel-islands",
|
|
"title": "USS Russell 2019: The Pyramid UAP Video and the Channel Islands Drone Swarm"},
|
|
]
|
|
|
|
|
|
def test_collision_fires_on_same_case_and_year():
|
|
note = collision_notice("Kecksburg 1965: New Acorn Evidence", CORPUS)
|
|
assert note is not None
|
|
assert "kecksburg" in note.lower()
|
|
assert "1965" in note
|
|
|
|
|
|
def test_collision_none_on_distinct_case():
|
|
assert collision_notice("Tehran 1976: The Jet-Disabling Encounter", CORPUS) is None
|
|
|
|
|
|
def test_collision_none_on_empty_corpus():
|
|
assert collision_notice("Kecksburg 1965: Anything", []) is None
|
|
|
|
|
|
def test_collision_note_is_markdown_safe():
|
|
corpus = [{"id": "9", "status": "published", "slug": "weird-1990-case",
|
|
"title": "Weird *1990* [Case] with_underscores and `ticks`"}]
|
|
note = collision_notice("Weird 1990: Case Revisited", corpus)
|
|
assert note is not None
|
|
# las entidades Markdown de títulos ajenos se sanean (solo quedan las nuestras)
|
|
bullets = [line for line in note.split("\n") if line.startswith("• ")]
|
|
assert bullets
|
|
for line in bullets:
|
|
for ch in "*_`[]":
|
|
assert ch not in line
|
|
|
|
|
|
def test_slugify_title():
|
|
assert _slugify_title("USS Russell 2019: The Pyramid UAP!") == "uss-russell-2019-the-pyramid-uap"
|