TCPDashboard/tests/test_data_validation.py
Ajasra 96ee25bd01 Refactor data validation module for improved modularity and functionality
- Removed the existing `validation.py` file and replaced it with a modular structure, introducing separate files for validation results, field validators, and the base validator class.
- Implemented comprehensive validation functions for common data types, enhancing reusability and maintainability.
- Added a new `__init__.py` to expose the validation utilities, ensuring a clean public interface.
- Created detailed documentation for the validation module, including usage examples and architectural details.
- Introduced extensive unit tests to cover the new validation framework, ensuring reliability and preventing regressions.

These changes enhance the overall architecture of the data validation module, making it more scalable and easier to manage.
2025-06-07 12:31:47 +08:00

188 lines
6.7 KiB
Python

"""
Tests for data validation module.
This module provides comprehensive test coverage for the data validation utilities
and base validator class.
"""
import pytest
from datetime import datetime, timezone
from decimal import Decimal
from typing import Dict, Any
from data.common.validation import (
ValidationResult,
BaseDataValidator,
is_valid_decimal,
validate_required_fields
)
from data.common.data_types import DataValidationResult, StandardizedTrade, TradeSide
class TestValidationResult:
"""Test ValidationResult class."""
def test_init_with_defaults(self):
"""Test initialization with default values."""
result = ValidationResult(is_valid=True)
assert result.is_valid
assert result.errors == []
assert result.warnings == []
assert result.sanitized_data is None
def test_init_with_errors(self):
"""Test initialization with errors."""
errors = ["Error 1", "Error 2"]
result = ValidationResult(is_valid=False, errors=errors)
assert not result.is_valid
assert result.errors == errors
assert result.warnings == []
def test_init_with_warnings(self):
"""Test initialization with warnings."""
warnings = ["Warning 1"]
result = ValidationResult(is_valid=True, warnings=warnings)
assert result.is_valid
assert result.warnings == warnings
assert result.errors == []
def test_init_with_sanitized_data(self):
"""Test initialization with sanitized data."""
data = {"key": "value"}
result = ValidationResult(is_valid=True, sanitized_data=data)
assert result.sanitized_data == data
class MockDataValidator(BaseDataValidator):
"""Mock implementation of BaseDataValidator for testing."""
def validate_symbol_format(self, symbol: str) -> ValidationResult:
"""Mock implementation of validate_symbol_format."""
if not symbol or not isinstance(symbol, str):
return ValidationResult(False, errors=["Invalid symbol format"])
return ValidationResult(True)
def validate_websocket_message(self, message: Dict[str, Any]) -> DataValidationResult:
"""Mock implementation of validate_websocket_message."""
if not isinstance(message, dict):
return DataValidationResult(False, ["Invalid message format"], [])
return DataValidationResult(True, [], [])
class TestBaseDataValidator:
"""Test BaseDataValidator class."""
@pytest.fixture
def validator(self):
"""Create a mock validator instance."""
return MockDataValidator("test_exchange")
def test_validate_price(self, validator):
"""Test price validation."""
# Test valid price
result = validator.validate_price("123.45")
assert result.is_valid
assert result.sanitized_data == Decimal("123.45")
# Test invalid price
result = validator.validate_price("invalid")
assert not result.is_valid
assert "Invalid price value" in result.errors[0]
# Test price bounds
result = validator.validate_price("0.000000001") # Below min
assert result.is_valid # Still valid but with warning
assert "below minimum" in result.warnings[0]
def test_validate_size(self, validator):
"""Test size validation."""
# Test valid size
result = validator.validate_size("10.5")
assert result.is_valid
assert result.sanitized_data == Decimal("10.5")
# Test invalid size
result = validator.validate_size("-1")
assert not result.is_valid
assert "must be positive" in result.errors[0]
def test_validate_timestamp(self, validator):
"""Test timestamp validation."""
current_time = int(datetime.now(timezone.utc).timestamp() * 1000)
# Test valid timestamp
result = validator.validate_timestamp(current_time)
assert result.is_valid
# Test invalid timestamp
result = validator.validate_timestamp("invalid")
assert not result.is_valid
assert "Invalid timestamp format" in result.errors[0]
# Test old timestamp
old_timestamp = 999999999999 # Before min_timestamp
result = validator.validate_timestamp(old_timestamp)
assert not result.is_valid
assert "too old" in result.errors[0]
def test_validate_trade_side(self, validator):
"""Test trade side validation."""
# Test valid sides
assert validator.validate_trade_side("buy").is_valid
assert validator.validate_trade_side("sell").is_valid
# Test invalid sides
result = validator.validate_trade_side("invalid")
assert not result.is_valid
assert "Must be 'buy' or 'sell'" in result.errors[0]
def test_validate_trade_id(self, validator):
"""Test trade ID validation."""
# Test valid trade IDs
assert validator.validate_trade_id("trade123").is_valid
assert validator.validate_trade_id("123").is_valid
assert validator.validate_trade_id("trade-123_abc").is_valid
# Test invalid trade IDs
result = validator.validate_trade_id("")
assert not result.is_valid
assert "cannot be empty" in result.errors[0]
def test_validate_symbol_match(self, validator):
"""Test symbol matching validation."""
# Test basic symbol validation
assert validator.validate_symbol_match("BTC-USD").is_valid
# Test symbol mismatch
result = validator.validate_symbol_match("BTC-USD", "ETH-USD")
assert result.is_valid # Still valid but with warning
assert "mismatch" in result.warnings[0]
# Test invalid symbol type
result = validator.validate_symbol_match(123)
assert not result.is_valid
assert "must be string" in result.errors[0]
def test_is_valid_decimal():
"""Test is_valid_decimal utility function."""
# Test valid decimals
assert is_valid_decimal("123.45")
assert is_valid_decimal(123.45)
assert is_valid_decimal(Decimal("123.45"))
# Test invalid decimals
assert not is_valid_decimal("invalid")
assert not is_valid_decimal(None)
assert not is_valid_decimal("")
def test_validate_required_fields():
"""Test validate_required_fields utility function."""
data = {"field1": "value1", "field2": None, "field3": "value3"}
required = ["field1", "field2", "field4"]
missing = validate_required_fields(data, required)
assert "field2" in missing # None value
assert "field4" in missing # Missing field
assert "field1" not in missing # Present field