import hashlib import json import uuid from datetime import UTC, datetime, timedelta from unittest import mock import pytest from anydi import Container from cachelib import BaseCache from fansifter_common.encrypter import Encrypter from fansifter_common.identifiers.types import DoubleOptInIdentifier from preference_center.config import Settings from preference_center.double_opt_in.exceptions import ( DoubleOptInTokenCheckError, DoubleOptInTokenExpiredError, ) from preference_center.double_opt_in.handlers import ( DoubleOptInConfirmHandler, DoubleOptInConfirmRequest, ) from tests.unit.types import CreateDoubleOptInToken class TestDoubleOptInConfirmHandler: def test_wrong_encrypted_token( self, container: Container, handler: DoubleOptInConfirmHandler, producer_mock: mock.MagicMock, ) -> None: cache_mock = mock.MagicMock(spec=BaseCache) with ( pytest.raises(DoubleOptInTokenCheckError), container.override(BaseCache, cache_mock), ): handler.handle( DoubleOptInConfirmRequest( token="invalid", ) ) cache_mock.get.assert_not_called() producer_mock.publish.assert_not_called() def test_confirm_key_not_in_cache( self, container: Container, handler: DoubleOptInConfirmHandler, create_double_opt_in_token: CreateDoubleOptInToken, producer_mock: mock.MagicMock, settings: Settings, ) -> None: identifier = DoubleOptInIdentifier( eventRowId=str(uuid.uuid4()), emailId=str(uuid.uuid4()), automatedEmailTriggerId=str(uuid.uuid4()), iat=datetime.now(UTC).timestamp(), ) encrypted_token = create_double_opt_in_token(identifier) cache_mock = mock.MagicMock(spec=BaseCache) cache_mock.get.return_value = None with container.override(BaseCache, cache_mock): handler.handle( DoubleOptInConfirmRequest( token=encrypted_token, ) ) key_hash = hashlib.sha256(identifier.model_dump_json().encode()).hexdigest() cache_mock.get.assert_called_with(key=f"{handler.cache_prefix}:{key_hash}") cache_mock.add.assert_called_with( key=f"{handler.cache_prefix}:{key_hash}", value=key_hash, timeout=handler.cache_timeout, ) producer_mock.publish.assert_called_with( topic=settings.kafka_topic_double_opt_in, value=mock.ANY, key=key_hash, ) def test_invalid_token_payload( self, container: Container, handler: DoubleOptInConfirmHandler, dummy_encrypter: Encrypter, producer_mock: mock.MagicMock, ) -> None: payload = json.dumps({"unexpected_field": "value"}) encrypted_token = dummy_encrypter.encrypt(payload.encode()).decode() cache_mock = mock.MagicMock(spec=BaseCache) cache_mock.get.return_value = None with ( pytest.raises(DoubleOptInTokenCheckError), container.override(BaseCache, cache_mock), ): handler.handle( DoubleOptInConfirmRequest( token=encrypted_token, ) ) producer_mock.publish.assert_not_called() def test_confirm_key_exists_in_cache( self, container: Container, handler: DoubleOptInConfirmHandler, create_double_opt_in_token: CreateDoubleOptInToken, producer_mock: mock.MagicMock, ) -> None: identifier = DoubleOptInIdentifier( eventRowId=str(uuid.uuid4()), emailId=str(uuid.uuid4()), automatedEmailTriggerId=str(uuid.uuid4()), iat=datetime.now(UTC).timestamp(), ) encrypted_token = create_double_opt_in_token(identifier) cache_mock = mock.MagicMock(spec=BaseCache) cache_mock.get.return_value = "cached" with container.override(BaseCache, cache_mock): handler.handle( DoubleOptInConfirmRequest( token=encrypted_token, ) ) key_hash = hashlib.sha256(identifier.model_dump_json().encode()).hexdigest() cache_mock.get.assert_called_with(key=f"{handler.cache_prefix}:{key_hash}") cache_mock.add.assert_not_called() producer_mock.publish.assert_not_called() def test_expired_token( self, container: Container, handler: DoubleOptInConfirmHandler, create_double_opt_in_token: CreateDoubleOptInToken, producer_mock: mock.MagicMock, ) -> None: expired_iat = (datetime.now(UTC) - timedelta(days=31)).timestamp() identifier = DoubleOptInIdentifier( eventRowId=str(uuid.uuid4()), emailId=str(uuid.uuid4()), automatedEmailTriggerId=str(uuid.uuid4()), iat=expired_iat, ) encrypted_token = create_double_opt_in_token(identifier) cache_mock = mock.MagicMock(spec=BaseCache) cache_mock.get.return_value = None with ( pytest.raises(DoubleOptInTokenExpiredError), container.override(BaseCache, cache_mock), ): handler.handle( DoubleOptInConfirmRequest( token=encrypted_token, ) ) cache_mock.add.assert_not_called() producer_mock.publish.assert_not_called()