Skip to content

Commit 06270b2

Browse files
committed
feat: add missing fields to register_feedback
1 parent 7552141 commit 06270b2

File tree

3 files changed

+68
-4
lines changed

3 files changed

+68
-4
lines changed

incognia/api.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import datetime as dt
22
from typing import Optional, List
33

4-
from .datetime_util import total_milliseconds_since_epoch
4+
from .datetime_util import total_milliseconds_since_epoch, has_timezone
55
from .endpoints import Endpoints
66
from .exceptions import IncogniaHTTPError, IncogniaError
77
from .json_util import encode
@@ -51,6 +51,8 @@ def register_new_signup(self,
5151
def register_feedback(self,
5252
event: str,
5353
timestamp: dt.datetime = None,
54+
occurred_at: dt.datetime = None,
55+
expires_at: dt.datetime = None,
5456
external_id: Optional[str] = None,
5557
login_id: Optional[str] = None,
5658
payment_id: Optional[str] = None,
@@ -60,6 +62,12 @@ def register_feedback(self,
6062
session_token: Optional[str] = None) -> None:
6163
if not event:
6264
raise IncogniaError('event is required.')
65+
if timestamp is not None and not has_timezone(timestamp):
66+
raise IncogniaError('timestamp must have timezone')
67+
if occurred_at is not None and not has_timezone(occurred_at):
68+
raise IncogniaError('occurred_at must have timezone')
69+
if expires_at is not None and not has_timezone(expires_at):
70+
raise IncogniaError('expires_at must have timezone')
6371

6472
try:
6573
headers = self.__get_authorization_header()
@@ -76,6 +84,10 @@ def register_feedback(self,
7684
}
7785
if timestamp is not None:
7886
body['timestamp'] = total_milliseconds_since_epoch(timestamp)
87+
if occurred_at is not None:
88+
body['occurred_at'] = occurred_at.isoformat()
89+
if expires_at is not None:
90+
body['expires_at'] = expires_at.isoformat()
7991
data = encode(body)
8092
return self.__request.post(Endpoints.FEEDBACKS, headers=headers, data=data)
8193

incognia/datetime_util.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22

33

44
def total_milliseconds_since_epoch(t: dt.datetime) -> int:
5-
return int((t - dt.datetime.utcfromtimestamp(0)).total_seconds() * 1000.0)
5+
return int((t - dt.datetime.fromtimestamp(0, dt.timezone.utc)).total_seconds() * 1000.0)
6+
7+
8+
def has_timezone(d: dt.datetime) -> bool:
9+
return d.tzinfo is not None and d.tzinfo.utcoffset(d) is not None

tests/test_api.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ class TestIncogniaAPI(TestCase):
7171
CLIENT_ERROR_CODE: Final[int] = 400
7272
VALID_EVENT_FEEDBACK_TYPE: Final[str] = 'valid_event_feedback_type'
7373
INVALID_EVENT_FEEDBACK_TYPE: Final[str] = 'invalid_event_feedback_type'
74-
TIMESTAMP: Final[dt.datetime] = dt.datetime.now()
74+
TIMESTAMP: Final[dt.datetime] = dt.datetime.now(dt.timezone.utc)
75+
TIMESTAMP_WITHOUT_TIMEZONE: Final[dt.datetime] = dt.datetime.now()
7576
LOGIN_ID: Final[str] = 'ANY_LOGIN_ID'
7677
PAYMENT_ID: Final[str] = 'ANY_PAYMENT_ID'
7778
REGISTER_VALID_FEEDBACK_DATA: Final[bytes] = encode({
@@ -86,7 +87,10 @@ class TestIncogniaAPI(TestCase):
8687
'account_id': f'{ACCOUNT_ID}',
8788
'installation_id': f'{INSTALLATION_ID}',
8889
'session_token': f'{SESSION_TOKEN}',
89-
'timestamp': int((TIMESTAMP - dt.datetime.utcfromtimestamp(0)).total_seconds() * 1000.0)
90+
'timestamp': int((
91+
TIMESTAMP - dt.datetime.fromtimestamp(0, dt.timezone.utc)).total_seconds() * 1000.0),
92+
'occurred_at': TIMESTAMP.isoformat(),
93+
'expires_at': TIMESTAMP.isoformat(),
9094
})
9195
REGISTER_INVALID_FEEDBACK_DATA: Final[bytes] = encode({
9296
'event': f'{INVALID_EVENT_FEEDBACK_TYPE}'
@@ -211,6 +215,8 @@ def test_register_feedback_when_all_fields_are_valid_should_work(
211215

212216
api.register_feedback(self.VALID_EVENT_FEEDBACK_TYPE,
213217
timestamp=self.TIMESTAMP,
218+
occurred_at=self.TIMESTAMP,
219+
expires_at=self.TIMESTAMP,
214220
external_id=self.EXTERNAL_ID,
215221
login_id=self.LOGIN_ID,
216222
payment_id=self.PAYMENT_ID,
@@ -235,6 +241,48 @@ def test_register_feedback_when_event_is_empty_should_raise_an_IncogniaError(
235241
mock_token_manager_get.assert_not_called()
236242
mock_base_request_post.assert_not_called()
237243

244+
@patch.object(BaseRequest, 'post')
245+
@patch.object(TokenManager, 'get', return_value=TOKEN_VALUES)
246+
def test_register_feedback_when_timestamp_does_not_have_timezone_should_raise_IncogniaError(
247+
self, mock_token_manager_get: Mock, mock_base_request_post: Mock):
248+
api = IncogniaAPI(self.CLIENT_ID, self.CLIENT_SECRET)
249+
250+
self.assertRaises(IncogniaError,
251+
api.register_feedback,
252+
event=self.VALID_EVENT_FEEDBACK_TYPE,
253+
timestamp=self.TIMESTAMP_WITHOUT_TIMEZONE)
254+
255+
mock_token_manager_get.assert_not_called()
256+
mock_base_request_post.assert_not_called()
257+
258+
@patch.object(BaseRequest, 'post')
259+
@patch.object(TokenManager, 'get', return_value=TOKEN_VALUES)
260+
def test_register_feedback_when_occurred_at_does_not_have_timezone_should_raise_IncogniaError(
261+
self, mock_token_manager_get: Mock, mock_base_request_post: Mock):
262+
api = IncogniaAPI(self.CLIENT_ID, self.CLIENT_SECRET)
263+
264+
self.assertRaises(IncogniaError,
265+
api.register_feedback,
266+
event=self.VALID_EVENT_FEEDBACK_TYPE,
267+
occurred_at=self.TIMESTAMP_WITHOUT_TIMEZONE)
268+
269+
mock_token_manager_get.assert_not_called()
270+
mock_base_request_post.assert_not_called()
271+
272+
@patch.object(BaseRequest, 'post')
273+
@patch.object(TokenManager, 'get', return_value=TOKEN_VALUES)
274+
def test_register_feedback_when_expires_at_does_not_have_timezone_should_raise_IncogniaError(
275+
self, mock_token_manager_get: Mock, mock_base_request_post: Mock):
276+
api = IncogniaAPI(self.CLIENT_ID, self.CLIENT_SECRET)
277+
278+
self.assertRaises(IncogniaError,
279+
api.register_feedback,
280+
event=self.VALID_EVENT_FEEDBACK_TYPE,
281+
expires_at=self.TIMESTAMP_WITHOUT_TIMEZONE)
282+
283+
mock_token_manager_get.assert_not_called()
284+
mock_base_request_post.assert_not_called()
285+
238286
@patch.object(BaseRequest, 'post')
239287
@patch.object(TokenManager, 'get', return_value=TOKEN_VALUES)
240288
def test_register_feedback_when_required_fields_are_invalid_should_raise_an_IncogniaHTTPError(

0 commit comments

Comments
 (0)