test_card_builder.py 5.34 KB
"""Unit tests for card_builder module."""

import json
import pytest

from hermes_feishu.card_builder import (
    build_content_card,
    build_mixed_card,
    build_table_card,
    card_to_json,
)
from hermes_feishu.table_parser import ParsedTable, TableColumn, TableCell


class TestBuildTableCard:
    def _make_table(self):
        return ParsedTable(
            headers=[
                TableColumn(name="Name", index=0, field_type="text"),
                TableColumn(name="Score", index=1, field_type="number"),
            ],
            rows=[
                [TableCell(text="Alice"), TableCell(text="95")],
                [TableCell(text="Bob"), TableCell(text="87.5")],
            ],
            raw_markdown="| Name | Score |\n| --- | --- |\n| Alice | 95 |\n| Bob | 87.5 |",
        )

    def test_basic_structure(self):
        table = self._make_table()
        card = build_table_card(table)
        assert card["config"]["wide_screen_mode"] is True
        assert card["header"]["title"]["content"] == "📊 数据表格"
        assert card["header"]["template"] == "blue"

    def test_table_element(self):
        table = self._make_table()
        card = build_table_card(table)
        elements = card["elements"]
        assert len(elements) == 1
        assert elements[0]["tag"] == "table"

    def test_columns(self):
        table = self._make_table()
        card = build_table_card(table)
        columns = card["elements"][0]["columns"]
        assert len(columns) == 2
        assert columns[0]["field_type"] == "text"
        assert columns[0]["name"] == "Name"
        assert columns[1]["field_type"] == "number"
        assert columns[1]["name"] == "Score"

    def test_rows(self):
        table = self._make_table()
        card = build_table_card(table)
        rows = card["elements"][0]["rows"]
        assert len(rows) == 2
        assert rows[0][0]["text"] == "Alice"
        assert rows[0][1]["text"] == "95"
        assert rows[0][1]["value"] == 95
        # 87.5 stays as float
        assert rows[1][1]["value"] == 87.5

    def test_custom_title(self):
        table = self._make_table()
        card = build_table_card(table, title="Custom Title")
        assert card["header"]["title"]["content"] == "Custom Title"

    def test_custom_template(self):
        table = self._make_table()
        card = build_table_card(table, template="green")
        assert card["header"]["template"] == "green"


class TestBuildContentCard:
    def test_basic_markdown(self):
        card = build_content_card("**Hello** World")
        assert len(card["elements"]) == 1
        assert card["elements"][0]["tag"] == "markdown"
        assert card["elements"][0]["content"] == "**Hello** World"

    def test_with_title(self):
        card = build_content_card("Content", title="Title")
        assert card["header"]["title"]["content"] == "Title"
        assert len(card["elements"]) == 1

    def test_without_title(self):
        card = build_content_card("Content")
        assert "header" not in card
        assert len(card["elements"]) == 1

    def test_wide_screen(self):
        card = build_content_card("Content")
        assert card["config"]["wide_screen_mode"] is True


class TestBuildMixedCard:
    def test_with_table_returns_card(self):
        md = "Intro text\n| A | B |\n| --- | --- |\n| 1 | 2 |\nOutro"
        card = build_mixed_card(md)
        assert card is not None
        # Should have 3 elements: markdown (intro) + table + markdown (outro)
        assert len(card["elements"]) == 3
        assert card["elements"][0]["tag"] == "markdown"
        assert card["elements"][1]["tag"] == "table"
        assert card["elements"][2]["tag"] == "markdown"

    def test_without_table_returns_none(self):
        md = "Just text here, no tables"
        card = build_mixed_card(md)
        assert card is None

    def test_with_title(self):
        md = "| A | B |\n| --- | --- |\n| 1 | 2 |"
        card = build_mixed_card(md, title="Report")
        assert card is not None
        assert card["header"]["title"]["content"] == "Report"

    def test_only_table(self):
        md = "| Name | Value |\n| --- | --- |\n| X | 100 |"
        card = build_mixed_card(md)
        assert card is not None
        assert len(card["elements"]) == 1
        assert card["elements"][0]["tag"] == "table"

    def test_text_between_tables(self):
        md = "| A | B |\n| --- | --- |\n| 1 | 2 |\nMiddle text\n| X | Y |\n| --- | --- |\n| a | b |"
        card = build_mixed_card(md)
        assert card is not None
        # table + markdown + table
        assert len(card["elements"]) == 3
        assert card["elements"][0]["tag"] == "table"
        assert card["elements"][1]["tag"] == "markdown"
        assert "Middle" in card["elements"][1]["content"]
        assert card["elements"][2]["tag"] == "table"


class TestCardToJson:
    def test_serialization(self):
        card = build_content_card("Test")
        json_str = card_to_json(card)
        assert isinstance(json_str, str)
        parsed = json.loads(json_str)
        assert parsed["config"]["wide_screen_mode"] is True

    def test_cjk_support(self):
        card = build_content_card("你好世界", title="测试标题")
        json_str = card_to_json(card)
        assert "你好世界" in json_str
        assert "测试标题" in json_str
        # ensure_ascii=False means CJK characters are not escaped
        assert "\\u" not in json_str