From 06270b281ad164b70825ff955cb623e6bdf8086b Mon Sep 17 00:00:00 2001 From: Thiago Cardoso Date: Thu, 18 Jul 2024 14:49:02 -0300 Subject: [PATCH] feat: add missing fields to register_feedback --- incognia/api.py | 14 ++++++++++- incognia/datetime_util.py | 6 ++++- tests/test_api.py | 52 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/incognia/api.py b/incognia/api.py index 1d56ff7..25687c7 100644 --- a/incognia/api.py +++ b/incognia/api.py @@ -1,7 +1,7 @@ import datetime as dt from typing import Optional, List -from .datetime_util import total_milliseconds_since_epoch +from .datetime_util import total_milliseconds_since_epoch, has_timezone from .endpoints import Endpoints from .exceptions import IncogniaHTTPError, IncogniaError from .json_util import encode @@ -51,6 +51,8 @@ def register_new_signup(self, def register_feedback(self, event: str, timestamp: dt.datetime = None, + occurred_at: dt.datetime = None, + expires_at: dt.datetime = None, external_id: Optional[str] = None, login_id: Optional[str] = None, payment_id: Optional[str] = None, @@ -60,6 +62,12 @@ def register_feedback(self, session_token: Optional[str] = None) -> None: if not event: raise IncogniaError('event is required.') + if timestamp is not None and not has_timezone(timestamp): + raise IncogniaError('timestamp must have timezone') + if occurred_at is not None and not has_timezone(occurred_at): + raise IncogniaError('occurred_at must have timezone') + if expires_at is not None and not has_timezone(expires_at): + raise IncogniaError('expires_at must have timezone') try: headers = self.__get_authorization_header() @@ -76,6 +84,10 @@ def register_feedback(self, } if timestamp is not None: body['timestamp'] = total_milliseconds_since_epoch(timestamp) + if occurred_at is not None: + body['occurred_at'] = occurred_at.isoformat() + if expires_at is not None: + body['expires_at'] = expires_at.isoformat() data = encode(body) return self.__request.post(Endpoints.FEEDBACKS, headers=headers, data=data) diff --git a/incognia/datetime_util.py b/incognia/datetime_util.py index bb27a20..f2f7d33 100644 --- a/incognia/datetime_util.py +++ b/incognia/datetime_util.py @@ -2,4 +2,8 @@ def total_milliseconds_since_epoch(t: dt.datetime) -> int: - return int((t - dt.datetime.utcfromtimestamp(0)).total_seconds() * 1000.0) + return int((t - dt.datetime.fromtimestamp(0, dt.timezone.utc)).total_seconds() * 1000.0) + + +def has_timezone(d: dt.datetime) -> bool: + return d.tzinfo is not None and d.tzinfo.utcoffset(d) is not None diff --git a/tests/test_api.py b/tests/test_api.py index 18ccc30..4bc07a0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -71,7 +71,8 @@ class TestIncogniaAPI(TestCase): CLIENT_ERROR_CODE: Final[int] = 400 VALID_EVENT_FEEDBACK_TYPE: Final[str] = 'valid_event_feedback_type' INVALID_EVENT_FEEDBACK_TYPE: Final[str] = 'invalid_event_feedback_type' - TIMESTAMP: Final[dt.datetime] = dt.datetime.now() + TIMESTAMP: Final[dt.datetime] = dt.datetime.now(dt.timezone.utc) + TIMESTAMP_WITHOUT_TIMEZONE: Final[dt.datetime] = dt.datetime.now() LOGIN_ID: Final[str] = 'ANY_LOGIN_ID' PAYMENT_ID: Final[str] = 'ANY_PAYMENT_ID' REGISTER_VALID_FEEDBACK_DATA: Final[bytes] = encode({ @@ -86,7 +87,10 @@ class TestIncogniaAPI(TestCase): 'account_id': f'{ACCOUNT_ID}', 'installation_id': f'{INSTALLATION_ID}', 'session_token': f'{SESSION_TOKEN}', - 'timestamp': int((TIMESTAMP - dt.datetime.utcfromtimestamp(0)).total_seconds() * 1000.0) + 'timestamp': int(( + TIMESTAMP - dt.datetime.fromtimestamp(0, dt.timezone.utc)).total_seconds() * 1000.0), + 'occurred_at': TIMESTAMP.isoformat(), + 'expires_at': TIMESTAMP.isoformat(), }) REGISTER_INVALID_FEEDBACK_DATA: Final[bytes] = encode({ 'event': f'{INVALID_EVENT_FEEDBACK_TYPE}' @@ -211,6 +215,8 @@ def test_register_feedback_when_all_fields_are_valid_should_work( api.register_feedback(self.VALID_EVENT_FEEDBACK_TYPE, timestamp=self.TIMESTAMP, + occurred_at=self.TIMESTAMP, + expires_at=self.TIMESTAMP, external_id=self.EXTERNAL_ID, login_id=self.LOGIN_ID, payment_id=self.PAYMENT_ID, @@ -235,6 +241,48 @@ def test_register_feedback_when_event_is_empty_should_raise_an_IncogniaError( mock_token_manager_get.assert_not_called() mock_base_request_post.assert_not_called() + @patch.object(BaseRequest, 'post') + @patch.object(TokenManager, 'get', return_value=TOKEN_VALUES) + def test_register_feedback_when_timestamp_does_not_have_timezone_should_raise_IncogniaError( + self, mock_token_manager_get: Mock, mock_base_request_post: Mock): + api = IncogniaAPI(self.CLIENT_ID, self.CLIENT_SECRET) + + self.assertRaises(IncogniaError, + api.register_feedback, + event=self.VALID_EVENT_FEEDBACK_TYPE, + timestamp=self.TIMESTAMP_WITHOUT_TIMEZONE) + + mock_token_manager_get.assert_not_called() + mock_base_request_post.assert_not_called() + + @patch.object(BaseRequest, 'post') + @patch.object(TokenManager, 'get', return_value=TOKEN_VALUES) + def test_register_feedback_when_occurred_at_does_not_have_timezone_should_raise_IncogniaError( + self, mock_token_manager_get: Mock, mock_base_request_post: Mock): + api = IncogniaAPI(self.CLIENT_ID, self.CLIENT_SECRET) + + self.assertRaises(IncogniaError, + api.register_feedback, + event=self.VALID_EVENT_FEEDBACK_TYPE, + occurred_at=self.TIMESTAMP_WITHOUT_TIMEZONE) + + mock_token_manager_get.assert_not_called() + mock_base_request_post.assert_not_called() + + @patch.object(BaseRequest, 'post') + @patch.object(TokenManager, 'get', return_value=TOKEN_VALUES) + def test_register_feedback_when_expires_at_does_not_have_timezone_should_raise_IncogniaError( + self, mock_token_manager_get: Mock, mock_base_request_post: Mock): + api = IncogniaAPI(self.CLIENT_ID, self.CLIENT_SECRET) + + self.assertRaises(IncogniaError, + api.register_feedback, + event=self.VALID_EVENT_FEEDBACK_TYPE, + expires_at=self.TIMESTAMP_WITHOUT_TIMEZONE) + + mock_token_manager_get.assert_not_called() + mock_base_request_post.assert_not_called() + @patch.object(BaseRequest, 'post') @patch.object(TokenManager, 'get', return_value=TOKEN_VALUES) def test_register_feedback_when_required_fields_are_invalid_should_raise_an_IncogniaHTTPError(