"""Unit tests for formatters module."""
from __future__ import annotations
import pytest
from mcp_odoo.formatters import (
MarkdownBuilder,
format_hours,
format_many2one,
format_money,
format_percentage,
)
class TestMarkdownBuilder:
"""Tests for MarkdownBuilder class."""
def test_init_with_title(self):
"""Test builder initialization with title."""
builder = MarkdownBuilder("Test Title")
result = builder.build()
assert result.startswith("# Test Title")
def test_init_without_title(self):
"""Test builder initialization without title."""
builder = MarkdownBuilder()
result = builder.build()
assert result == ""
def test_add_heading(self):
"""Test adding headings at different levels."""
builder = MarkdownBuilder()
builder.add_heading("Level 2", 2)
builder.add_heading("Level 3", 3)
result = builder.build()
assert "## Level 2" in result
assert "### Level 3" in result
def test_add_heading_bounds(self):
"""Test heading level bounds (1-6)."""
builder = MarkdownBuilder()
builder.add_heading("Min", 0) # Should become 1
builder.add_heading("Max", 7) # Should become 6
result = builder.build()
assert "# Min" in result
assert "###### Max" in result
def test_add_text(self):
"""Test adding plain text."""
builder = MarkdownBuilder()
builder.add_text("Hello World")
assert builder.build() == "Hello World"
def test_add_line(self):
"""Test adding empty line."""
builder = MarkdownBuilder()
builder.add_text("Before")
builder.add_line()
builder.add_text("After")
result = builder.build()
assert "Before\n\nAfter" in result
def test_add_separator(self):
"""Test adding horizontal separator."""
builder = MarkdownBuilder()
builder.add_separator()
assert "---" in builder.build()
def test_add_table(self):
"""Test adding a table."""
builder = MarkdownBuilder()
builder.add_table(
["ID", "Name"],
[[1, "Alice"], [2, "Bob"]],
)
result = builder.build()
assert "| ID | Name |" in result
assert "| --- | --- |" in result
assert "| 1 | Alice |" in result
assert "| 2 | Bob |" in result
def test_add_table_with_alignments(self):
"""Test table with column alignments."""
builder = MarkdownBuilder()
builder.add_table(
["Left", "Center", "Right"],
[[1, 2, 3]],
alignments=["left", "center", "right"],
)
result = builder.build()
assert "| --- |" in result
assert "| :---: |" in result
assert "| ---: |" in result
def test_add_table_empty_headers(self):
"""Test table with empty headers returns early."""
builder = MarkdownBuilder()
builder.add_table([], [[1, 2]])
assert builder.build() == ""
def test_add_table_padded_rows(self):
"""Test table rows are padded if shorter than headers."""
builder = MarkdownBuilder()
builder.add_table(
["A", "B", "C"],
[[1]], # Only one value
)
result = builder.build()
# Row should have 3 cells - each row has 4 pipes (start + 3 separators)
# 3 rows (header, separator, data) * 4 pipes = 12
assert result.count("|") == 12
assert "| 1 |" in result # First value present
def test_add_list_unordered(self):
"""Test adding unordered list."""
builder = MarkdownBuilder()
builder.add_list(["Item 1", "Item 2"])
result = builder.build()
assert "- Item 1" in result
assert "- Item 2" in result
def test_add_list_ordered(self):
"""Test adding ordered list."""
builder = MarkdownBuilder()
builder.add_list(["First", "Second"], ordered=True)
result = builder.build()
assert "1. First" in result
assert "2. Second" in result
def test_add_key_value(self):
"""Test adding key-value pairs."""
builder = MarkdownBuilder()
builder.add_key_value({"Name": "Alice", "Age": 30})
result = builder.build()
assert "**Name**: Alice" in result
assert "**Age**: 30" in result
def test_add_key_value_skips_none(self):
"""Test key-value skips None values."""
builder = MarkdownBuilder()
builder.add_key_value({"Name": "Alice", "Email": None})
result = builder.build()
assert "Name" in result
assert "Email" not in result
def test_add_key_value_no_bullet(self):
"""Test key-value without bullet points."""
builder = MarkdownBuilder()
builder.add_key_value({"Name": "Alice"}, bullet=False)
result = builder.build()
assert "**Name**: Alice" in result
assert "- " not in result
def test_add_code_block(self):
"""Test adding code block."""
builder = MarkdownBuilder()
builder.add_code_block("print('hello')", language="python")
result = builder.build()
assert "```python" in result
assert "print('hello')" in result
assert "```" in result
def test_add_pagination_with_total(self):
"""Test pagination info with total count."""
builder = MarkdownBuilder()
builder.add_pagination(count=10, limit=50, offset=0, total=100)
result = builder.build()
assert "10 of 100" in result
def test_add_pagination_without_total(self):
"""Test pagination info without total count."""
builder = MarkdownBuilder()
builder.add_pagination(count=10, limit=50, offset=0)
result = builder.build()
assert "10 result(s)" in result
def test_add_pagination_more_available(self):
"""Test pagination shows offset hint when more results available."""
builder = MarkdownBuilder()
builder.add_pagination(count=50, limit=50, offset=0)
result = builder.build()
assert "offset=50" in result
def test_add_record_item(self):
"""Test adding a record item."""
builder = MarkdownBuilder()
builder.add_record_item(1, "Test Record", {"Field": "Value"})
result = builder.build()
assert "**ID 1**: Test Record" in result
assert "Field: Value" in result
def test_chaining(self):
"""Test method chaining works."""
result = (
MarkdownBuilder("Title")
.add_heading("Section", 2)
.add_text("Content")
.add_line()
.build()
)
assert "# Title" in result
assert "## Section" in result
assert "Content" in result
class TestFormatMany2one:
"""Tests for format_many2one function."""
def test_tuple_value(self):
"""Test extracting name from tuple."""
assert format_many2one((1, "Partner Name")) == "Partner Name"
def test_list_value(self):
"""Test extracting name from list."""
assert format_many2one([1, "Partner Name"]) == "Partner Name"
def test_false_value(self):
"""Test False value returns None."""
assert format_many2one(False) is None
def test_none_value(self):
"""Test None value returns None."""
assert format_many2one(None) is None
def test_short_tuple(self):
"""Test tuple with less than 2 elements."""
assert format_many2one((1,)) is None
class TestFormatMoney:
"""Tests for format_money function."""
def test_default_currency(self):
"""Test formatting with default EUR currency."""
assert format_money(100) == "100.00 EUR"
def test_custom_currency(self):
"""Test formatting with custom currency."""
assert format_money(100, "USD") == "100.00 USD"
def test_thousands_separator(self):
"""Test thousands separator."""
assert format_money(1234567.89) == "1,234,567.89 EUR"
def test_negative_amount(self):
"""Test negative amount."""
assert format_money(-50.5) == "-50.50 EUR"
class TestFormatHours:
"""Tests for format_hours function."""
def test_whole_hours(self):
"""Test formatting whole hours."""
assert format_hours(8) == "8.0h"
def test_fractional_hours(self):
"""Test formatting fractional hours."""
assert format_hours(8.5) == "8.5h"
def test_zero_hours(self):
"""Test formatting zero hours."""
assert format_hours(0) == "0.0h"
class TestFormatPercentage:
"""Tests for format_percentage function."""
def test_default_decimals(self):
"""Test default one decimal place."""
assert format_percentage(75.5) == "75.5%"
def test_custom_decimals(self):
"""Test custom decimal places."""
assert format_percentage(75.555, decimals=2) == "75.56%"
def test_zero_decimals(self):
"""Test zero decimal places."""
assert format_percentage(75.9, decimals=0) == "76%"