From bb7fc4b153bd69dceb3fd1268ed751537fd5cbc6 Mon Sep 17 00:00:00 2001 From: James Woglom Date: Mon, 26 Apr 2021 23:07:15 -0400 Subject: [PATCH] api/android: add tests for last_event_uploaded and retry behavior --- tconnectsync/api/android.py | 28 +++++++++++- tests/api/fake.py | 2 +- tests/api/test_android.py | 90 +++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/tconnectsync/api/android.py b/tconnectsync/api/android.py index d469d36..a178b5b 100644 --- a/tconnectsync/api/android.py +++ b/tconnectsync/api/android.py @@ -5,6 +5,7 @@ import datetime import csv import base64 import arrow +import time from bs4 import BeautifulSoup @@ -34,6 +35,8 @@ class AndroidApi: def __init__(self, email, password): self.login(email, password) + self._email = email + self._password = password def login(self, email, password): r = requests.post( @@ -73,12 +76,33 @@ class AndroidApi: raise Exception('No access token') return {'Authorization': 'Bearer %s' % self.accessToken} - def get(self, endpoint, query={}, **kwargs): + def _get(self, endpoint, query={}, **kwargs): r = requests.get(self.BASE_URL + endpoint, query, headers=self.api_headers(), **kwargs) + if r.status_code != 200: - raise ApiException(r.status_code, "Internal API HTTP %s response: %s" % (str(r.status_code), r.text)) + raise ApiException(r.status_code, "Android API HTTP %s response: %s" % (str(r.status_code), r.text)) return r.json() + def get(self, endpoint, query={}, tries=0, **kwargs): + try: + return self._get(endpoint, query, **kwargs) + except ApiException as e: + if tries > 0: + raise ApiException(e.status_code, "Android API HTTP %s on retry #%d: %s" % (e.status_code, tries, e)) + + # Trigger automatic re-login, and try again once + if e.status_code == 401: + self.accessTokenExpiresAt = time.time() + self.login(self._email, self._password) + + return self.get(endpoint, query, tries=tries+1, **kwargs) + + if e.status_code == 500: + return self.get(endpoint, query, tries=tries+1, **kwargs) + + raise e + + def post(self, endpoint, query={}, **kwargs): r = requests.post(self.BASE_URL + endpoint, query, headers=self.api_headers(), **kwargs) if r.status_code != 200: diff --git a/tests/api/fake.py b/tests/api/fake.py index b54a919..50fd527 100644 --- a/tests/api/fake.py +++ b/tests/api/fake.py @@ -34,7 +34,7 @@ class AndroidApi(tconnectsync.api.android.AndroidApi): def needs_relogin(self): return False - def get(self, endpoint, query={}, **kwargs): + def _get(self, endpoint, query={}, **kwargs): raise NotImplementedError class TConnectApi(tconnectsync.api.TConnectApi): diff --git a/tests/api/test_android.py b/tests/api/test_android.py index e69de29..174c998 100644 --- a/tests/api/test_android.py +++ b/tests/api/test_android.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +import unittest +import itertools +import datetime + +from .fake import AndroidApi + +from tconnectsync.api.common import ApiException + +class TestAndroidApi(unittest.TestCase): + def fake_get_with_http_code(self, http_code, expected_endpoint, num_times): + tries = 0 + def fake_get(endpoint, query): + nonlocal http_code, expected_endpoint, num_times, tries + if endpoint.endswith(expected_endpoint): + if tries < num_times: + tries += 1 + raise ApiException(http_code, "fake HTTP %d" % http_code) + + return {"faked_json": True} + + raise NotImplementedError + + return fake_get + + def test_last_event_uploaded_works_after_single_http_500(self): + android = AndroidApi() + + android._get = self.fake_get_with_http_code(500, "cloud/upload/getlasteventuploaded?sn=1111111", 1) + + self.assertEqual( + android.last_event_uploaded(1111111), + { + "faked_json": True + }) + + def test_last_event_uploaded_fails_after_two_http_500s(self): + android = AndroidApi() + + android._get = self.fake_get_with_http_code(500, "cloud/upload/getlasteventuploaded?sn=1111111", 2) + + self.assertRaises(ApiException, android.last_event_uploaded, 1111111) + + def test_last_event_uploaded_triggers_relogin_after_single_http_401(self): + android = AndroidApi() + android._email = 'email' + android._password = 'password' + + hit_login = [] + def stub_login(email, password): + nonlocal hit_login + hit_login.append((email, password)) + + android.login = stub_login + + android._get = self.fake_get_with_http_code(401, "cloud/upload/getlasteventuploaded?sn=1111111", 1) + + self.assertEqual( + android.last_event_uploaded(1111111), + { + "faked_json": True + }) + + self.assertListEqual(hit_login, [ + ('email', 'password') + ]) + + def test_last_event_uploaded_fails_after_two_http_401s(self): + android = AndroidApi() + android._email = 'email' + android._password = 'password' + + hit_login = [] + def stub_login(email, password): + nonlocal hit_login + hit_login.append((email, password)) + + android.login = stub_login + + android._get = self.fake_get_with_http_code(401, "cloud/upload/getlasteventuploaded?sn=1111111", 2) + + self.assertRaises(ApiException, android.last_event_uploaded, 1111111) + + self.assertListEqual(hit_login, [ + ('email', 'password') + ]) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file