"""Unit tests for validators module."""
from __future__ import annotations
import pytest
from mcp_odoo.exceptions import OdooValidationError
from mcp_odoo.validators import (
sanitize_string,
today,
validate_date,
validate_date_range,
validate_hours,
validate_id,
validate_ids,
validate_non_empty_string,
validate_positive_amount,
)
class TestValidateDate:
"""Tests for validate_date function."""
def test_valid_date(self):
"""Test valid date format."""
assert validate_date("2024-01-15") == "2024-01-15"
assert validate_date("2024-12-31") == "2024-12-31"
def test_invalid_format(self):
"""Test invalid date format raises error."""
with pytest.raises(OdooValidationError) as exc_info:
validate_date("15-01-2024")
assert "Invalid date format" in str(exc_info.value)
def test_invalid_date_value(self):
"""Test invalid date value raises error."""
with pytest.raises(OdooValidationError):
validate_date("2024-02-30") # Invalid day
def test_custom_field_name(self):
"""Test custom field name in error message."""
with pytest.raises(OdooValidationError) as exc_info:
validate_date("invalid", "start_date")
assert "start_date" in str(exc_info.value)
class TestValidateDateRange:
"""Tests for validate_date_range function."""
def test_valid_range(self):
"""Test valid date range."""
start, end = validate_date_range("2024-01-01", "2024-01-31")
assert start == "2024-01-01"
assert end == "2024-01-31"
def test_none_values(self):
"""Test None values are passed through."""
start, end = validate_date_range(None, None)
assert start is None
assert end is None
def test_inverted_range(self):
"""Test inverted range raises error."""
with pytest.raises(OdooValidationError) as exc_info:
validate_date_range("2024-12-31", "2024-01-01")
assert "before" in str(exc_info.value)
class TestValidatePositiveAmount:
"""Tests for validate_positive_amount function."""
def test_positive_value(self):
"""Test positive value is accepted."""
assert validate_positive_amount(10.5) == 10.5
assert validate_positive_amount(100) == 100.0
def test_zero_not_allowed(self):
"""Test zero raises error by default."""
with pytest.raises(OdooValidationError):
validate_positive_amount(0)
def test_zero_allowed(self):
"""Test zero is accepted when allow_zero=True."""
assert validate_positive_amount(0, allow_zero=True) == 0.0
def test_negative_value(self):
"""Test negative value raises error."""
with pytest.raises(OdooValidationError):
validate_positive_amount(-10)
class TestValidateHours:
"""Tests for validate_hours function."""
def test_valid_hours(self):
"""Test valid hours values."""
assert validate_hours(8.0) == 8.0
assert validate_hours(0.5) == 0.5
assert validate_hours(24) == 24.0
def test_exceeds_24_hours(self):
"""Test hours exceeding 24 raises error."""
with pytest.raises(OdooValidationError) as exc_info:
validate_hours(25)
assert "24" in str(exc_info.value)
def test_zero_hours(self):
"""Test zero hours raises error."""
with pytest.raises(OdooValidationError):
validate_hours(0)
class TestValidateNonEmptyString:
"""Tests for validate_non_empty_string function."""
def test_valid_string(self):
"""Test valid string is returned stripped."""
assert validate_non_empty_string(" hello ", "name") == "hello"
def test_empty_string(self):
"""Test empty string raises error."""
with pytest.raises(OdooValidationError):
validate_non_empty_string("", "name")
def test_whitespace_only(self):
"""Test whitespace-only string raises error."""
with pytest.raises(OdooValidationError):
validate_non_empty_string(" ", "name")
def test_max_length_exceeded(self):
"""Test max length validation."""
with pytest.raises(OdooValidationError):
validate_non_empty_string("hello world", "name", max_length=5)
class TestSanitizeString:
"""Tests for sanitize_string function."""
def test_normal_string(self):
"""Test normal string is unchanged."""
assert sanitize_string("Hello World") == "Hello World"
def test_strips_whitespace(self):
"""Test leading/trailing whitespace is stripped."""
assert sanitize_string(" hello ") == "hello"
def test_removes_null_bytes(self):
"""Test null bytes are removed."""
assert sanitize_string("hello\x00world") == "helloworld"
def test_preserves_newlines(self):
"""Test newlines are preserved."""
assert sanitize_string("hello\nworld") == "hello\nworld"
class TestValidateId:
"""Tests for validate_id function."""
def test_valid_id(self):
"""Test valid ID is returned."""
assert validate_id(1) == 1
assert validate_id(100) == 100
assert validate_id("50") == 50
def test_zero_id(self):
"""Test zero ID raises error."""
with pytest.raises(OdooValidationError):
validate_id(0)
def test_negative_id(self):
"""Test negative ID raises error."""
with pytest.raises(OdooValidationError):
validate_id(-1)
def test_non_numeric(self):
"""Test non-numeric value raises error."""
with pytest.raises(OdooValidationError):
validate_id("abc")
class TestValidateIds:
"""Tests for validate_ids function."""
def test_valid_ids(self):
"""Test valid ID list is returned."""
assert validate_ids([1, 2, 3]) == [1, 2, 3]
assert validate_ids(["1", "2"]) == [1, 2]
def test_empty_list(self):
"""Test empty list is valid."""
assert validate_ids([]) == []
def test_invalid_id_in_list(self):
"""Test invalid ID in list raises error."""
with pytest.raises(OdooValidationError):
validate_ids([1, 0, 3])
class TestToday:
"""Tests for today function."""
def test_returns_date_string(self):
"""Test today returns date in correct format."""
result = today()
assert len(result) == 10
assert result.count("-") == 2
# Validate format
validate_date(result)