oclaw/tests/test_write_xlsx_tool.py
oliver dbba8cafbf Add write_xlsx tool so agents export Excel via structured data instead of fragile openpyxl scripts.
Generation returns attachment_id only; channel delivery still requires save_deliverable_attachment.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 23:54:16 +08:00

140 lines
5 KiB
Python

from __future__ import annotations
import io
from openpyxl import load_workbook
from runtime.chat.tool_runtime import _attachments_from_tool_result, _ref_type_for_mime
from runtime.tools.public.write_xlsx_tool import XLSX_MIME, write_xlsx_tool
from svc.files.attachment_assets import AttachmentAssetStore
def test_attachment_store_keeps_xlsx_extension(tmp_path) -> None:
store = AttachmentAssetStore(root_dir=tmp_path / "att")
meta = store.save_bytes(b"PK\x03\x04fake", filename="report.xlsx", mime=XLSX_MIME)
assert meta.mime == XLSX_MIME
path = store.get_local_path(meta.attachment_id)
assert path is not None
assert path.suffix == ".xlsx"
blob, loaded = store.load_bytes(meta.attachment_id)
assert blob.startswith(b"PK")
assert loaded is not None
assert loaded.name == "report.xlsx"
def test_write_xlsx_returns_attachment_id_without_deliverable(tmp_path, monkeypatch) -> None:
store = AttachmentAssetStore(root_dir=tmp_path / "att")
monkeypatch.setattr(
"runtime.tools.public.write_xlsx_tool.AttachmentAssetStore",
lambda root_dir=None: store if root_dir is None else AttachmentAssetStore(root_dir=root_dir),
)
spec = write_xlsx_tool()
out = spec.handler(
{
"name": "alarm_summary.xlsx",
"sheets": [
{
"name": "按网元",
"headers": ["host_name", "severity", "count"],
"rows": [["NE-A", "critical", 12], ["NE-B", "major", 5]],
},
{
"name": "汇总",
"headers": ["metric", "value"],
"rows": [["total", 17]],
},
],
}
)
assert out.get("ok") is True
assert out.get("attachment_id")
assert out.get("name") == "alarm_summary.xlsx"
assert out.get("mime") == XLSX_MIME
assert out.get("deliverable") is not True
assert out.get("sheet_count") == 2
assert "hint" in out
blob, meta = store.load_bytes(str(out["attachment_id"]))
assert meta is not None
assert blob[:2] == b"PK"
wb = load_workbook(io.BytesIO(blob))
assert wb.sheetnames == ["按网元", "汇总"]
ws = wb["按网元"]
assert [c.value for c in ws[1]] == ["host_name", "severity", "count"]
assert ws["A2"].value == "NE-A"
assert ws["C2"].value == 12
assert ws.freeze_panes == "A2"
def test_write_xlsx_optional_workspace_path(tmp_path, monkeypatch) -> None:
store = AttachmentAssetStore(root_dir=tmp_path / "att")
monkeypatch.setattr(
"runtime.tools.public.write_xlsx_tool.AttachmentAssetStore",
lambda root_dir=None: store if root_dir is None else AttachmentAssetStore(root_dir=root_dir),
)
target = tmp_path / "workspace" / "tmp" / "out.xlsx"
monkeypatch.setattr(
"runtime.tools.public.write_xlsx_tool.resolve_workspace_path",
lambda raw: target,
)
spec = write_xlsx_tool()
out = spec.handler(
{
"path": "data/workspace/tmp/out.xlsx",
"sheets": [{"name": "S1", "headers": ["a"], "rows": [[1]]}],
}
)
assert out.get("ok") is True
assert out.get("attachment_id")
assert out.get("path") == str(target)
assert target.exists()
assert target.read_bytes()[:2] == b"PK"
def test_write_xlsx_requires_sheets() -> None:
spec = write_xlsx_tool()
out = spec.handler({})
assert out.get("ok") is False
assert out.get("error") == "sheets_required"
def test_write_xlsx_tool_result_maps_to_binary_ref() -> None:
assert _ref_type_for_mime(XLSX_MIME) == "binary_ref"
refs = _attachments_from_tool_result(
{
"ok": True,
"attachment_id": "a" * 64,
"name": "r.xlsx",
"mime": XLSX_MIME,
"bytes": 12,
}
)
assert len(refs) == 1
assert refs[0]["type"] == "binary_ref"
assert refs[0].get("deliverable") is not True
def test_save_deliverable_marks_write_xlsx_attachment(tmp_path, monkeypatch) -> None:
from runtime.tools.public.save_deliverable_attachment_tool import save_deliverable_attachment_tool
store = AttachmentAssetStore(root_dir=tmp_path / "att")
monkeypatch.setattr(
"runtime.tools.public.write_xlsx_tool.AttachmentAssetStore",
lambda root_dir=None: store if root_dir is None else AttachmentAssetStore(root_dir=root_dir),
)
monkeypatch.setattr(
"runtime.tools.public.save_deliverable_attachment_tool.AttachmentAssetStore",
lambda root_dir=None: store if root_dir is None else AttachmentAssetStore(root_dir=root_dir),
)
gen = write_xlsx_tool().handler(
{"sheets": [{"headers": ["x"], "rows": [["你好"]]}], "name": "cn.xlsx"}
)
aid = str(gen["attachment_id"])
marked = save_deliverable_attachment_tool().handler({"attachment_id": aid})
assert marked.get("ok") is True
assert marked.get("deliverable") is True
assert marked.get("attachment_id") == aid
assert marked.get("mime") == XLSX_MIME