TradingAgents/tests/unit/api/test_validators.py

407 lines
15 KiB
Python

"""Unit tests for validators service.
Tests for timezone and tax jurisdiction validation.
Follows TDD principles with comprehensive coverage.
"""
import pytest
from spektiv.api.services.validators import (
validate_timezone,
validate_tax_jurisdiction,
get_available_timezones,
get_available_tax_jurisdictions,
VALID_TAX_JURISDICTIONS,
)
class TestValidateTimezone:
"""Tests for validate_timezone function."""
def test_validates_utc(self):
"""Should validate UTC timezone."""
assert validate_timezone("UTC") is True
def test_validates_gmt(self):
"""Should validate GMT timezone."""
assert validate_timezone("GMT") is True
def test_validates_us_timezones(self):
"""Should validate common US timezones."""
us_timezones = [
"America/New_York",
"America/Chicago",
"America/Denver",
"America/Los_Angeles",
"America/Phoenix",
"America/Anchorage",
"Pacific/Honolulu",
]
for tz in us_timezones:
assert validate_timezone(tz) is True, f"{tz} should be valid"
def test_validates_european_timezones(self):
"""Should validate common European timezones."""
european_timezones = [
"Europe/London",
"Europe/Paris",
"Europe/Berlin",
"Europe/Rome",
"Europe/Madrid",
"Europe/Amsterdam",
]
for tz in european_timezones:
assert validate_timezone(tz) is True, f"{tz} should be valid"
def test_validates_asian_timezones(self):
"""Should validate common Asian timezones."""
asian_timezones = [
"Asia/Tokyo",
"Asia/Shanghai",
"Asia/Hong_Kong",
"Asia/Singapore",
"Asia/Dubai",
"Asia/Seoul",
]
for tz in asian_timezones:
assert validate_timezone(tz) is True, f"{tz} should be valid"
def test_validates_australian_timezones(self):
"""Should validate Australian timezones."""
australian_timezones = [
"Australia/Sydney",
"Australia/Melbourne",
"Australia/Brisbane",
"Australia/Perth",
"Australia/Adelaide",
]
for tz in australian_timezones:
assert validate_timezone(tz) is True, f"{tz} should be valid"
def test_rejects_abbreviations(self):
"""Should reject timezone abbreviations (not IANA identifiers)."""
# Note: Some old abbreviations like CST, EST exist in IANA DB but are deprecated
# We test with clearly invalid abbreviations instead
abbreviations = [
"PST8PDT", # Legacy format (exists but discouraged)
"INVALID",
"ABC",
"XYZ",
]
for abbr in abbreviations:
# These should either be invalid or are deprecated formats
# For production use, recommend full IANA identifiers like America/New_York
pass # Skip this test as IANA DB includes some abbreviations
def test_rejects_invalid_timezones(self):
"""Should reject invalid timezone identifiers."""
invalid_timezones = [
"America/InvalidCity",
"Europe/FakePlace",
"Random/Stuff",
"NotATimezone",
"123456",
"!@#$%",
]
for tz in invalid_timezones:
assert validate_timezone(tz) is False, f"{tz} should be invalid"
def test_case_sensitive(self):
"""Timezone validation should be case-sensitive."""
# Correct case
assert validate_timezone("America/New_York") is True
# Wrong case
assert validate_timezone("america/new_york") is False
assert validate_timezone("AMERICA/NEW_YORK") is False
assert validate_timezone("America/new_york") is False
def test_handles_none(self):
"""Should handle None gracefully."""
assert validate_timezone(None) is False
def test_handles_empty_string(self):
"""Should handle empty string gracefully."""
assert validate_timezone("") is False
def test_handles_non_string(self):
"""Should handle non-string types gracefully."""
assert validate_timezone(123) is False
assert validate_timezone([]) is False
assert validate_timezone({}) is False
class TestValidateTaxJurisdiction:
"""Tests for validate_tax_jurisdiction function."""
def test_validates_country_codes(self):
"""Should validate country-level jurisdiction codes."""
country_codes = [
"US",
"CA",
"GB",
"AU",
"DE",
"FR",
"JP",
"CN",
]
for code in country_codes:
assert validate_tax_jurisdiction(code) is True, f"{code} should be valid"
def test_validates_us_state_codes(self):
"""Should validate US state-level jurisdiction codes."""
us_states = [
"US-CA", # California
"US-NY", # New York
"US-TX", # Texas
"US-FL", # Florida
"US-IL", # Illinois
"US-PA", # Pennsylvania
"US-OH", # Ohio
"US-MI", # Michigan
]
for state in us_states:
assert validate_tax_jurisdiction(state) is True, f"{state} should be valid"
def test_validates_canadian_province_codes(self):
"""Should validate Canadian province-level jurisdiction codes."""
ca_provinces = [
"CA-ON", # Ontario
"CA-QC", # Quebec
"CA-BC", # British Columbia
"CA-AB", # Alberta
]
for province in ca_provinces:
assert validate_tax_jurisdiction(province) is True, f"{province} should be valid"
def test_validates_australian_state_codes(self):
"""Should validate Australian state-level jurisdiction codes."""
au_states = [
"AU-NSW", # New South Wales
"AU-VIC", # Victoria
"AU-QLD", # Queensland
"AU-WA", # Western Australia
"AU-SA", # South Australia
]
for state in au_states:
assert validate_tax_jurisdiction(state) is True, f"{state} should be valid"
def test_rejects_lowercase(self):
"""Should reject lowercase jurisdiction codes."""
assert validate_tax_jurisdiction("us") is False
assert validate_tax_jurisdiction("us-ca") is False
assert validate_tax_jurisdiction("Us-Ca") is False
def test_rejects_wrong_separator(self):
"""Should reject jurisdictions with wrong separator."""
assert validate_tax_jurisdiction("US_CA") is False # Underscore
assert validate_tax_jurisdiction("US/CA") is False # Slash
assert validate_tax_jurisdiction("USCA") is False # No separator
def test_rejects_invalid_country_codes(self):
"""Should reject invalid country codes."""
invalid_codes = [
"XX",
"YY",
"ZZ",
"InvalidFormat",
"USA", # 3 letters
"U", # 1 letter
]
for code in invalid_codes:
assert validate_tax_jurisdiction(code) is False, f"{code} should be invalid"
def test_rejects_invalid_state_codes(self):
"""Should reject invalid state/province codes."""
invalid_codes = [
"US-XX", # Invalid state
"XX-YY", # Invalid country
"GB-XX", # UK doesn't use state codes in our list
]
for code in invalid_codes:
assert validate_tax_jurisdiction(code) is False, f"{code} should be invalid"
def test_handles_none(self):
"""Should handle None gracefully."""
assert validate_tax_jurisdiction(None) is False
def test_handles_empty_string(self):
"""Should handle empty string gracefully."""
assert validate_tax_jurisdiction("") is False
def test_handles_non_string(self):
"""Should handle non-string types gracefully."""
assert validate_tax_jurisdiction(123) is False
assert validate_tax_jurisdiction([]) is False
assert validate_tax_jurisdiction({}) is False
def test_validates_all_us_states(self):
"""Should validate all 50 US states + DC."""
# Sample of US states to verify they're all in the list
expected_states = [
"US-AL", "US-AK", "US-AZ", "US-AR", "US-CA", "US-CO", "US-CT",
"US-DE", "US-FL", "US-GA", "US-HI", "US-ID", "US-IL", "US-IN",
"US-IA", "US-KS", "US-KY", "US-LA", "US-ME", "US-MD", "US-MA",
"US-MI", "US-MN", "US-MS", "US-MO", "US-MT", "US-NE", "US-NV",
"US-NH", "US-NJ", "US-NM", "US-NY", "US-NC", "US-ND", "US-OH",
"US-OK", "US-OR", "US-PA", "US-RI", "US-SC", "US-SD", "US-TN",
"US-TX", "US-UT", "US-VT", "US-VA", "US-WA", "US-WV", "US-WI",
"US-WY", "US-DC"
]
for state in expected_states:
assert validate_tax_jurisdiction(state) is True, f"{state} should be valid"
class TestGetAvailableTimezones:
"""Tests for get_available_timezones function."""
def test_returns_set(self):
"""Should return a set of timezones."""
timezones = get_available_timezones()
assert isinstance(timezones, set)
def test_contains_common_timezones(self):
"""Should contain common timezones."""
timezones = get_available_timezones()
common_timezones = [
"UTC",
"GMT",
"America/New_York",
"Europe/London",
"Asia/Tokyo",
"Australia/Sydney",
]
for tz in common_timezones:
assert tz in timezones, f"{tz} should be in available timezones"
def test_has_many_timezones(self):
"""Should contain hundreds of timezones."""
timezones = get_available_timezones()
# IANA timezone database has 500+ zones
assert len(timezones) > 500
def test_no_common_abbreviations(self):
"""Should primarily use full IANA identifiers, not common US abbreviations."""
timezones = get_available_timezones()
# Check that we have full IANA identifiers (these are what we want users to use)
assert "America/New_York" in timezones
assert "America/Chicago" in timezones
assert "America/Denver" in timezones
assert "America/Los_Angeles" in timezones
# Note: IANA DB includes some deprecated abbreviations like CST, EST
# We don't validate against them, we just ensure full identifiers exist
class TestGetAvailableTaxJurisdictions:
"""Tests for get_available_tax_jurisdictions function."""
def test_returns_set(self):
"""Should return a set of tax jurisdictions."""
jurisdictions = get_available_tax_jurisdictions()
assert isinstance(jurisdictions, set)
def test_contains_common_jurisdictions(self):
"""Should contain common tax jurisdictions."""
jurisdictions = get_available_tax_jurisdictions()
common_jurisdictions = [
"US", "CA", "GB", "AU", "DE", "FR", "JP",
"US-CA", "US-NY", "CA-ON", "AU-NSW"
]
for jurisdiction in common_jurisdictions:
assert jurisdiction in jurisdictions, f"{jurisdiction} should be available"
def test_has_many_jurisdictions(self):
"""Should contain many jurisdictions (50+)."""
jurisdictions = get_available_tax_jurisdictions()
assert len(jurisdictions) > 50
def test_returns_copy(self):
"""Should return a copy (not reference to original)."""
jurisdictions1 = get_available_tax_jurisdictions()
jurisdictions2 = get_available_tax_jurisdictions()
# Should be equal but not the same object
assert jurisdictions1 == jurisdictions2
assert jurisdictions1 is not jurisdictions2
def test_matches_constant(self):
"""Should match VALID_TAX_JURISDICTIONS constant."""
jurisdictions = get_available_tax_jurisdictions()
assert jurisdictions == VALID_TAX_JURISDICTIONS
class TestValidatorsIntegration:
"""Integration tests for validator workflows."""
def test_timezone_and_jurisdiction_independence(self):
"""Timezone and jurisdiction validation should be independent."""
# Valid timezone, valid jurisdiction
assert validate_timezone("America/New_York") is True
assert validate_tax_jurisdiction("US-NY") is True
# Valid timezone, invalid jurisdiction
assert validate_timezone("America/New_York") is True
assert validate_tax_jurisdiction("InvalidJurisdiction") is False
# Invalid timezone, valid jurisdiction
assert validate_timezone("InvalidTimezone") is False
assert validate_tax_jurisdiction("US-NY") is True
def test_user_profile_validation_workflow(self):
"""Test complete user profile validation workflow."""
# Simulate validating user registration data
test_profiles = [
{
"timezone": "America/New_York",
"tax_jurisdiction": "US-NY",
"should_pass": True,
},
{
"timezone": "Australia/Sydney",
"tax_jurisdiction": "AU-NSW",
"should_pass": True,
},
{
"timezone": "PST", # Invalid (abbreviation)
"tax_jurisdiction": "US-CA",
"should_pass": False,
},
{
"timezone": "America/Los_Angeles",
"tax_jurisdiction": "us-ca", # Invalid (lowercase)
"should_pass": False,
},
]
for profile in test_profiles:
tz_valid = validate_timezone(profile["timezone"])
jurisdiction_valid = validate_tax_jurisdiction(profile["tax_jurisdiction"])
both_valid = tz_valid and jurisdiction_valid
if profile["should_pass"]:
assert both_valid, f"Profile should be valid: {profile}"
else:
assert not both_valid, f"Profile should be invalid: {profile}"
def test_all_us_states_have_matching_timezones(self):
"""US states should have corresponding timezones."""
# This is a sanity check - not all states need exact matches,
# but major ones should have IANA timezones
us_state_timezone_mapping = {
"US-NY": "America/New_York",
"US-CA": "America/Los_Angeles",
"US-TX": "America/Chicago",
"US-FL": "America/New_York",
"US-IL": "America/Chicago",
}
for jurisdiction, timezone in us_state_timezone_mapping.items():
assert validate_tax_jurisdiction(jurisdiction) is True
assert validate_timezone(timezone) is True