"""docx skill — load, eligibility, and create→inspect round-trip.""" from __future__ import annotations import json import sys from pathlib import Path import pytest from opensquilla.skills.eligibility import EligibilityContext, check_eligibility from opensquilla.skills.loader import SkillLoader from opensquilla.skills.meta.executors.skill_exec import run_skill_exec_step from opensquilla.skills.meta.types import MetaStep ROOT = Path(__file__).resolve().parents[1] BUNDLED = ROOT / "src" / "opensquilla" / "skills" / "bundled" DOCX_DIR = BUNDLED / "docx" SCRIPTS = DOCX_DIR / "scripts" def _spec_to_loader() -> object: return SkillLoader(bundled_dir=BUNDLED).get_by_name("docx") def test_skill_loads() -> None: spec = _spec_to_loader() assert spec is not None assert spec.name == "docx" assert spec.metadata is not None assert spec.provenance.origin == "clawhub-mit0" assert spec.provenance.license == "MIT-0" assert spec.entrypoint is not None def test_eligibility_with_python_present(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( "opensquilla.skills.eligibility.shutil.which", lambda name: "/usr/bin/python3" if name in {"python", "python3"} else None, ) spec = _spec_to_loader() assert spec is not None assert check_eligibility(spec, EligibilityContext.auto()) def test_eligibility_without_python(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( "opensquilla.skills.eligibility.shutil.which", lambda name: None, ) spec = _spec_to_loader() assert spec is not None assert not check_eligibility(spec, EligibilityContext.auto()) def test_create_then_inspect_round_trip(tmp_path: Path) -> None: sys.path.insert(0, str(SCRIPTS)) try: import create_docx # type: ignore[import-not-found] import inspect_docx # type: ignore[import-not-found] finally: sys.path.pop(0) spec = { "metadata": {"title": "Round-trip", "author": "Tester"}, "body": [ {"kind": "heading", "level": 1, "text": "Hello"}, {"kind": "paragraph", "text": "World."}, {"kind": "table", "rows": [["A", "B"], ["1", "2"]]}, ], } out_path = tmp_path / "out.docx" doc = create_docx.build(spec) doc.save(str(out_path)) assert out_path.exists() inspected = inspect_docx.inspect(out_path) assert inspected["sections"] >= 1 texts = [p["text"] for p in inspected["paragraphs"]] assert "Hello" in texts assert "World." in texts assert inspected["tables"] and inspected["tables"][0][0] == ["A", "B"] assert inspected["has_tracked_changes"] is False def test_edit_replace_text(tmp_path: Path) -> None: sys.path.insert(0, str(SCRIPTS)) try: import create_docx # type: ignore[import-not-found] import edit_docx # type: ignore[import-not-found] import inspect_docx # type: ignore[import-not-found] finally: sys.path.pop(0) src = tmp_path / "src.docx" create_docx.build( {"body": [{"kind": "paragraph", "text": "Hello {{NAME}}, welcome."}]} ).save(str(src)) from docx import Document doc = Document(str(src)) ops = [{"op": "replace_text", "find": "{{NAME}}", "with": "Wei"}] edit_docx.apply_ops(doc, ops) out = tmp_path / "out.docx" doc.save(str(out)) inspected = inspect_docx.inspect(out) text = " ".join(p["text"] for p in inspected["paragraphs"]) assert "{{NAME}}" not in text assert "Wei" in text def test_inspect_cli_outputs_json(tmp_path: Path) -> None: sys.path.insert(0, str(SCRIPTS)) try: import create_docx # type: ignore[import-not-found] import inspect_docx # type: ignore[import-not-found] finally: sys.path.pop(0) src = tmp_path / "src.docx" create_docx.build({"body": [{"kind": "paragraph", "text": "x"}]}).save(str(src)) payload = inspect_docx.inspect(src) encoded = json.dumps(payload, ensure_ascii=False) assert "paragraphs" in encoded assert "tables" in encoded @pytest.mark.asyncio async def test_skill_exec_exports_markdown_docx(tmp_path: Path) -> None: sys.path.insert(0, str(SCRIPTS)) try: import inspect_docx # type: ignore[import-not-found] finally: sys.path.pop(0) loader = SkillLoader( bundled_dir=BUNDLED, snapshot_path=tmp_path / "skills_snapshot.json", ) loader.invalidate_cache() out_path = tmp_path / "competitive-intel.docx" step = MetaStep( id="export_docx", kind="skill_exec", skill="docx", with_args={ "markdown": ( "# Competitive intel brief\n\n" "Acme has a new hiring signal.\n\n" "| Account | Signal |\n" "|---|---|\n" "| Acme | hiring |\n" ), "output_path": str(out_path), }, ) result = await run_skill_exec_step( step, "docx", {"collected": {"intel_clarify": {"export_docx": "YES"}}}, {"intel_brief_audit": "Competitive intel brief"}, skill_loader=loader, workspace_dir=str(tmp_path), ) assert result == str(out_path) inspected = inspect_docx.inspect(out_path) texts = [p["text"] for p in inspected["paragraphs"]] assert "Competitive intel brief" in texts assert "Acme has a new hiring signal." in texts assert inspected["tables"][0][0] == ["Account", "Signal"] assert inspected["tables"][0][1] == ["Acme", "hiring"]