hermes-agent/tests/skills/test_office_document_skills.py

174 lines
5.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Invariant tests for the bundled office/document skills.
Covers skills/productivity/{docx,xlsx,pdf,powerpoint} — the office
document creation/editing suite. Tests assert contracts (frontmatter
shape, referenced scripts exist, cross-links resolve), not snapshots
of skill content.
"""
from __future__ import annotations
import re
from pathlib import Path
import pytest
import yaml
REPO = Path(__file__).resolve().parent.parent.parent
SKILLS = REPO / "skills"
OPTIONAL_SKILLS = REPO / "optional-skills"
OFFICE_SKILLS = ["docx", "xlsx", "pdf", "powerpoint"]
def _skill_dir(name: str) -> Path:
return SKILLS / "productivity" / name
def _frontmatter(skill_md: Path) -> dict:
text = skill_md.read_text(encoding="utf-8")
match = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
assert match, f"{skill_md} has no YAML frontmatter"
return yaml.safe_load(match.group(1))
@pytest.mark.parametrize("name", OFFICE_SKILLS)
def test_skill_exists_with_frontmatter(name):
skill_md = _skill_dir(name) / "SKILL.md"
assert skill_md.exists(), f"missing {skill_md}"
fm = _frontmatter(skill_md)
assert fm["name"] == name
assert fm["description"].strip()
assert len(fm["description"]) <= 60, (
f"{name}: description is {len(fm['description'])} chars (max 60)"
)
assert fm["description"].rstrip('"').endswith(".")
platforms = fm.get("platforms")
assert platforms, f"{name}: missing platforms gating"
assert set(platforms) <= {"linux", "macos", "windows"}
@pytest.mark.parametrize("name", OFFICE_SKILLS)
def test_referenced_scripts_exist(name):
"""Every scripts/... path mentioned in SKILL.md must exist on disk."""
skill_dir = _skill_dir(name)
body = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
refs = set(re.findall(r"scripts/[\w./-]+\.py", body))
assert refs, f"{name}: SKILL.md references no helper scripts"
for ref in refs:
assert (skill_dir / ref).exists(), f"{name}: SKILL.md references missing {ref}"
@pytest.mark.parametrize("name", OFFICE_SKILLS)
def test_license_file_present(name):
"""Adapted Anthropic skills must carry their LICENSE.txt."""
fm = _frontmatter(_skill_dir(name) / "SKILL.md")
if "LICENSE.txt" in str(fm.get("license", "")):
assert (_skill_dir(name) / "LICENSE.txt").exists(), (
f"{name}: license points to LICENSE.txt but the file is missing"
)
def test_docx_validator_schema_paths_exist():
"""base.py maps XML parts to XSD files — every mapped schema must ship."""
for skill in ("docx", "powerpoint"):
base = _skill_dir(skill) / "scripts" / "office" / "validators" / "base.py"
schemas = _skill_dir(skill) / "scripts" / "office" / "schemas"
text = base.read_text(encoding="utf-8")
refs = set(re.findall(r'"((?:ecma|ISO|mce|microsoft)[\w./-]+\.xsd)"', text))
assert refs, f"{skill}: no schema references found in validators/base.py"
for ref in refs:
assert (schemas / ref).exists(), f"{skill}: validator references missing schema {ref}"
def test_docs_pages_generated():
"""Each bundled office skill has a generated docs-site page."""
docs_dir = REPO / "website" / "docs" / "user-guide" / "skills" / "bundled" / "productivity"
for name in OFFICE_SKILLS:
assert (docs_dir / f"productivity-{name}.md").exists(), (
f"missing generated docs page for {name}; run website/scripts/generate-skill-docs.py"
)
# Document/payload readers must not depend on the host locale. OOXML part
# files are UTF-8 (declared in the XML prolog) and the form-fields JSON the
# agent authors is UTF-8, but these sites used locale-default text mode: on
# Windows (cp1251/GBK/cp932) the validators parsed silently mojibake'd
# document text and the pdf fill scripts wrote mojibake'd values into the
# user's form — or crashed outright where the bytes don't decode.
_ENCODING_SENSITIVE_READS = [
(
"docx/scripts/office/validators/base.py",
'with open(xml_file, "rb") as f:',
),
(
"powerpoint/scripts/office/validators/base.py",
'with open(xml_file, "rb") as f:',
),
(
"pdf/scripts/check_bounding_boxes.py",
'with open(sys.argv[1], encoding="utf-8") as f:',
),
(
"pdf/scripts/create_validation_image.py",
"with open(fields_json_path, 'r', encoding='utf-8') as f:",
),
(
"pdf/scripts/fill_fillable_fields.py",
'with open(fields_json_path, encoding="utf-8") as f:',
),
(
"pdf/scripts/fill_pdf_form_with_annotations.py",
'with open(fields_json_path, "r", encoding="utf-8") as f:',
),
]
def test_check_bounding_boxes_reads_utf8_fields_json(tmp_path):
"""Run the real script on a UTF-8 fields.json with non-ASCII field
descriptions under a forced non-UTF-8 locale. Without the explicit
encoding the json.load crashes (C locale on POSIX; cp1251 chokes on
the 0x98 byte of U+2018 on Windows)."""
import json
import os
import subprocess
import sys
script = _skill_dir("pdf") / "scripts" / "check_bounding_boxes.py"
fields = {
"form_fields": [
{
"description": "Фамилия (label)",
"page_number": 1,
"label_bounding_box": [0, 0, 10, 10],
"entry_bounding_box": [20, 20, 30, 40],
}
]
}
fields_json = tmp_path / "fields.json"
fields_json.write_bytes(
json.dumps(fields, ensure_ascii=False, indent=2).encode("utf-8")
)
env = dict(os.environ)
env.update({"LC_ALL": "C", "LANG": "C", "PYTHONUTF8": "0", "PYTHONIOENCODING": "utf-8"})
result = subprocess.run(
[sys.executable, str(script), str(fields_json)],
capture_output=True,
text=True,
env=env,
timeout=60,
)
assert result.returncode == 0, (
f"script failed under non-UTF-8 locale:\n{result.stderr}"
)
assert "SUCCESS" in result.stdout