"""Body rendering as pure functions (ingest-spec §5). Byte-compatible with the reference implementation (portfolio-optimiser): markdown table with `| --- |` separator, escape order `\\` then `|` then newlines→space, SQL value conversion with fail-on-unknown-type, and the verbatim fenced block for http bodies. """ from __future__ import annotations import pytest from llm_ingestion_okf.errors import IngestError, RenderError from llm_ingestion_okf.render import render_fenced_block, render_table, sql_value_to_text # --- error hierarchy --- def test_render_error_is_ingest_error() -> None: assert issubclass(RenderError, IngestError) # --- render_table --- def test_basic_table_bytes() -> None: assert ( render_table(["a", "b"], [["1", "x"], ["2", "y"]]) == "| a | b |\n| --- | --- |\n| 1 | x |\n| 2 | y |\n" ) def test_header_only_table() -> None: assert render_table(["a"], []) == "| a |\n| --- |\n" def test_table_is_lf_only_with_one_trailing_newline() -> None: rendered = render_table(["a"], [["x"]]) assert "\r" not in rendered assert rendered.endswith("|\n") assert not rendered.endswith("\n\n") @pytest.mark.parametrize( ("raw", "escaped"), [ ("a|b", "a\\|b"), # pipe → \| ("a\\b", "a\\\\b"), # backslash → \\ ("|", "\\|"), # escape ORDER: backslash first, else this becomes \\| ("\\|", "\\\\\\|"), # combined: raw \| → \\ + \| ("a\nb", "a b"), # LF → single space ("a\rb", "a b"), # CR → single space ("a\r\nb", "a b"), # CRLF is ONE unit → one space ], ) def test_cell_escaping(raw: str, escaped: str) -> None: assert render_table(["h"], [[raw]]) == f"| h |\n| --- |\n| {escaped} |\n" def test_header_cells_escaped_identically() -> None: assert render_table(["a|b"], []) == "| a\\|b |\n| --- |\n" # --- sql_value_to_text --- def test_sql_null_is_empty_string() -> None: assert sql_value_to_text(None) == "" @pytest.mark.parametrize( ("value", "text"), [(0, "0"), (42, "42"), (-7, "-7"), (10**20, "100000000000000000000")] ) def test_sql_integers_plain_decimal(value: int, text: str) -> None: assert sql_value_to_text(value) == text @pytest.mark.parametrize( ("value", "text"), [(1.5, "1.5"), (0.1, "0.1"), (-2.25, "-2.25"), (1e300, "1e+300")], ) def test_sql_floats_shortest_round_trip(value: float, text: str) -> None: assert sql_value_to_text(value) == text def test_sql_text_verbatim_unescaped() -> None: # Table escaping is render_table's job, not the value conversion's. assert sql_value_to_text("a|b\\c") == "a|b\\c" @pytest.mark.parametrize("value", [True, False, b"blob", [1], {"a": 1}, object()]) def test_sql_unsupported_types_fail(value: object) -> None: """Never silent coercion (spec §5) — bool guarded explicitly (int subclass).""" with pytest.raises(RenderError): sql_value_to_text(value) # --- render_fenced_block (http bodies) --- def test_fenced_block_verbatim() -> None: assert render_fenced_block("hello") == "```\nhello\n```\n" def test_fenced_block_strips_trailing_newlines_only() -> None: assert render_fenced_block("hello\n\n") == "```\nhello\n```\n" assert render_fenced_block("a\n\nb\n") == "```\na\n\nb\n```\n" def test_fenced_block_not_table_escaped() -> None: assert render_fenced_block("a|b\\c") == "```\na|b\\c\n```\n" def test_fenced_block_empty_body() -> None: assert render_fenced_block("") == "```\n\n```\n"