test(pseo): add 45 tests for health checks + pSEO Engine admin routes
Covers content/health.py (get_template_stats, get_template_freshness,
get_content_gaps, check_hreflang_orphans, check_missing_build_files,
check_broken_scenario_refs, get_all_health_issues) and all 6 routes in
admin/pseo_routes.py (dashboard, health partial, gaps partial, generate
gaps, jobs list, job status polling).
Also fixes two bugs found while writing tests:
- check_hreflang_orphans: was grouping by url_path, but EN/DE articles
have different paths. Now extracts natural key from slug pattern
"{template_slug}-{lang}-{nk}" and groups by nk.
- pseo_job_status.html + pseo_jobs.html: | default('') | truncate() fails
when completed_at is None (default() only handles undefined, not None).
Fixed to (value or '') | truncate().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,8 +32,8 @@
|
||||
</div>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="text-xs text-slate">{{ job.created_at | default('') | truncate(19, True, '') }}</td>
|
||||
<td class="text-xs text-slate">{{ job.completed_at | default('') | truncate(19, True, '') }}</td>
|
||||
<td class="text-xs text-slate">{{ (job.created_at or '') | truncate(19, True, '') }}</td>
|
||||
<td class="text-xs text-slate">{{ (job.completed_at or '') | truncate(19, True, '') }}</td>
|
||||
<td>
|
||||
{% if job.error %}
|
||||
<details>
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
</div>
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="text-xs text-slate">{{ job.created_at | default('') | truncate(19, True, '') }}</td>
|
||||
<td class="text-xs text-slate">{{ job.completed_at | default('') | truncate(19, True, '') }}</td>
|
||||
<td class="text-xs text-slate">{{ (job.created_at or '') | truncate(19, True, '') }}</td>
|
||||
<td class="text-xs text-slate">{{ (job.completed_at or '') | truncate(19, True, '') }}</td>
|
||||
<td>
|
||||
{% if job.error %}
|
||||
<details>
|
||||
|
||||
@@ -235,10 +235,14 @@ async def check_hreflang_orphans(templates: list[dict]) -> list[dict]:
|
||||
For example: city-cost-de generates EN + DE. If the EN article exists but
|
||||
DE is absent, that article is an hreflang orphan.
|
||||
|
||||
Orphan detection is based on the slug pattern "{template_slug}-{lang}-{natural_key}".
|
||||
Articles are grouped by natural key; if any expected language is missing, the group
|
||||
is an orphan.
|
||||
|
||||
Returns list of dicts:
|
||||
{
|
||||
"template_slug": str,
|
||||
"url_path": str,
|
||||
"url_path": str, # url_path of one present article for context
|
||||
"present_languages": list[str],
|
||||
"missing_languages": list[str],
|
||||
}
|
||||
@@ -250,24 +254,39 @@ async def check_hreflang_orphans(templates: list[dict]) -> list[dict]:
|
||||
continue # Single-language template — no orphans possible.
|
||||
|
||||
rows = await fetch_all(
|
||||
"""SELECT url_path,
|
||||
GROUP_CONCAT(language) as langs,
|
||||
COUNT(DISTINCT language) as lang_count
|
||||
FROM articles
|
||||
WHERE template_slug = ? AND status = 'published'
|
||||
GROUP BY url_path
|
||||
HAVING COUNT(DISTINCT language) < ?""",
|
||||
(t["slug"], len(expected)),
|
||||
"SELECT slug, language, url_path FROM articles"
|
||||
" WHERE template_slug = ? AND status = 'published'",
|
||||
(t["slug"],),
|
||||
)
|
||||
|
||||
# Group by natural key extracted from slug pattern:
|
||||
# "{template_slug}-{lang}-{natural_key}" → strip template prefix, then lang prefix.
|
||||
slug_prefix = t["slug"] + "-"
|
||||
by_nk: dict[str, dict] = {} # nk → {"langs": set, "url_path": str}
|
||||
for r in rows:
|
||||
present = set(r["langs"].split(","))
|
||||
slug = r["slug"]
|
||||
lang = r["language"]
|
||||
if not slug.startswith(slug_prefix):
|
||||
continue
|
||||
rest = slug[len(slug_prefix):] # "{lang}-{natural_key}"
|
||||
lang_prefix = lang + "-"
|
||||
if not rest.startswith(lang_prefix):
|
||||
continue
|
||||
nk = rest[len(lang_prefix):]
|
||||
if nk not in by_nk:
|
||||
by_nk[nk] = {"langs": set(), "url_path": r["url_path"]}
|
||||
by_nk[nk]["langs"].add(lang)
|
||||
|
||||
for nk, info in by_nk.items():
|
||||
present = info["langs"]
|
||||
missing = sorted(expected - present)
|
||||
orphans.append({
|
||||
"template_slug": t["slug"],
|
||||
"url_path": r["url_path"],
|
||||
"present_languages": sorted(present),
|
||||
"missing_languages": missing,
|
||||
})
|
||||
if missing:
|
||||
orphans.append({
|
||||
"template_slug": t["slug"],
|
||||
"url_path": info["url_path"],
|
||||
"present_languages": sorted(present),
|
||||
"missing_languages": missing,
|
||||
})
|
||||
|
||||
return orphans
|
||||
|
||||
|
||||
Reference in New Issue
Block a user