"""Test M2MToken.""" import datetime import freezegun import pytest from zoneinfo import ZoneInfo from owsclient.m2m.base import M2MToken @pytest.fixture() def expiration_string() -> str: """Return a expected string representation of expiration.""" return "2024-07-22 16:55:30.13455+0000" @pytest.fixture() def expiration_datetime() -> datetime.datetime: """Return corresponding datetime object expiration_string.""" return datetime.datetime( 2024, 7, 22, 16, 55, 30, 134550, datetime.timezone.utc, ) @pytest.mark.parametrize( "expiration_string, expiration_datetime", [ pytest.param( "2024-07-22 16:55:30.13455+0000", datetime.datetime( 2024, 7, 22, 16, 55, 30, 134550, datetime.timezone.utc, ), id="Has TZ and timestamp with microseconds", ), pytest.param( "2024-07-22 16:55:30.13455-0500", datetime.datetime( 2024, 7, 22, 16, 55, 30, 134550, ZoneInfo("EST"), ), id="Has TZ with non-zero offset and timestamp with microseconds", ), pytest.param( "2024-07-22 16:55:30+0000", datetime.datetime( 2024, 7, 22, 16, 55, 30, 0, datetime.timezone.utc, ), id="Haz TZ and timestamp without microseconds", ), pytest.param( "2024-07-22 16:55+0000", datetime.datetime( 2024, 7, 22, 16, 55, 0, 0, datetime.timezone.utc, ), id="Haz TZ and timestamp without milliseconds, microseconds", ), pytest.param( "2024-07-22T16:55+0000", datetime.datetime( 2024, 7, 22, 16, 55, 0, 0, datetime.timezone.utc, ), id="Haz TZ and optional isoformat separator 'T'", ), ], ) def test_m2m_token_create( expiration_string: str, expiration_datetime: datetime.datetime, ) -> None: """Test M2MToken schema.""" token_string = "fake token" token = M2MToken.model_validate( {"token": token_string, "expires_at": expiration_string} ) assert token.token == token_string assert token.expires_at == expiration_datetime @pytest.mark.parametrize( "invalid_expiration", [ pytest.param("bs bs bs bs", id="not a date."), pytest.param("2024-07-22 16:55:30.13455-2400", id="invalid tz offset"), pytest.param("2024-07-22 16:55:30.13455", id="missing tz"), pytest.param("2024-07-22 16:55:30.13455+00000", id="invalid tz"), pytest.param( "2024-07-22 16:55:30.+0000", id="missing microseconds after decimal" ), pytest.param("2024-07-22 16:55:.0+0000", id="missing seconds after colon"), pytest.param("2024-07-22 16::30.0+0000", id="missing minutes between colons"), pytest.param("2024-07-22 :55:30.0+0000", id="missing hours before 1st colon"), ], ) def test_m2m_token_invalid_expiration( invalid_expiration: str, ) -> None: """Test M2MToken with invalid timestamps.""" token_string = "fake token" with pytest.raises(ValueError): M2MToken.model_validate( { "token": token_string, "expires_at": invalid_expiration, } ) @pytest.mark.parametrize( "invalid_token", [ pytest.param("", id="Empty string not allowed."), pytest.param(" ", id="Token with all spaces is not allowed"), ], ) def test_m2m_token_invalid_token( invalid_token: str, expiration_string: str, ) -> None: """Test M2MToken schema with invalid tokens.""" with pytest.raises(ValueError): M2MToken.model_validate( { "token_string": invalid_token, "expires_at": expiration_string, } ) def test_is_expired( expiration_string: str, ) -> None: """Test token is expired happy path.""" token = M2MToken.model_validate( {"token": "happy path", "expires_at": expiration_string} ) assert token.is_expired() def test_is_not_expired() -> None: """Test token is not expired.""" expires_at = datetime.datetime.now(datetime.UTC) + datetime.timedelta(minutes=3) expiration_string = expires_at.strftime("%Y-%m-%d %H:%M:%S.%f%z") token = M2MToken.model_validate({"token": "TOKEN", "expires_at": expiration_string}) assert not token.is_expired() @freezegun.freeze_time("2022-01-02 00:00:00") def test_is_expired_leeway_seconds() -> None: """Test token is_expired with leeway_seconds.""" # expired_at is 2022-01-02 00:00:00 expires_at = datetime.datetime.now(datetime.UTC) expiration_string = expires_at.strftime("%Y-%m-%d %H:%M:%S.%f%z") token = M2MToken.model_validate({"token": "TOKEN", "expires_at": expiration_string}) with freezegun.freeze_time("2022-01-02 00:00:00"): # the token is expired now() with 0 leeway assert token.is_expired(leeway_seconds=0) with freezegun.freeze_time("2022-01-01 23:59:59"): # the token will expire in 1 minute. # a leeway of 60 seconds should return is_expired == True assert token.is_expired(leeway_seconds=60) with freezegun.freeze_time("2022-01-01 23:59:59"): # the token will expire in 1 minute. # a leeway of negative 60 seconds should return is_expired == False assert not token.is_expired(leeway_seconds=-60) with freezegun.freeze_time("2022-01-01 23:55:00"): # The token will expire in 5 minutes # a leeway of 60 seconds should return is_expired == False assert not token.is_expired(leeway_seconds=60)