from datetime import date, datetime, timedelta import pytest from tradingagents.validation import ( DateValidationError, TickerValidationError, format_date, get_next_trading_day, get_previous_trading_day, is_trading_day, is_valid_date, is_valid_ticker, parse_date, validate_date, validate_date_range, validate_ticker, validate_tickers, ) class TestValidateTicker: def test_valid_simple_ticker(self): assert validate_ticker("AAPL") == "AAPL" assert validate_ticker("A") == "A" assert validate_ticker("GOOGL") == "GOOGL" def test_valid_ticker_lowercase_converted(self): assert validate_ticker("aapl") == "AAPL" assert validate_ticker("Msft") == "MSFT" def test_valid_ticker_with_whitespace(self): assert validate_ticker(" AAPL ") == "AAPL" assert validate_ticker("\tTSLA\n") == "TSLA" def test_valid_ticker_with_class_indicator(self): assert validate_ticker("BRK-B") == "BRK-B" assert validate_ticker("BRK-A") == "BRK-A" assert validate_ticker("BRK.B") == "BRK.B" def test_invalid_ticker_none(self): with pytest.raises(TickerValidationError, match="cannot be None"): validate_ticker(None) def test_invalid_ticker_none_allowed(self): assert validate_ticker(None, allow_empty=True) == "" def test_invalid_ticker_empty(self): with pytest.raises(TickerValidationError, match="cannot be empty"): validate_ticker("") def test_invalid_ticker_empty_allowed(self): assert validate_ticker("", allow_empty=True) == "" def test_invalid_ticker_wrong_type(self): with pytest.raises(TickerValidationError, match="must be a string"): validate_ticker(123) def test_invalid_ticker_too_long(self): with pytest.raises(TickerValidationError, match="too long"): validate_ticker("VERYLONGTICKER") def test_invalid_ticker_format(self): with pytest.raises(TickerValidationError, match="Invalid ticker format"): validate_ticker("AAPL123") with pytest.raises(TickerValidationError, match="Invalid ticker format"): validate_ticker("AA-PL-B") with pytest.raises(TickerValidationError, match="Invalid ticker format"): validate_ticker("A@PL") class TestValidateTickers: def test_valid_tickers_list(self): result = validate_tickers(["AAPL", "MSFT", "GOOGL"]) assert result == ["AAPL", "MSFT", "GOOGL"] def test_valid_tickers_lowercase_converted(self): result = validate_tickers(["aapl", "msft"]) assert result == ["AAPL", "MSFT"] def test_valid_tickers_tuple(self): result = validate_tickers(("AAPL", "MSFT")) assert result == ["AAPL", "MSFT"] def test_invalid_tickers_none(self): with pytest.raises(TickerValidationError, match="cannot be None"): validate_tickers(None) def test_invalid_tickers_none_allowed(self): assert validate_tickers(None, allow_empty_list=True) == [] def test_invalid_tickers_empty_list(self): with pytest.raises(TickerValidationError, match="cannot be empty"): validate_tickers([]) def test_invalid_tickers_empty_list_allowed(self): assert validate_tickers([], allow_empty_list=True) == [] def test_invalid_tickers_wrong_type(self): with pytest.raises(TickerValidationError, match="must be a list"): validate_tickers("AAPL") def test_invalid_tickers_contains_invalid(self): with pytest.raises(TickerValidationError, match="Invalid tickers"): validate_tickers(["AAPL", "INVALID123", "MSFT"]) class TestParseDate: def test_parse_date_string_default_format(self): result = parse_date("2024-01-15") assert result == date(2024, 1, 15) def test_parse_date_string_various_formats(self): assert parse_date("2024/01/15") == date(2024, 1, 15) assert parse_date("01/15/2024") == date(2024, 1, 15) assert parse_date("20240115") == date(2024, 1, 15) def test_parse_date_from_date(self): d = date(2024, 1, 15) assert parse_date(d) == d def test_parse_date_from_datetime(self): dt = datetime(2024, 1, 15, 10, 30) assert parse_date(dt) == date(2024, 1, 15) def test_parse_date_none(self): assert parse_date(None) is None def test_parse_date_empty_string(self): assert parse_date("") is None assert parse_date(" ") is None def test_parse_date_invalid_format(self): with pytest.raises(DateValidationError, match="Could not parse date"): parse_date("not-a-date") def test_parse_date_invalid_type(self): with pytest.raises(DateValidationError, match="must be string"): parse_date(123) class TestValidateDate: def test_validate_date_valid(self): result = validate_date("2024-01-15") assert result == date(2024, 1, 15) def test_validate_date_none_not_allowed(self): with pytest.raises(DateValidationError, match="cannot be None"): validate_date(None) def test_validate_date_none_allowed(self): assert validate_date(None, allow_none=True) is None def test_validate_date_before_min(self): with pytest.raises(DateValidationError, match="before minimum"): validate_date("1960-01-01") def test_validate_date_custom_min(self): with pytest.raises(DateValidationError, match="before minimum"): validate_date("2020-01-01", min_date=date(2021, 1, 1)) def test_validate_date_future_not_allowed(self): future = date.today() + timedelta(days=30) with pytest.raises(DateValidationError, match="in the future"): validate_date(future.strftime("%Y-%m-%d"), allow_future=False) def test_validate_date_future_allowed(self): future = date.today() + timedelta(days=30) result = validate_date(future.strftime("%Y-%m-%d"), allow_future=True) assert result == future def test_validate_date_after_max(self): far_future = date.today() + timedelta(days=500) with pytest.raises(DateValidationError, match="after maximum"): validate_date(far_future.strftime("%Y-%m-%d")) def test_validate_date_weekend_not_allowed(self): saturday = date(2024, 1, 6) with pytest.raises(DateValidationError, match="Saturday"): validate_date(saturday, allow_weekend=False) sunday = date(2024, 1, 7) with pytest.raises(DateValidationError, match="Sunday"): validate_date(sunday, allow_weekend=False) def test_validate_date_weekend_allowed(self): saturday = date(2024, 1, 6) result = validate_date(saturday, allow_weekend=True) assert result == saturday class TestValidateDateRange: def test_validate_date_range_valid(self): start, end = validate_date_range("2024-01-01", "2024-01-31") assert start == date(2024, 1, 1) assert end == date(2024, 1, 31) def test_validate_date_range_end_before_start(self): with pytest.raises(DateValidationError, match="must be on or after"): validate_date_range("2024-01-31", "2024-01-01") def test_validate_date_range_same_day(self): with pytest.raises(DateValidationError, match="must be after"): validate_date_range("2024-01-15", "2024-01-15") def test_validate_date_range_max_range(self): with pytest.raises(DateValidationError, match="exceeds maximum"): validate_date_range("2020-01-01", "2024-01-01", max_range_days=365) class TestFormatDate: def test_format_date_default(self): result = format_date(date(2024, 1, 15)) assert result == "2024-01-15" def test_format_date_custom_format(self): result = format_date(date(2024, 1, 15), output_format="%m/%d/%Y") assert result == "01/15/2024" def test_format_date_from_string(self): result = format_date("2024-01-15", output_format="%Y%m%d") assert result == "20240115" def test_format_date_none(self): with pytest.raises(DateValidationError, match="Cannot format None"): format_date(None) class TestHelperFunctions: def test_is_valid_ticker(self): assert is_valid_ticker("AAPL") is True assert is_valid_ticker("INVALID123") is False assert is_valid_ticker("") is False def test_is_valid_date(self): assert is_valid_date("2024-01-15") is True assert is_valid_date("not-a-date") is False assert is_valid_date(date(2024, 1, 15)) is True def test_is_trading_day(self): assert is_trading_day(date(2024, 1, 15)) is True assert is_trading_day(date(2024, 1, 13)) is False assert is_trading_day("2024-01-15") is True def test_get_previous_trading_day_from_monday(self): monday = date(2024, 1, 15) result = get_previous_trading_day(monday) assert result == monday def test_get_previous_trading_day_from_saturday(self): saturday = date(2024, 1, 13) result = get_previous_trading_day(saturday) assert result == date(2024, 1, 12) def test_get_previous_trading_day_from_sunday(self): sunday = date(2024, 1, 14) result = get_previous_trading_day(sunday) assert result == date(2024, 1, 12) def test_get_next_trading_day_from_friday(self): friday = date(2024, 1, 12) result = get_next_trading_day(friday) assert result == friday def test_get_next_trading_day_from_saturday(self): saturday = date(2024, 1, 13) result = get_next_trading_day(saturday) assert result == date(2024, 1, 15) def test_get_next_trading_day_from_sunday(self): sunday = date(2024, 1, 14) result = get_next_trading_day(sunday) assert result == date(2024, 1, 15)