diff --git a/.travis.yml b/.travis.yml index 34e5a3e0e6..aef60fa3c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ install: - pip install pylint script: - python pylint-recursive.py - - python -m unittest discover -s pokemongo_bot -p "*_test.py" + - python -m unittest discover -v -p "*_test.py" diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d23ba72805..47345c8ebc 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -42,4 +42,5 @@ * SpaceWhale * klingan * reddivision + * DayBr3ak * kbinani diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index 507ae19583..1b210ac40c 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -144,3 +144,10 @@ def print_yellow(message): def print_red(message): print(u'\033[91m' + message.decode('utf-8') + '\033[0m') + +def float_equal(f1, f2, epsilon=1e-8): + if f1 > f2: + return f1 - f2 < epsilon + if f2 > f1: + return f2 - f1 < epsilon + return True diff --git a/requirements.txt b/requirements.txt index 6963189fa6..788b054ba4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,5 @@ socketIO_client==0.7.0 eventlet==0.19.0 universal-analytics-python==0.2.4 gpxpy==1.1.1 +mock==2.0.0 +timeout-decorator==0.3.2 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..2db23ffd9d --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,26 @@ +# __init__.py +from mock import MagicMock + +from pgoapi import PGoApi +from pokemongo_bot.api_wrapper import ApiWrapper +from pokemongo_bot import PokemonGoBot + +class FakeApi(ApiWrapper): + def __init__(self, return_value=None): + super(FakeApi, self).__init__(PGoApi()) + self._api.call = MagicMock(return_value=return_value) + + def _can_call(self): + return True + + def setApiReturnValue(self, value): + self._api.call.return_value = value + + +class FakeBot(PokemonGoBot): + def __init__(self): + self.config = MagicMock() + self.api = FakeApi() + + def updateConfig(self, conf): + self.config.__dict__.update(conf) diff --git a/tests/api_wrapper_test.py b/tests/api_wrapper_test.py new file mode 100644 index 0000000000..0a835e0e20 --- /dev/null +++ b/tests/api_wrapper_test.py @@ -0,0 +1,103 @@ +import unittest +from mock import MagicMock, patch +from timeout_decorator import timeout, TimeoutError + +from tests import FakeApi + +from pgoapi import PGoApi +from pgoapi.exceptions import NotLoggedInException, ServerBusyOrOfflineException +from pokemongo_bot.api_wrapper import ApiWrapper + +class TestApiWrapper(unittest.TestCase): + def test_raises_not_logged_in_exception(self): + api = ApiWrapper(PGoApi()) + api.get_inventory(test='awesome') + with self.assertRaises(NotLoggedInException): + api.call() + + def test_api_call_with_no_requests_set(self): + api = ApiWrapper(PGoApi()) + with self.assertRaises(RuntimeError): + api.call() + + @patch('pokemongo_bot.api_wrapper.sleep') + def test_api_server_is_unreachable_raises_server_busy_or_offline_exception(self, sleep): + sleep.return_value = True # we don't need to really sleep + api = FakeApi('Wrong Value') + api.get_inventory(test='awesome') + # we expect an exception because the "server" isn't returning a valid response + with self.assertRaises(ServerBusyOrOfflineException): + api.call() + + def test_mocked_call(self): + api = FakeApi(True) + api._is_response_valid = MagicMock(return_value=True) + api.get_inventory(test='awesome') + result = api.call() + self.assertTrue(result) + + def test_return_value_is_not_valid(self): + + def returnApi(ret_value): + api = FakeApi(ret_value) + api.get_inventory(test='awesome') + return api + + wrong_return_values = [ + None, + False, + {}, + {'responses': {}}, + {'status_code': 0}, + {'responses': {'GET_INVENTORY_OR_NOT': {}}, 'status_code': 0} + ] + for wrong in wrong_return_values: + api = returnApi(wrong) + request_callers = api._pop_request_callers() # we can pop because we do no call + + is_valid = api._is_response_valid(wrong, request_callers) + self.assertFalse(is_valid, 'return value {} is valid somehow ?'.format(wrong)) + + def test_return_value_is_valid(self): + api = FakeApi() # we set the return value below + api.get_inventory(test='awesome') + + request = api.request_callers[0] # only one request + self.assertEqual(request.upper(), 'GET_INVENTORY') + + good_return_value = {'responses': {request.upper(): {}}, 'status_code': 0} + api.setApiReturnValue(good_return_value) + + result = api.call() + self.assertEqual(result, good_return_value) + self.assertEqual(len(api.request_callers), 0, 'request_callers must be empty') + + def test_multiple_requests(self): + api = FakeApi() + api.get_inventory(test='awesome') + api.fort_details() + + good_return_value = {'responses': {'GET_INVENTORY': {}, 'FORT_DETAILS': {}}, 'status_code': 0} + api.setApiReturnValue(good_return_value) + + result = api.call() + self.assertEqual(result, good_return_value) + + @timeout(1) + def test_api_call_throttle_should_pass(self): + api = FakeApi(True) + api._is_response_valid = MagicMock(return_value=True) + api.requests_per_seconds = 5 + + for i in range(api.requests_per_seconds): + api.call() + + @timeout(1) # expects a timeout + def test_api_call_throttle_should_fail(self): + api = FakeApi(True) + api._is_response_valid = MagicMock(return_value=True) + api.requests_per_seconds = 5 + + with self.assertRaises(TimeoutError): + for i in range(api.requests_per_seconds * 2): + api.call() diff --git a/pokemongo_bot/test/base_task_test.py b/tests/base_task_test.py similarity index 95% rename from pokemongo_bot/test/base_task_test.py rename to tests/base_task_test.py index ed87c44e86..16684d900c 100644 --- a/pokemongo_bot/test/base_task_test.py +++ b/tests/base_task_test.py @@ -38,6 +38,3 @@ def test_throws_without_work(self): self.bot, self.config ) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/location_parser_test.py b/tests/location_parser_test.py new file mode 100644 index 0000000000..e724adebf8 --- /dev/null +++ b/tests/location_parser_test.py @@ -0,0 +1,33 @@ +# coding: utf-8 +import unittest +from mock import MagicMock + +from geopy.exc import GeocoderQueryError +from tests import FakeBot + + +class TestLocationParser(unittest.TestCase): + + def setUp(self): + self.bot = FakeBot() + config = dict( + test=False, + location='Paris', + location_cache=False, + username='Foobar', + ) + self.bot.updateConfig(config) + + def test_named_position(self): + position = (42, 42, 0) + self.bot.get_pos_by_name = MagicMock(return_value=position) + self.bot._set_starting_position() + self.assertEqual(self.bot.position, position) + + def test_named_position_utf8(self): + position = (42, 42, 0) + self.bot.config.location = u"àéùƱǣЊ؍ ข᠃" + self.bot.get_pos_by_name = MagicMock(return_value=position) + + self.bot._set_starting_position() + self.assertEqual(self.bot.position, position) diff --git a/tests/step_walker_test.py b/tests/step_walker_test.py new file mode 100644 index 0000000000..7472953ac6 --- /dev/null +++ b/tests/step_walker_test.py @@ -0,0 +1,73 @@ +import unittest +from mock import MagicMock, patch + +from pokemongo_bot.step_walker import StepWalker +from pokemongo_bot.cell_workers.utils import float_equal + +NORMALIZED_LAT_LNG_DISTANCE_STEP = 6.3593e-6 + +class TestStepWalker(unittest.TestCase): + def setUp(self): + self.patcherSleep = patch('pokemongo_bot.step_walker.sleep') + self.patcherRandomLat = patch('pokemongo_bot.step_walker.random_lat_long_delta', return_value=0) + self.patcherSleep.start() + self.patcherRandomLat.start() + + self.bot = MagicMock() + self.bot.position = [0, 0, 0] + self.bot.api = MagicMock() + + self.lat, self.lng, self.alt = 0, 0, 0 + + # let us get back the position set by the StepWalker + def api_set_position(lat, lng, alt): + self.lat, self.lng, self.alt = lat, lng, alt + self.bot.api.set_position = api_set_position + + def tearDown(self): + self.patcherSleep.stop() + self.patcherRandomLat.stop() + + def test_normalized_distance(self): + sw = StepWalker(self.bot, 1, 0.1, 0.1) + self.assertGreater(sw.dLat, 0) + self.assertGreater(sw.dLng, 0) + + stayInPlace = sw.step() + self.assertFalse(stayInPlace) + + self.assertTrue(float_equal(self.lat, NORMALIZED_LAT_LNG_DISTANCE_STEP)) + self.assertTrue(float_equal(self.lng, NORMALIZED_LAT_LNG_DISTANCE_STEP)) + + def test_normalized_distance_times_2(self): + sw = StepWalker(self.bot, 2, 0.1, 0.1) + self.assertTrue(sw.dLat > 0) + self.assertTrue(sw.dLng > 0) + + stayInPlace = sw.step() + self.assertFalse(stayInPlace) + + self.assertTrue(float_equal(self.lat, NORMALIZED_LAT_LNG_DISTANCE_STEP * 2)) + self.assertTrue(float_equal(self.lng, NORMALIZED_LAT_LNG_DISTANCE_STEP * 2)) + + def test_small_distance_same_spot(self): + sw = StepWalker(self.bot, 1, 0, 0) + self.assertEqual(sw.dLat, 0, 'dLat should be 0') + self.assertEqual(sw.dLng, 0, 'dLng should be 0') + + self.assertTrue(sw.step(), 'step should return True') + self.assertTrue(self.lat == self.bot.position[0]) + self.assertTrue(self.lng == self.bot.position[1]) + + def test_small_distance_small_step(self): + sw = StepWalker(self.bot, 1, 1e-5, 1e-5) + self.assertEqual(sw.dLat, 0) + self.assertEqual(sw.dLng, 0) + + @unittest.skip('This behavior is To Be Defined') + def test_big_distances(self): + # FIXME currently the StepWalker acts like it won't move if big distances gives as input + # see args below + # with self.assertRaises(RuntimeError): + sw = StepWalker(self.bot, 1, 10, 10) + sw.step() # equals True i.e act like the distance is too short for a step diff --git a/pokemongo_bot/test/tree_config_builder_test.py b/tests/tree_config_builder_test.py similarity index 97% rename from pokemongo_bot/test/tree_config_builder_test.py rename to tests/tree_config_builder_test.py index a4670e220a..2c7e082331 100644 --- a/pokemongo_bot/test/tree_config_builder_test.py +++ b/tests/tree_config_builder_test.py @@ -71,6 +71,3 @@ def test_task_with_config(self): builder = TreeConfigBuilder(self.bot, obj) tree = builder.build() self.assertTrue(tree[0].config.get('longer_eggs_first', False)) - -if __name__ == '__main__': - unittest.main()