fischer-agentkit/tests/documents/test_excel_renderer.py

125 lines
4.2 KiB
Python

"""Tests for ExcelRenderer — Markdown/JSON → .xlsx mapping (U3)."""
from __future__ import annotations
import json
from pathlib import Path
from openpyxl import load_workbook
from agentkit.documents.renderers.excel_renderer import ExcelRenderer
def _render(content: str, tmp_path: Path) -> Path:
out = tmp_path / "test.xlsx"
ExcelRenderer().render(content, out)
return out
def _read_workbook(path: Path) -> dict[str, list[list[str]]]:
"""Return {sheet_name: [[row cells], ...]} from a .xlsx file."""
wb = load_workbook(str(path))
result: dict[str, list[list[str]]] = {}
for ws in wb.worksheets:
rows: list[list[str]] = []
for row in ws.iter_rows(values_only=True):
rows.append([str(c) if c is not None else "" for c in row])
result[ws.title] = rows
return result
def test_markdown_single_table(tmp_path: Path) -> None:
"""A single GFM table becomes a Table1 sheet with correct data."""
md = "| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |\n"
path = _render(md, tmp_path)
sheets = _read_workbook(path)
assert "Table1" in sheets
rows = sheets["Table1"]
assert rows[0] == ["Name", "Age"]
assert rows[1] == ["Alice", "30"]
assert rows[2] == ["Bob", "25"]
def test_markdown_multiple_tables(tmp_path: Path) -> None:
"""Multiple GFM tables become separate sheets (Table1, Table2)."""
md = (
"| A | B |\n| --- | --- |\n| 1 | 2 |\n\n"
"Some text between.\n\n"
"| C | D |\n| --- | --- |\n| 3 | 4 |\n"
)
path = _render(md, tmp_path)
sheets = _read_workbook(path)
assert "Table1" in sheets
assert "Table2" in sheets
assert sheets["Table1"][0] == ["A", "B"]
assert sheets["Table2"][0] == ["C", "D"]
def test_markdown_no_table_creates_summary(tmp_path: Path) -> None:
"""Markdown without tables puts text lines in a Summary sheet."""
md = "Just some text.\nAnother line.\n"
path = _render(md, tmp_path)
sheets = _read_workbook(path)
# At least one sheet exists with the text
all_text = []
for rows in sheets.values():
all_text.extend(cell for row in rows for cell in row)
assert "Just some text." in all_text
assert "Another line." in all_text
def test_json_input_multi_sheet(tmp_path: Path) -> None:
"""JSON input {sheet: rows} creates named sheets."""
data = {
"Sales": [["Product", "Revenue"], ["Widget", "1000"], ["Gadget", "2000"]],
"Costs": [["Item", "Amount"], ["Rent", "500"]],
}
path = _render(json.dumps(data), tmp_path)
sheets = _read_workbook(path)
assert "Sales" in sheets
assert "Costs" in sheets
assert sheets["Sales"][0] == ["Product", "Revenue"]
assert sheets["Sales"][1] == ["Widget", "1000"]
assert sheets["Costs"][1] == ["Rent", "500"]
def test_json_input_single_sheet(tmp_path: Path) -> None:
"""JSON with one sheet creates exactly that sheet."""
data = {"Data": [["X", "Y"], ["1", "2"]]}
path = _render(json.dumps(data), tmp_path)
sheets = _read_workbook(path)
assert "Data" in sheets
assert sheets["Data"][0] == ["X", "Y"]
def test_empty_markdown(tmp_path: Path) -> None:
"""Empty input produces a valid workbook with at least one sheet."""
path = _render("", tmp_path)
assert path.exists()
wb = load_workbook(str(path))
assert len(wb.sheetnames) >= 1
def test_mixed_table_and_text(tmp_path: Path) -> None:
"""Text before/after a table goes to Summary, table goes to Table1."""
md = "Intro line.\n\n| Col1 | Col2 |\n| --- | --- |\n| a | b |\n\nOutro line.\n"
path = _render(md, tmp_path)
sheets = _read_workbook(path)
assert "Table1" in sheets
# Summary should contain intro and outro
if "Summary" in sheets:
summary_cells = [cell for row in sheets["Summary"] for cell in row]
assert "Intro line." in summary_cells
assert "Outro line." in summary_cells
def test_long_sheet_name_truncated(tmp_path: Path) -> None:
"""Sheet names longer than 31 chars are truncated (Excel limit)."""
long_name = "A" * 50
data = {long_name: [["x"]]}
path = _render(json.dumps(data), tmp_path)
wb = load_workbook(str(path))
# The sheet name should be at most 31 chars
for name in wb.sheetnames:
assert len(name) <= 31