diff --git a/.travis.yml b/.travis.yml index 015488e..6cfd486 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,10 @@ cache: - $HOME/.cache install: - - pip install "setuptools>=17.1" coveralls + - pip install -r requirements.txt script: - - python setup.py nosetests + - pytest --cov=predicthq tests/ after_success: - coveralls diff --git a/predicthq/version.py b/predicthq/version.py index 73e3bb4..80eb7f9 100644 --- a/predicthq/version.py +++ b/predicthq/version.py @@ -1 +1 @@ -__version__ = '0.3.2' +__version__ = '0.3.3' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..76ebd38 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +coveralls>=1.8.2,<2.0 +mock==1.3.0 +pytest>=4.6.0,<5.0 +pytest-cov>=2.8.1,<3.0 +python-dateutil>=2.4.2,<3.0 +pytz==2015.4 +requests>=2.7.0,<3.0 +responses==0.5.1 +schematics==2.0.0.dev2 +six>=1.9.0,<2.0 diff --git a/setup.cfg b/setup.cfg index 96d630f..f403ffb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,5 @@ [wheel] universal = 1 -[nosetests] -verbosity=2 -detailed-errors=1 -with-coverage=1 -cover-package=predicthq -cover-erase=1 -cover-min-percentage=90 +[aliases] +test=pytest diff --git a/setup.py b/setup.py index 22835ea..8d34c21 100644 --- a/setup.py +++ b/setup.py @@ -36,22 +36,15 @@ def read(*parts): author_email="developers@predicthq.com", url=REPO_URL, packages=find_packages(exclude=("tests*",)), - test_suite="nose.collector", setup_requires=[ - "nose==1.3.7" - ], - tests_require=[ - "nose==1.3.7", - "coverage>=4.0.0", - "responses==0.5.1", - "mock==1.3.0", + 'pytest-runner>=5.1,<6.0', ], install_requires=[ "six>=1.9.0", "requests>=2.7.0", "schematics==2.0.0.dev2", "python-dateutil>=2.4.2", - "pytz>=2015.4", + "pytz==2015.4", ], classifiers=[ "Development Status :: 4 - Beta", diff --git a/tests/config_tests.py b/tests/config_tests.py deleted file mode 100644 index 6fab237..0000000 --- a/tests/config_tests.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function - -import os -import unittest - -from predicthq.config import Config -from predicthq.exceptions import ConfigError - - -class ConfigTest(unittest.TestCase): - - def test_defaults(self): - config = Config() - self.assertEqual(config.ENDPOINT_URL, "https://api.predicthq.com") - self.assertEqual(config.OAUTH2_CLIENT_ID, None) - self.assertEqual(config.OAUTH2_CLIENT_SECRET, None) - self.assertEqual(config.OAUTH2_SCOPE, None) - self.assertEqual(config.OAUTH2_ACCESS_TOKEN, None) - self.assertEqual(config.LOGGING_LOG_LEVEL, "WARNING") - - def test_defaults_from_locations(self): - CONFIG_LOCATIONS = ( - os.path.join(os.path.dirname(__file__), 'fixtures', 'test.conf'), - os.path.join(os.path.dirname(__file__), 'fixtures', 'test2.conf') - ) - - config = Config(*CONFIG_LOCATIONS) - self.assertEqual(config.ENDPOINT_URL, "https://example.org") - self.assertEqual(config.OAUTH2_CLIENT_ID, "client_id") - self.assertEqual(config.OAUTH2_CLIENT_SECRET, "client_secret") - self.assertEqual(config.OAUTH2_SCOPE, "account events") - self.assertEqual(config.OAUTH2_ACCESS_TOKEN, "access_token") - self.assertEqual(config.LOGGING_LOG_LEVEL, "DEBUG") - - def test_defaults_from_environment(self): - os.environ["PREDICTHQ_ENDPOINT_URL"] = "https://example.org/endpoint/" - try: - config = Config(os.path.join(os.path.dirname(__file__), 'fixtures', 'test.conf')) - self.assertEqual(config.ENDPOINT_URL, "https://example.org/endpoint/") - self.assertEqual(config.OAUTH2_CLIENT_ID, "client_id") - self.assertEqual(config.OAUTH2_CLIENT_SECRET, "client_secret") - self.assertEqual(config.OAUTH2_SCOPE, "scope") - self.assertEqual(config.OAUTH2_ACCESS_TOKEN, "access_token") - self.assertEqual(config.LOGGING_LOG_LEVEL, "log_level") - finally: - del os.environ["PREDICTHQ_ENDPOINT_URL"] - - def test_get_config(self): - config = Config() - self.assertEqual(config.ENDPOINT_URL, "https://api.predicthq.com") - - with self.assertRaises(ConfigError): - assert config.INVALID_CONFIG - - def test_set_config(self): - config = Config() - config.ENDPOINT_URL = "https://example.org" - self.assertEqual(config.ENDPOINT_URL, "https://example.org") - - with self.assertRaises(ConfigError): - config.INVALID_CONFIG = "invalid" diff --git a/tests/endpoints/__init__.py b/tests/endpoints/__init__.py index d45e719..e69de29 100644 --- a/tests/endpoints/__init__.py +++ b/tests/endpoints/__init__.py @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function diff --git a/tests/endpoints/decorators_tests.py b/tests/endpoints/decorators_tests.py deleted file mode 100644 index f1b3652..0000000 --- a/tests/endpoints/decorators_tests.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function - -import unittest - -from predicthq.endpoints import decorators, schemas -from predicthq.endpoints.base import BaseEndpoint -from predicthq.exceptions import ValidationError - - -class DecoratorsTest(unittest.TestCase): - - def test_to_params(self): - kwargs = {"string_type": "my-string", "list_type": [1, 2, 3], "dict_type": {"key1": "val1", "key2": "val2"}, "bool_type": True} - expected = {'string_type': 'my-string', 'list_type': '1,2,3', 'dict_type.key1': u'val1', 'dict_type.key2': 'val2', 'bool_type': 1} - self.assertDictEqual(decorators._to_url_params(kwargs), expected) - - def test_kwargs_processor(self): - kwargs = {"normal_arg": "value", "nested__arg": "value"} - expected = {'normal_arg': 'value', 'nested': {'arg': 'value'}} - self.assertDictEqual(decorators._process_kwargs(kwargs), expected) - - def test_accepts(self): - - class SchemaExample(schemas.Model): - arg1 = schemas.StringType(required=True) - arg2 = schemas.ListType(schemas.IntType) - - class EndpointExample(BaseEndpoint): - - @decorators.accepts(SchemaExample) - def func(self, **kwargs): - return kwargs - - endpoint = EndpointExample(None) - self.assertDictEqual(endpoint.func(arg1="test", arg2=[1, 2]), {'arg1': 'test', 'arg2': '1,2'}) - - self.assertDictEqual(endpoint.func(SchemaExample({"arg1": "test", "arg2": [1, 2]})), {'arg1': 'test', 'arg2': '1,2'}) - - self.assertDictEqual(endpoint.func({"arg1": "test", "arg2": [1, 2]}), {'arg1': 'test', 'arg2': '1,2'}) - - with self.assertRaises(ValidationError): - endpoint.func(arg2=[1, 2]) - - with self.assertRaises(ValidationError): - endpoint.func(arg1="value", arg2="invalid") - - def test_accepts_for_body_use(self): - - class SchemaExample(schemas.Model): - arg1 = schemas.StringType(required=True) - arg2 = schemas.ListType(schemas.IntType) - - class EndpointExample(BaseEndpoint): - - @decorators.accepts(SchemaExample, query_string=False) - def func(self, **kwargs): - return kwargs - - endpoint = EndpointExample(None) - self.assertDictEqual(endpoint.func({"arg1": "test", "arg2": [1, 2]}), {'arg1': 'test', 'arg2': [1, 2]}) - - def test_returns(self): - - class SchemaExample(schemas.Model): - arg1 = schemas.StringType(required=True) - arg2 = schemas.ListType(schemas.IntType) - - class EndpointExample(BaseEndpoint): - - @decorators.returns(SchemaExample) - def func(self, **kwargs): - return kwargs - - endpoint = EndpointExample(None) - self.assertEqual(endpoint.func(arg1="test", arg2=[1, 2]), SchemaExample({'arg1': 'test', 'arg2': [1, 2]})) - - with self.assertRaises(ValidationError): - endpoint.func(arg2=[1, 2]) - - with self.assertRaises(ValidationError): - endpoint.func(arg1="value", arg2="invalid") - - def test_returns_resultset_of_native_types(self): - - class SchemaExample(schemas.ResultSet): - - results = schemas.ListType(schemas.StringType) - - class EndpointExample(BaseEndpoint): - - @decorators.returns(SchemaExample) - def func(self, **kwargs): - return kwargs - - endpoint = EndpointExample(None) - self.assertEqual(endpoint.func(results=["item1", "item2"]), SchemaExample({"results": ["item1", "item2"]})) - self.assertEqual(endpoint.func()._more(results=["item3", "item4"]), SchemaExample({'results': ['item3', 'item4']})) - self.assertEqual(endpoint, endpoint.func()._endpoint) - - def test_returns_resultset_of_models(self): - - class ModelExample(schemas.ResultSet): - - name = schemas.StringType() - - class SchemaExample(schemas.ResultSet): - - results = schemas.ResultType(ModelExample) - - class EndpointExample(BaseEndpoint): - - @decorators.returns(SchemaExample) - def func(self, **kwargs): - return kwargs - - endpoint = EndpointExample(None) - results = endpoint.func(results=[{"name": "item1"}, {"name": "item2"}]) - self.assertEqual(results, SchemaExample({"results": [{"name": "item1"}, {"name": "item2"}]})) - self.assertEqual(endpoint.func()._more(results=[{"name": "item2"}, {"name": "item4"}]), SchemaExample({'results': [{"name": "item2"}, {"name": "item4"}]})) - - for item in results: - self.assertEqual(item._endpoint, endpoint) diff --git a/tests/endpoints/schemas_tests.py b/tests/endpoints/schemas_tests.py deleted file mode 100644 index 74b761d..0000000 --- a/tests/endpoints/schemas_tests.py +++ /dev/null @@ -1,244 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function - -import unittest - -import pytz -from datetime import datetime, date - -from predicthq.endpoints import decorators, schemas -from predicthq.endpoints.base import BaseEndpoint - - -class SchemasTest(unittest.TestCase): - - def test_datetime_type(self): - - class SchemaExample(schemas.Model): - - my_datetime = schemas.DateTimeType() - - test_date = datetime(2016, 1, 1, tzinfo=pytz.UTC) - self.assertEqual(SchemaExample({"my_datetime": "2016-01-01T00:00:00+00:00"}).my_datetime, test_date) - self.assertEqual(SchemaExample({"my_datetime": "2016-01-01T00:00:00+0000"}).my_datetime, test_date) - self.assertEqual(SchemaExample({"my_datetime": "2016-01-01T00:00:00Z"}).my_datetime, test_date) - self.assertEqual(SchemaExample({"my_datetime": test_date}).my_datetime, test_date) - - def test_date_type(self): - - class SchemaExample(schemas.Model): - - my_date = schemas.DateType() - - test_date = date(2016, 1, 1) - self.assertEqual(SchemaExample({"my_date": "2016-01-01"}).my_date, test_date) - self.assertEqual(SchemaExample({"my_date": "2016-01-01T00:00:00+0000"}).my_date, test_date) - self.assertEqual(SchemaExample({"my_date": "2016-01-01T00:00:00Z"}).my_date, test_date) - self.assertEqual(SchemaExample({"my_date": test_date}).my_date, test_date) - - - def test_string_model_and_string_model_type(self): - - class MyModel(schemas.StringModel): - - import_format = r"(?P.*)==(?P\d*)" - export_format = "{left}=={right}" - - left = schemas.StringType() - right = schemas.IntType() - - class SchemaExample(schemas.Model): - - my_model = schemas.StringModelType(MyModel) - - short_data = {"my_model": "ten==10"} - long_data = {"my_model": {"left": "ten", "right": 10}} - model_data = {"my_model": MyModel("ten==10")} - invalid_data = {"my_model": "10==ten"} - - expected_data = {"my_model": "ten==10"} - - m = SchemaExample() - - self.assertDictEqual(m.import_data(short_data).to_primitive(), expected_data) - self.assertDictEqual(m.import_data(long_data).to_primitive(), expected_data) - self.assertDictEqual(m.import_data(model_data).to_primitive(), expected_data) - - self.assertDictEqual(m.import_data(short_data).to_dict(), expected_data) - self.assertDictEqual(m.import_data(long_data).to_dict(), expected_data) - self.assertDictEqual(m.import_data(model_data).to_dict(), expected_data) - - with self.assertRaises(schemas.SchematicsDataError): - m.import_data(invalid_data) - - def test_string_list_type(self): - - class SchemaExample(schemas.Model): - - area_list = schemas.StringListType(schemas.StringModelType(schemas.Area), separator="+") - string_list = schemas.StringListType(schemas.StringType, separator="+") - - string_data = {"string_list": "a+b+c", "area_list": "10km@-36.847585,174.765742+10km@-41.288058,174.778265"} - list_data = {"string_list": ["a", "b", "c"], "area_list": ["10km@-36.847585,174.765742", "10km@-41.288058,174.778265"]} - dict_data = {"string_list": ["a", "b", "c"], "area_list": [{"radius": "10km", "latitude": -36.847585, "longitude": 174.765742}, {"radius": "10km", "latitude": -41.288058, "longitude": 174.778265}]} - - expected_data = {"string_list": "a+b+c", "area_list": "10km@-36.847585,174.765742+10km@-41.288058,174.778265"} - - m = SchemaExample() - self.assertDictEqual(m.import_data(string_data).to_primitive(), expected_data) - self.assertDictEqual(m.import_data(list_data).to_primitive(), expected_data) - self.assertDictEqual(m.import_data(dict_data).to_primitive(), expected_data) - - unique_item_data = {"string_list": "a", "area_list": "10km@-36.847585,174.765742"} - unique_item_dict_data = {"string_list": "a", "area_list": {"radius": "10km", "latitude": -36.847585, "longitude": 174.765742}} - self.assertDictEqual(m.import_data(unique_item_data).to_primitive(), unique_item_data) - self.assertDictEqual(m.import_data(unique_item_dict_data).to_primitive(), unique_item_data) - - def test_list_type(self): - - class SchemaExample(schemas.Model): - - string_list = schemas.ListType(schemas.StringType) - - m = SchemaExample() - self.assertDictEqual(m.import_data({"string_list": "string"}).to_primitive(), {"string_list": ["string"]}) - self.assertDictEqual(m.import_data({"string_list": ["string1", "string2"]}).to_primitive(), {"string_list": ["string1", "string2"]}) - - def test_geo_json_point_type(self): - - class SchemaExample(schemas.Model): - - point = schemas.GeoJSONPointType() - - m = SchemaExample() - self.assertDictEqual(m.import_data({"point": [174.765742, -36.847585]}).to_primitive(), {"point": [174.765742, -36.847585]}) - - with self.assertRaises(schemas.SchematicsDataError): - m.import_data({"point": [-36.847585, 174.765742]}, validate=True) - - def test_date_around_type(self): - - class SchemaExample(schemas.Model): - around = schemas.ModelType(schemas.DateAround) - - m = SchemaExample() - - self.assertDictEqual(m.import_data({"around": {"origin": '2020-01-01', "offset": "1d", "scale": "0d", "decay": "0.1"}}).to_primitive(), - {'around': {'origin': '2020-01-01', 'decay': 0.1, 'scale': u'0d', 'offset': u'1d'}}) - - with self.assertRaises(schemas.SchematicsDataError): - m.import_data({"around": "2020-01-01"}, validate=True) - - def test_location_around_type(self): - class SchemaExample(schemas.Model): - around = schemas.ModelType(schemas.LocationAround) - - m = SchemaExample() - - self.assertDictEqual(m.import_data( - {"around": {"origin": '40.730610,-73.935242', "offset": "1km", "scale": "2km", "decay": "0.1"}}).to_primitive(), - {'around': {'origin': u'40.730610,-73.935242', 'decay': 0.1, 'scale': u'2km', 'offset': u'1km'}}) - - with self.assertRaises(schemas.SchematicsDataError): - m.import_data({"around": "40.730610,-73.935242"}, validate=True) - - def test_area_model(self): - - class SchemaExample(schemas.Model): - - area = schemas.StringModelType(schemas.Area) - - short_data = {"area": "10km@-36.847585,174.765742"} - long_data = {"area": {"radius": "10km", "latitude": -36.847585, "longitude": 174.765742}} - model_data = {"area": schemas.Area("10km@-36.847585,174.765742")} - invalid_data = {"area": "10k@-36.847585,174.765742"} - - expected_expected = {"area": "10km@-36.847585,174.765742"} - - m = SchemaExample() - self.assertDictEqual(m.import_data(short_data).to_primitive(), expected_expected) - self.assertDictEqual(m.import_data(long_data).to_primitive(), expected_expected) - self.assertDictEqual(m.import_data(model_data).to_primitive(), expected_expected) - - self.assertDictEqual(m.import_data(short_data).to_dict(), expected_expected) - self.assertDictEqual(m.import_data(long_data).to_dict(), expected_expected) - self.assertDictEqual(m.import_data(model_data).to_dict(), expected_expected) - - with self.assertRaises(schemas.SchematicsDataError): - m.import_data(invalid_data) - - def test_location_model(self): - - class SchemaExample(schemas.Model): - - location = schemas.StringModelType(schemas.Location) - - short_data = {"location": "@-36.847585,174.765742"} - long_data = {"location": {"latitude": -36.847585, "longitude": 174.765742}} - model_data = {"location": schemas.Location("@-36.847585,174.765742")} - invalid_data = {"location": "-36.847585,174.765742"} - - expected_expected = {"location": "@-36.847585,174.765742"} - - m = SchemaExample() - self.assertDictEqual(m.import_data(short_data).to_primitive(), expected_expected) - self.assertDictEqual(m.import_data(long_data).to_primitive(), expected_expected) - self.assertDictEqual(m.import_data(model_data).to_primitive(), expected_expected) - - self.assertDictEqual(m.import_data(short_data).to_dict(), expected_expected) - self.assertDictEqual(m.import_data(long_data).to_dict(), expected_expected) - self.assertDictEqual(m.import_data(model_data).to_dict(), expected_expected) - - with self.assertRaises(schemas.SchematicsDataError): - m.import_data(invalid_data) - - def test_resultset(self): - - class ResultExample(schemas.Model): - - value = schemas.IntType() - - class ResultSetExample(schemas.ResultSet): - - results = schemas.ResultType(ResultExample) - - class EndpointExample(BaseEndpoint): - - @decorators.returns(ResultSetExample) - def load_page(self, page): - page = int(page) - return { - "count": 9, - "next": "http://example.org/?page={}".format(page + 1) if page < 3 else None, - "previous": "http://example.org/?page={}".format(page - 1) if page > 1 else None, - "results": [{"value": 1 + (3 * (page - 1))}, {"value": 2 + (3 * (page - 1))}, {"value": 3 + (3 * (page - 1))}] - } - - endpoint = EndpointExample(None) - - p1 = endpoint.load_page(page=1) - self.assertEqual(p1.count, 9) - self.assertListEqual(list(p1), [ResultExample({"value": 1}), ResultExample({"value": 2}), ResultExample({"value": 3})]) - self.assertFalse(p1.has_previous()) - self.assertTrue(p1.has_next()) - self.assertIsNone(p1.get_previous()) - - p2 = p1.get_next() - - self.assertListEqual(list(p2), [ResultExample({"value": 4}), ResultExample({"value": 5}), ResultExample({"value": 6})]) - self.assertTrue(p2.has_previous()) - self.assertTrue(p2.has_next()) - - p3 = p2.get_next() - self.assertListEqual(list(p3), [ResultExample({"value": 7}), ResultExample({"value": 8}), ResultExample({"value": 9})]) - self.assertTrue(p3.has_previous()) - self.assertFalse(p3.has_next()) - - self.assertIsNone(p3.get_next()) - self.assertListEqual(list(p3.get_previous()), list(p2)) - - self.assertListEqual(list(p1.iter_pages()), [endpoint.load_page(page=2), endpoint.load_page(page=3)]) - self.assertListEqual(list(p1.iter_all()), list(p1) + list(p2) + list(p3)) - - for item in p1.iter_all(): - self.assertEqual(item._endpoint, endpoint) diff --git a/tests/endpoints/test_decorators.py b/tests/endpoints/test_decorators.py new file mode 100644 index 0000000..48c2dd0 --- /dev/null +++ b/tests/endpoints/test_decorators.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import, print_function + +import pytest + +from predicthq.endpoints import decorators, schemas +from predicthq.endpoints.base import BaseEndpoint +from predicthq.exceptions import ValidationError + + +def test_to_params(): + kwargs = {"string_type": "my-string", "list_type": [1, 2, 3], "dict_type": {"key1": "val1", "key2": "val2"}, "bool_type": True} + expected = {'string_type': 'my-string', 'list_type': '1,2,3', 'dict_type.key1': u'val1', 'dict_type.key2': 'val2', 'bool_type': 1} + assert decorators._to_url_params(kwargs) == expected + + +def test_kwargs_processor(): + kwargs = {"normal_arg": "value", "nested__arg": "value"} + expected = {'normal_arg': 'value', 'nested': {'arg': 'value'}} + assert decorators._process_kwargs(kwargs) == expected + + +def test_accepts(): + + class SchemaExample(schemas.Model): + arg1 = schemas.StringType(required=True) + arg2 = schemas.ListType(schemas.IntType) + + class EndpointExample(BaseEndpoint): + + @decorators.accepts(SchemaExample) + def func(self, **kwargs): + return kwargs + + endpoint = EndpointExample(None) + assert endpoint.func(arg1="test", arg2=[1, 2]) == {'arg1': 'test', 'arg2': '1,2'} + + assert endpoint.func(SchemaExample({"arg1": "test", "arg2": [1, 2]})) == {'arg1': 'test', 'arg2': '1,2'} + + assert endpoint.func({"arg1": "test", "arg2": [1, 2]}) == {'arg1': 'test', 'arg2': '1,2'} + + with pytest.raises(ValidationError): + endpoint.func(arg2=[1, 2]) + + with pytest.raises(ValidationError): + endpoint.func(arg1="value", arg2="invalid") + + +def test_accepts_for_body_use(): + + class SchemaExample(schemas.Model): + arg1 = schemas.StringType(required=True) + arg2 = schemas.ListType(schemas.IntType) + + class EndpointExample(BaseEndpoint): + + @decorators.accepts(SchemaExample, query_string=False) + def func(self, **kwargs): + return kwargs + + endpoint = EndpointExample(None) + assert endpoint.func({"arg1": "test", "arg2": [1, 2]}) == {'arg1': 'test', 'arg2': [1, 2]} + + +def test_returns(): + + class SchemaExample(schemas.Model): + arg1 = schemas.StringType(required=True) + arg2 = schemas.ListType(schemas.IntType) + + class EndpointExample(BaseEndpoint): + + @decorators.returns(SchemaExample) + def func(self, **kwargs): + return kwargs + + endpoint = EndpointExample(None) + assert endpoint.func(arg1="test", arg2=[1, 2]) == SchemaExample({'arg1': 'test', 'arg2': [1, 2]}) + + with pytest.raises(ValidationError): + endpoint.func(arg2=[1, 2]) + + with pytest.raises(ValidationError): + endpoint.func(arg1="value", arg2="invalid") + + +def test_returns_resultset_of_native_types(): + + class SchemaExample(schemas.ResultSet): + + results = schemas.ListType(schemas.StringType) + + class EndpointExample(BaseEndpoint): + + @decorators.returns(SchemaExample) + def func(self, **kwargs): + return kwargs + + endpoint = EndpointExample(None) + assert endpoint.func(results=["item1", "item2"]) == SchemaExample({"results": ["item1", "item2"]}) + assert endpoint.func()._more(results=["item3", "item4"]) == SchemaExample({'results': ['item3', 'item4']}) + assert endpoint == endpoint.func()._endpoint + + +def test_returns_resultset_of_models(): + + class ModelExample(schemas.ResultSet): + + name = schemas.StringType() + + class SchemaExample(schemas.ResultSet): + + results = schemas.ResultType(ModelExample) + + class EndpointExample(BaseEndpoint): + + @decorators.returns(SchemaExample) + def func(self, **kwargs): + return kwargs + + endpoint = EndpointExample(None) + results = endpoint.func(results=[{"name": "item1"}, {"name": "item2"}]) + assert results, SchemaExample({"results": [{"name": "item1"} == {"name": "item2"}]}) + assert endpoint.func()._more(results=[{"name": "item2"}, {"name": "item4"}]) == SchemaExample({'results': [{"name": "item2"}, {"name": "item4"}]}) + + for item in results: + assert item._endpoint == endpoint diff --git a/tests/endpoints/oauth2_tests.py b/tests/endpoints/test_oauth2.py similarity index 54% rename from tests/endpoints/oauth2_tests.py rename to tests/endpoints/test_oauth2.py index 4857f7b..5c71044 100644 --- a/tests/endpoints/oauth2_tests.py +++ b/tests/endpoints/test_oauth2.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import, print_function +import pytest import unittest import six @@ -16,46 +17,46 @@ class OAuth2Test(unittest.TestCase): def test_get_token_params(self, client): token = client.oauth2.get_token(client_id='client_id', client_secret='client_secret', scope=['account', 'events']) client.request.assert_called_once_with('post', '/oauth2/token/', auth=('client_id', 'client_secret'), data={'scope': 'account events', 'grant_type': 'client_credentials'}) - self.assertIsInstance(token, AccessToken) - self.assertDictEqual(token.to_primitive(), client.request.return_value) + assert isinstance(token, AccessToken) + assert token.to_primitive() == client.request.return_value - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): client.oauth2.get_token(client_id=None) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): client.oauth2.get_token(invalid_arg=None) @with_mock_client() def test_revoke_token_params(self, client): result = client.oauth2.revoke_token(client_id='client_id', client_secret='client_secret', token='token123') client.request.assert_called_once_with('post', '/oauth2/revoke/', auth=('client_id', 'client_secret'), data={'token': 'token123', 'token_type_hint': 'access_token'}) - self.assertIsNone(result) + assert result is None - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): client.oauth2.revoke_token(token=None) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): client.oauth2.revoke_token(invalid_arg=None) @with_client() @with_mock_responses() def test_get_token(self, client, responses): token = client.oauth2.get_token(client_id='client_id', client_secret='client_secret', scope=['account', 'events']) - self.assertIsInstance(token, AccessToken) - self.assertEqual(token.access_token, "token123") - self.assertEqual(token.token_type, "Bearer") - self.assertEqual(token.scope, ['account', 'events']) - self.assertEqual(len(responses.calls), 1) - self.assertEqual(responses.calls[0].request.headers['Content-Type'], 'application/x-www-form-urlencoded') - self.assertEqual(responses.calls[0].request.headers['Authorization'], 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=') - self.assertDictEqual(dict(six.moves.urllib.parse.parse_qsl(responses.calls[0].request.body)), {'scope': 'account events', 'grant_type': 'client_credentials'}) + assert isinstance(token, AccessToken) + assert token.access_token == "token123" + assert token.token_type == "Bearer" + assert token.scope == ['account', 'events'] + assert len(responses.calls) == 1 + assert responses.calls[0].request.headers['Content-Type'] == 'application/x-www-form-urlencoded' + assert responses.calls[0].request.headers['Authorization'] == 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=' + assert dict(six.moves.urllib.parse.parse_qsl(responses.calls[0].request.body)) == {'scope': 'account events', 'grant_type': 'client_credentials'} @with_client() @with_mock_responses() def test_revoke_token(self, client, responses): result = client.oauth2.revoke_token(client_id='client_id', client_secret='client_secret', token='token123') - self.assertIsNone(result) - self.assertEqual(len(responses.calls), 1) - self.assertEqual(responses.calls[0].request.headers['Content-Type'], 'application/x-www-form-urlencoded') - self.assertEqual(responses.calls[0].request.headers['Authorization'], 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=') - self.assertDictEqual(dict(six.moves.urllib.parse.parse_qsl(responses.calls[0].request.body)), {'token_type_hint': 'access_token', 'token': 'token123'}) + assert result is None + assert len(responses.calls) == 1 + assert responses.calls[0].request.headers['Content-Type'] == 'application/x-www-form-urlencoded' + assert responses.calls[0].request.headers['Authorization'] == 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=' + assert dict(six.moves.urllib.parse.parse_qsl(responses.calls[0].request.body)) == {'token_type_hint': 'access_token', 'token': 'token123'} diff --git a/tests/endpoints/test_schemas.py b/tests/endpoints/test_schemas.py new file mode 100644 index 0000000..aeb6140 --- /dev/null +++ b/tests/endpoints/test_schemas.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import, print_function + +import pytest + +import pytz +from datetime import datetime, date + +from predicthq.endpoints import decorators, schemas +from predicthq.endpoints.base import BaseEndpoint + + +def test_datetime_type(): + + class SchemaExample(schemas.Model): + + my_datetime = schemas.DateTimeType() + + test_date = datetime(2016, 1, 1, tzinfo=pytz.UTC) + assert SchemaExample({"my_datetime": "2016-01-01T00:00:00+00:00"}).my_datetime == test_date + assert SchemaExample({"my_datetime": "2016-01-01T00:00:00+0000"}).my_datetime == test_date + assert SchemaExample({"my_datetime": "2016-01-01T00:00:00Z"}).my_datetime == test_date + assert SchemaExample({"my_datetime": test_date}).my_datetime == test_date + + +def test_date_type(): + + class SchemaExample(schemas.Model): + + my_date = schemas.DateType() + + test_date = date(2016, 1, 1) + assert SchemaExample({"my_date": "2016-01-01"}).my_date == test_date + assert SchemaExample({"my_date": "2016-01-01T00:00:00+0000"}).my_date == test_date + assert SchemaExample({"my_date": "2016-01-01T00:00:00Z"}).my_date == test_date + assert SchemaExample({"my_date": test_date}).my_date == test_date + + +def test_string_model_and_string_model_type(): + + class MyModel(schemas.StringModel): + + import_format = r"(?P.*)==(?P\d*)" + export_format = "{left}=={right}" + + left = schemas.StringType() + right = schemas.IntType() + + class SchemaExample(schemas.Model): + + my_model = schemas.StringModelType(MyModel) + + short_data = {"my_model": "ten==10"} + long_data = {"my_model": {"left": "ten", "right": 10}} + model_data = {"my_model": MyModel("ten==10")} + invalid_data = {"my_model": "10==ten"} + + expected_data = {"my_model": "ten==10"} + + m = SchemaExample() + + assert m.import_data(short_data).to_primitive() == expected_data + assert m.import_data(long_data).to_primitive() == expected_data + assert m.import_data(model_data).to_primitive() == expected_data + + assert m.import_data(short_data).to_dict() == expected_data + assert m.import_data(long_data).to_dict() == expected_data + assert m.import_data(model_data).to_dict() == expected_data + + with pytest.raises(schemas.SchematicsDataError): + m.import_data(invalid_data) + + +def test_string_list_type(): + + class SchemaExample(schemas.Model): + + area_list = schemas.StringListType(schemas.StringModelType(schemas.Area), separator="+") + string_list = schemas.StringListType(schemas.StringType, separator="+") + + string_data = {"string_list": "a+b+c", "area_list": "10km@-36.847585,174.765742+10km@-41.288058,174.778265"} + list_data = {"string_list": ["a", "b", "c"], "area_list": ["10km@-36.847585,174.765742", "10km@-41.288058,174.778265"]} + dict_data = {"string_list": ["a", "b", "c"], "area_list": [{"radius": "10km", "latitude": -36.847585, "longitude": 174.765742}, {"radius": "10km", "latitude": -41.288058, "longitude": 174.778265}]} + + expected_data = {"string_list": "a+b+c", "area_list": "10km@-36.847585,174.765742+10km@-41.288058,174.778265"} + + m = SchemaExample() + assert m.import_data(string_data).to_primitive() == expected_data + assert m.import_data(list_data).to_primitive() == expected_data + assert m.import_data(dict_data).to_primitive() == expected_data + + unique_item_data = {"string_list": "a", "area_list": "10km@-36.847585,174.765742"} + unique_item_dict_data = {"string_list": "a", "area_list": {"radius": "10km", "latitude": -36.847585, "longitude": 174.765742}} + assert m.import_data(unique_item_data).to_primitive() == unique_item_data + assert m.import_data(unique_item_dict_data).to_primitive() == unique_item_data + + +def test_list_type(): + + class SchemaExample(schemas.Model): + + string_list = schemas.ListType(schemas.StringType) + + m = SchemaExample() + assert m.import_data({"string_list": "string"}).to_primitive() == {"string_list": ["string"]} + assert m.import_data({"string_list": ["string1", "string2"]}).to_primitive() == {"string_list": ["string1", "string2"]} + + +def test_geo_json_point_type(): + + class SchemaExample(schemas.Model): + + point = schemas.GeoJSONPointType() + + m = SchemaExample() + assert m.import_data({"point": [174.765742, -36.847585]}).to_primitive() == {"point": [174.765742, -36.847585]} + + with pytest.raises(schemas.SchematicsDataError): + m.import_data({"point": [-36.847585, 174.765742]}, validate=True) + + +def test_date_around_type(): + + class SchemaExample(schemas.Model): + around = schemas.ModelType(schemas.DateAround) + + m = SchemaExample() + + assert m.import_data({"around": {"origin": '2020-01-01', "offset": "1d", "scale": "0d", "decay": "0.1"}}).to_primitive() == {'around': {'origin': '2020-01-01', 'decay': 0.1, 'scale': u'0d', 'offset': u'1d'}} + + with pytest.raises(schemas.SchematicsDataError): + m.import_data({"around": "2020-01-01"}, validate=True) + + +def test_location_around_type(): + class SchemaExample(schemas.Model): + around = schemas.ModelType(schemas.LocationAround) + + m = SchemaExample() + + assert m.import_data({"around": {"origin": '40.730610,-73.935242', "offset": "1km", "scale": "2km", "decay": "0.1"}}).to_primitive() == {'around': {'origin': u'40.730610,-73.935242', 'decay': 0.1, 'scale': u'2km', 'offset': u'1km'}} + + with pytest.raises(schemas.SchematicsDataError): + m.import_data({"around": "40.730610,-73.935242"}, validate=True) + + +def test_area_model(): + + class SchemaExample(schemas.Model): + + area = schemas.StringModelType(schemas.Area) + + short_data = {"area": "10km@-36.847585,174.765742"} + long_data = {"area": {"radius": "10km", "latitude": -36.847585, "longitude": 174.765742}} + model_data = {"area": schemas.Area("10km@-36.847585,174.765742")} + invalid_data = {"area": "10k@-36.847585,174.765742"} + + expected_expected = {"area": "10km@-36.847585,174.765742"} + + m = SchemaExample() + assert m.import_data(short_data).to_primitive() == expected_expected + assert m.import_data(long_data).to_primitive() == expected_expected + assert m.import_data(model_data).to_primitive() == expected_expected + + assert m.import_data(short_data).to_dict() == expected_expected + assert m.import_data(long_data).to_dict() == expected_expected + assert m.import_data(model_data).to_dict() == expected_expected + + with pytest.raises(schemas.SchematicsDataError): + m.import_data(invalid_data) + + +def test_location_model(): + + class SchemaExample(schemas.Model): + + location = schemas.StringModelType(schemas.Location) + + short_data = {"location": "@-36.847585,174.765742"} + long_data = {"location": {"latitude": -36.847585, "longitude": 174.765742}} + model_data = {"location": schemas.Location("@-36.847585,174.765742")} + invalid_data = {"location": "-36.847585,174.765742"} + + expected_expected = {"location": "@-36.847585,174.765742"} + + m = SchemaExample() + assert m.import_data(short_data).to_primitive() == expected_expected + assert m.import_data(long_data).to_primitive() == expected_expected + assert m.import_data(model_data).to_primitive() == expected_expected + + assert m.import_data(short_data).to_dict() == expected_expected + assert m.import_data(long_data).to_dict() == expected_expected + assert m.import_data(model_data).to_dict() == expected_expected + + with pytest.raises(schemas.SchematicsDataError): + m.import_data(invalid_data) + + +def test_resultset(): + + class ResultExample(schemas.Model): + + value = schemas.IntType() + + class ResultSetExample(schemas.ResultSet): + + results = schemas.ResultType(ResultExample) + + class EndpointExample(BaseEndpoint): + + @decorators.returns(ResultSetExample) + def load_page(self, page): + page = int(page) + return { + "count": 9, + "next": "http://example.org/?page={}".format(page + 1) if page < 3 else None, + "previous": "http://example.org/?page={}".format(page - 1) if page > 1 else None, + "results": [{"value": 1 + (3 * (page - 1))}, {"value": 2 + (3 * (page - 1))}, {"value": 3 + (3 * (page - 1))}] + } + + endpoint = EndpointExample(None) + + p1 = endpoint.load_page(page=1) + assert p1.count == 9 + assert list(p1) == [ResultExample({"value": 1}), ResultExample({"value": 2}), ResultExample({"value": 3})] + assert p1.has_previous() is False + assert p1.has_next() is True + assert p1.get_previous() is None + + p2 = p1.get_next() + + assert list(p2) == [ResultExample({"value": 4}), ResultExample({"value": 5}), ResultExample({"value": 6})] + assert p2.has_previous() is True + assert p2.has_next() is True + + p3 = p2.get_next() + assert list(p3) == [ResultExample({"value": 7}), ResultExample({"value": 8}), ResultExample({"value": 9})] + assert p3.has_previous() is True + assert p3.has_next() is False + + assert p3.get_next() is None + assert list(p3.get_previous()) == list(p2) + + assert list(p1.iter_pages()) == [endpoint.load_page(page=2), endpoint.load_page(page=3)] + assert list(p1.iter_all()) == list(p1) + list(p2) + list(p3) + + for item in p1.iter_all(): + assert item._endpoint == endpoint diff --git a/tests/endpoints/v1/__init__.py b/tests/endpoints/v1/__init__.py index d45e719..e69de29 100644 --- a/tests/endpoints/v1/__init__.py +++ b/tests/endpoints/v1/__init__.py @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function diff --git a/tests/endpoints/v1/events_tests.py b/tests/endpoints/v1/events_tests.py deleted file mode 100644 index 6697512..0000000 --- a/tests/endpoints/v1/events_tests.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function - -import unittest -from datetime import datetime - -from tests import with_mock_client, with_mock_responses, with_client - -from predicthq.endpoints.v1.events.schemas import EventResultSet, CalendarResultSet, Count - - -class EventsTest(unittest.TestCase): - - @with_mock_client() - def test_search_params_underscores(self, client): - client.events.search(id="id", q="query", country=["NZ", "AU"], - rank_level=[4, 5], rank__gt=85, local_rank_level=[4, 5], local_rank__gt=85, - within__radius="2km", within__longitude=-71.0432, within__latitude=42.346, - label=["label1", "label2"], category="category", state='deleted', - start_around__origin='2016-03-05', start_around__scale='5d', - place__scope=["place1", "place2"], place__exact=["place3"], - start__gte="2016-03-01", start__lt=datetime(2016, 4, 1), start__tz="Pacific/Auckland", - end__gte="2016-05-01", end__lt=datetime(2016, 6, 1), end__tz="Pacific/Auckland", - active__gte="2016-03-01", active__lt=datetime(2016, 4, 1), active__tz="Pacific/Auckland", - updated__gte="2016-03-01", updated__lt=datetime(2016, 4, 1), updated__tz="Pacific/Auckland") - - client.request.assert_called_once_with('get', '/v1/events/', params={ - 'id': 'id', 'category': 'category', 'state': 'deleted', 'country': 'NZ,AU', - 'rank.gt': 85, 'rank_level': '4,5', 'local_rank.gt': 85, 'local_rank_level': '4,5', - 'within': '2km@42.346,-71.0432', 'label': 'label1,label2', 'q': 'query', - 'start_around.origin': '2016-03-05', 'start_around.scale': '5d', - 'place.scope': 'place1,place2', 'place.exact': 'place3', - 'start.lt': '2016-04-01T00:00:00.000000', 'start.gte': '2016-03-01T00:00:00.000000', 'start.tz': 'Pacific/Auckland', - 'end.lt': '2016-06-01T00:00:00.000000', 'end.gte': '2016-05-01T00:00:00.000000', 'end.tz': 'Pacific/Auckland', - 'active.lt': '2016-04-01T00:00:00.000000', 'active.gte': '2016-03-01T00:00:00.000000', 'active.tz': 'Pacific/Auckland', - 'updated.lt': '2016-04-01T00:00:00.000000', 'updated.gte': '2016-03-01T00:00:00.000000', 'updated.tz': 'Pacific/Auckland'}) - - @with_mock_client() - def test_search_params_dicts(self, client): - client.events.search(id="id", q="query", rank_level=[4,5], rank={"gt": 85}, country=["NZ", "AU"], - within={"radius": "2km", "longitude": -71.0432, "latitude": 42.346}, - label=["label1", "label2"], category="category", state='deleted', - place={"scope": ["place1", "place2"], "exact": "place3"}, - location_around={'origin': '40.730610,-73.935242', 'scale': '2km', 'offset': '0.5km'}, - start={"gte": "2016-03-01", "lt": datetime(2016, 4, 1), "tz": "Pacific/Auckland"}, - end={"gte": "2016-05-01", "lt": datetime(2016, 6, 1), "tz": "Pacific/Auckland"}, - active={"gte": "2016-03-01", "lt": datetime(2016, 4, 1), "tz": "Pacific/Auckland"}, - updated={"gte": "2016-03-01", "lt": datetime(2016, 4, 1), "tz": "Pacific/Auckland"}) - - client.request.assert_called_once_with('get', '/v1/events/', params={ - 'id': 'id', 'rank.gt': 85, 'rank_level': '4,5', 'category': 'category', 'state': 'deleted', 'country': 'NZ,AU', - 'within': '2km@42.346,-71.0432', 'label': 'label1,label2', 'q': 'query', - 'place.scope': 'place1,place2', 'place.exact': 'place3', - 'location_around.origin': '40.730610,-73.935242', 'location_around.scale': '2km', 'location_around.offset': '0.5km', - 'start.lt': '2016-04-01T00:00:00.000000', 'start.gte': '2016-03-01T00:00:00.000000', 'start.tz': 'Pacific/Auckland', - 'end.lt': '2016-06-01T00:00:00.000000', 'end.gte': '2016-05-01T00:00:00.000000', 'end.tz': 'Pacific/Auckland', - 'active.lt': '2016-04-01T00:00:00.000000', 'active.gte': '2016-03-01T00:00:00.000000', 'active.tz': 'Pacific/Auckland', - 'updated.lt': '2016-04-01T00:00:00.000000', 'updated.gte': '2016-03-01T00:00:00.000000', 'updated.tz': 'Pacific/Auckland'}) - - @with_mock_client() - def test_search_for_account(self, client): - client.events.for_account('account-id').search(q="query") - client.request.assert_called_once_with('get', '/v1/accounts/account-id/events/', params={'q': 'query'}) - - @with_client() - @with_mock_responses() - def test_search(self, client, responses): - result = client.events.search(q="Foo Fighters", country="AU", limit=10) - self.assertIsInstance(result, EventResultSet) - self.assertEqual(result.count, len(list(result.iter_all()))) - self.assertEqual(2, len(responses.calls)) - - @with_client() - @with_mock_responses() - def test_count(self, client, responses): - result = client.events.count(active__gte="2015-01-01", active__lte="2015-12-31", within="50km@-27.470784,153.030124") - self.assertIsInstance(result, Count) - self.assertEqual(result.count, 2501) - - @with_client() - @with_mock_responses() - def test_calendar(self, client, responses): - result = client.events.calendar(active__gte="2015-12-24", active__lte="2015-12-26", country="NZ", top_events__limit=1, top_events__sort=["rank"], active__tz="Pacific/Auckland") - self.assertIsInstance(result, CalendarResultSet) - self.assertEqual(result.count, 60) - self.assertEqual(3, len(list(result.iter_all()))) - self.assertEqual(1, len(responses.calls)) diff --git a/tests/endpoints/v1/accounts_tests.py b/tests/endpoints/v1/test_accounts.py similarity index 88% rename from tests/endpoints/v1/accounts_tests.py rename to tests/endpoints/v1/test_accounts.py index 2599091..7df588f 100644 --- a/tests/endpoints/v1/accounts_tests.py +++ b/tests/endpoints/v1/test_accounts.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals, absolute_import, print_function import unittest -from datetime import datetime from tests import with_mock_client, with_mock_responses, with_client @@ -20,4 +19,4 @@ def test_self_params(self, client): @with_mock_responses() def test_self(self, client, responses): account = client.accounts.self() - self.assertIsInstance(account, Account) + assert isinstance(account, Account) diff --git a/tests/endpoints/v1/test_events.py b/tests/endpoints/v1/test_events.py new file mode 100644 index 0000000..46a18ff --- /dev/null +++ b/tests/endpoints/v1/test_events.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import, print_function + +import unittest +from datetime import datetime + +from tests import with_mock_client, with_mock_responses, with_client + +from predicthq.endpoints.v1.events.schemas import EventResultSet, CalendarResultSet, Count + + +class EventsTest(unittest.TestCase): + + @with_mock_client() + def test_search_params_underscores(self, client): + client.events.search( + id="id", q="query", country=["NZ", "AU"], + rank_level=[4, 5], rank__gt=85, local_rank_level=[4, 5], local_rank__gt=85, + within__radius="2km", within__longitude=-71.0432, within__latitude=42.346, + label=["label1", "label2"], category="category", state='deleted', + start_around__origin='2016-03-05', start_around__scale='5d', + place__scope=["place1", "place2"], place__exact=["place3"], + start__gte="2016-03-01", start__lt=datetime(2016, 4, 1), start__tz="Pacific/Auckland", + end__gte="2016-05-01", end__lt=datetime(2016, 6, 1), end__tz="Pacific/Auckland", + active__gte="2016-03-01", active__lt=datetime(2016, 4, 1), active__tz="Pacific/Auckland", + updated__gte="2016-03-01", updated__lt=datetime(2016, 4, 1), updated__tz="Pacific/Auckland", + ) + + client.request.assert_called_once_with( + 'get', '/v1/events/', params={ + 'id': 'id', 'category': 'category', 'state': 'deleted', 'country': 'NZ,AU', + 'rank.gt': 85, 'rank_level': '4,5', 'local_rank.gt': 85, 'local_rank_level': '4,5', + 'within': '2km@42.346,-71.0432', 'label': 'label1,label2', 'q': 'query', + 'start_around.origin': '2016-03-05', 'start_around.scale': '5d', + 'place.scope': 'place1,place2', 'place.exact': 'place3', + 'start.lt': '2016-04-01T00:00:00.000000', 'start.gte': '2016-03-01T00:00:00.000000', 'start.tz': 'Pacific/Auckland', + 'end.lt': '2016-06-01T00:00:00.000000', 'end.gte': '2016-05-01T00:00:00.000000', 'end.tz': 'Pacific/Auckland', + 'active.lt': '2016-04-01T00:00:00.000000', 'active.gte': '2016-03-01T00:00:00.000000', 'active.tz': 'Pacific/Auckland', + 'updated.lt': '2016-04-01T00:00:00.000000', 'updated.gte': '2016-03-01T00:00:00.000000', 'updated.tz': 'Pacific/Auckland', + } + ) + + @with_mock_client() + def test_search_params_dicts(self, client): + client.events.search( + id="id", q="query", rank_level=[4, 5], rank={"gt": 85}, country=["NZ", "AU"], + within={"radius": "2km", "longitude": -71.0432, "latitude": 42.346}, + label=["label1", "label2"], category="category", state='deleted', + place={"scope": ["place1", "place2"], "exact": "place3"}, + location_around={'origin': '40.730610,-73.935242', 'scale': '2km', 'offset': '0.5km'}, + start={"gte": "2016-03-01", "lt": datetime(2016, 4, 1), "tz": "Pacific/Auckland"}, + end={"gte": "2016-05-01", "lt": datetime(2016, 6, 1), "tz": "Pacific/Auckland"}, + active={"gte": "2016-03-01", "lt": datetime(2016, 4, 1), "tz": "Pacific/Auckland"}, + updated={"gte": "2016-03-01", "lt": datetime(2016, 4, 1), "tz": "Pacific/Auckland"}, + ) + + client.request.assert_called_once_with( + 'get', '/v1/events/', params={ + 'id': 'id', 'rank.gt': 85, 'rank_level': '4,5', 'category': 'category', 'state': 'deleted', 'country': 'NZ,AU', + 'within': '2km@42.346,-71.0432', 'label': 'label1,label2', 'q': 'query', + 'place.scope': 'place1,place2', 'place.exact': 'place3', + 'location_around.origin': '40.730610,-73.935242', 'location_around.scale': '2km', 'location_around.offset': '0.5km', + 'start.lt': '2016-04-01T00:00:00.000000', 'start.gte': '2016-03-01T00:00:00.000000', 'start.tz': 'Pacific/Auckland', + 'end.lt': '2016-06-01T00:00:00.000000', 'end.gte': '2016-05-01T00:00:00.000000', 'end.tz': 'Pacific/Auckland', + 'active.lt': '2016-04-01T00:00:00.000000', 'active.gte': '2016-03-01T00:00:00.000000', 'active.tz': 'Pacific/Auckland', + 'updated.lt': '2016-04-01T00:00:00.000000', 'updated.gte': '2016-03-01T00:00:00.000000', 'updated.tz': 'Pacific/Auckland', + } + ) + + @with_mock_client() + def test_search_for_account(self, client): + client.events.for_account('account-id').search(q="query") + client.request.assert_called_once_with('get', '/v1/accounts/account-id/events/', params={'q': 'query'}) + + @with_client() + @with_mock_responses() + def test_search(self, client, responses): + result = client.events.search(q="Foo Fighters", country="AU", limit=10) + assert isinstance(result, EventResultSet) + assert result.count == len(list(result.iter_all())) + assert len(responses.calls) == 2 + + @with_client() + @with_mock_responses() + def test_count(self, client, responses): + result = client.events.count(active__gte="2015-01-01", active__lte="2015-12-31", within="50km@-27.470784,153.030124") + assert isinstance(result, Count) + assert result.count == 2501 + + @with_client() + @with_mock_responses() + def test_calendar(self, client, responses): + result = client.events.calendar(active__gte="2015-12-24", active__lte="2015-12-26", country="NZ", top_events__limit=1, top_events__sort=["rank"], active__tz="Pacific/Auckland") + assert isinstance(result, CalendarResultSet) + assert result.count == 60 + assert len(list(result.iter_all())) == 3 + assert len(responses.calls) == 1 diff --git a/tests/endpoints/v1/places_tests.py b/tests/endpoints/v1/test_places.py similarity index 77% rename from tests/endpoints/v1/places_tests.py rename to tests/endpoints/v1/test_places.py index 7f833a2..9d1d440 100644 --- a/tests/endpoints/v1/places_tests.py +++ b/tests/endpoints/v1/test_places.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import, print_function +import pytest import unittest from predicthq.endpoints import schemas -from tests import with_mock_client, with_mock_responses, with_client - from predicthq.endpoints.v1.places.schemas import PlaceResultSet +from tests import with_mock_client, with_mock_responses, with_client class PlacesTest(unittest.TestCase): @@ -18,13 +18,13 @@ def test_search_params(self, client): @with_mock_client() def test_invalide_search_params(self, client): - with self.assertRaises(schemas.SchematicsValidationError): + with pytest.raises(schemas.SchematicsValidationError): client.places.search() @with_client() @with_mock_responses() def test_search(self, client, responses): result = client.places.search(country=["NZ", "AU"]) - self.assertIsInstance(result, PlaceResultSet) - self.assertEqual(result.count, len(list(result.iter_all()))) - self.assertEqual(1, len(responses.calls)) + assert isinstance(result, PlaceResultSet) + assert result.count == len(list(result.iter_all())) + assert len(responses.calls) == 1 diff --git a/tests/client_tests.py b/tests/test_client.py similarity index 56% rename from tests/client_tests.py rename to tests/test_client.py index 33ea097..a4f7b49 100644 --- a/tests/client_tests.py +++ b/tests/test_client.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import, print_function - -import unittest +import pytest import predicthq from predicthq import endpoints @@ -12,97 +11,97 @@ from tests import with_mock_responses, with_config, with_mock_client, load_fixture -class ClientTest(unittest.TestCase): +class ClientTest(object): def setUp(self): self.client = predicthq.Client() @with_config(ENDPOINT_URL="https://api.predicthq.com") def test_build_url(self): - self.assertEqual(self.client.build_url('v1'), "https://api.predicthq.com/v1/") + assert self.client.build_url('v1') == "https://api.predicthq.com/v1/" def test_endpoints_initialization(self): - self.assertIsInstance(self.client.oauth2, endpoints.OAuth2Endpoint) - self.assertIsInstance(self.client.accounts, endpoints.AccountsEndpoint) - self.assertIsInstance(self.client.events, endpoints.EventsEndpoint) - self.assertIsInstance(self.client.places, endpoints.PlacesEndpoint) + assert isinstance(self.client.oauth2, endpoints.OAuth2Endpoint) + assert isinstance(self.client.accounts, endpoints.AccountsEndpoint) + assert isinstance(self.client.events, endpoints.EventsEndpoint) + assert isinstance(self.client.places, endpoints.PlacesEndpoint) @with_mock_responses() def test_request(self, responses): self.client.logger.setLevel('DEBUG') - self.assertDictEqual(self.client.get("/v1/"), responses.calls[0].response.json()) + assert self.client.get("/v1/") == responses.calls[0].response.json() - self.assertIsNone(self.client.get("/no-content/")) + assert self.client.get("/no-content/") is None - self.assertIsNone(self.client.get("/invalid-json/")) + assert self.client.get("/invalid-json/") is None - with self.assertRaises(ClientError) as ctx: + with pytest.raises(ClientError) as excinfo: self.client.get("/not-found/") - self.assertEqual(ctx.exception.message, responses.calls[3].response.json()) + assert excinfo.value.args[0] == responses.calls[3].response.json() - with self.assertRaises(ServerError) as ctx: + with pytest.raises(ServerError) as excinfo: self.client.get("/server-error/") - self.assertEqual(ctx.exception.message, responses.calls[4].response.json()) + assert excinfo.value.args[0] == responses.calls[4].response.json() - with self.assertRaises(ServerError) as ctx: + with pytest.raises(ServerError) as excinfo: self.client.get("/no-json/") - self.assertEqual(ctx.exception.message, responses.calls[5].response.content) + assert excinfo.value.args[0] == responses.calls[5].response.content # Test headers self.client.authenticate(client_id='client_id', client_secret='client_secret', scope=['account']) - self.assertEqual(responses.calls[6].request.headers['Authorization'], 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=') - self.assertEqual(responses.calls[6].request.headers['Content-Type'], 'application/x-www-form-urlencoded') + assert responses.calls[6].request.headers['Authorization'] == 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=' + assert responses.calls[6].request.headers['Content-Type'] == 'application/x-www-form-urlencoded' self.client.accounts.self() - self.assertEqual(responses.calls[7].request.headers['Authorization'], 'Bearer token123') - self.assertEqual(responses.calls[7].request.headers['Accept'], 'application/json') + assert responses.calls[7].request.headers['Authorization'] == 'Bearer token123' + assert responses.calls[7].request.headers['Accept'] == 'application/json' @with_mock_client(request_returns={"result": "value"}) def test_get(self, client): result = self.client.get('/get/', params={'q': 'query'}) client.request.assert_called_once_with('get', '/get/', params={'q': 'query'}) - self.assertDictEqual(result, client.request.return_value) + assert result == client.request.return_value @with_mock_client(request_returns={"result": "value"}) def test_post(self, client): result = self.client.post('/post/', data={'key': 'value'}) client.request.assert_called_once_with('post', '/post/', data={'key': 'value'}) - self.assertDictEqual(result, client.request.return_value) + assert result == client.request.return_value @with_mock_client(request_returns={"result": "value"}) def test_put(self, client): result = self.client.put('/put/', data={'key': 'value'}) client.request.assert_called_once_with('put', '/put/', data={'key': 'value'}) - self.assertDictEqual(result, client.request.return_value) + assert result == client.request.return_value @with_mock_client(request_returns={"result": "value"}) def test_patch(self, client): result = self.client.patch('/patch/', data={'key': 'value'}) client.request.assert_called_once_with('patch', '/patch/', data={'key': 'value'}) - self.assertDictEqual(result, client.request.return_value) + assert result == client.request.return_value @with_mock_client() def test_delete(self, client): result = client.delete('/delete/') client.request.assert_called_once_with('delete', '/delete/') - self.assertEqual(result, client.request.return_value) + assert result == client.request.return_value @with_mock_client(request_returns=load_fixture('access_token')) def test_authenticate(self, client): token = self.client.authenticate(client_id='client_id', client_secret='client_secret', scope=['account', 'events']) client.request.assert_called_once_with('post', '/oauth2/token/', auth=('client_id', 'client_secret'), data={'scope': 'account events', 'grant_type': 'client_credentials'}) - self.assertIsInstance(token, AccessToken) - self.assertDictEqual(token.to_primitive(), client.request.return_value) + assert isinstance(token, AccessToken) + assert token.to_primitive() == client.request.return_value - self.assertEqual(self.client.access_token, 'token123') + assert self.client.access_token == 'token123' def test_construct_with_access_token(self): client = predicthq.Client(access_token='token123') - self.assertEqual(client.access_token, 'token123') + assert client.access_token == 'token123' @with_config(OAUTH2_ACCESS_TOKEN='token123') def test_construct_with_access_token_config(self): client = predicthq.Client() - self.assertEqual(client.access_token, 'token123') + assert client.access_token == 'token123' diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..f349d11 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import, print_function + +import os +import pytest + +from predicthq.config import Config +from predicthq.exceptions import ConfigError + + +def test_defaults(): + config = Config() + assert config.ENDPOINT_URL == "https://api.predicthq.com" + assert config.OAUTH2_CLIENT_ID is None + assert config.OAUTH2_CLIENT_SECRET is None + assert config.OAUTH2_SCOPE is None + assert config.OAUTH2_ACCESS_TOKEN is None + assert config.LOGGING_LOG_LEVEL == "WARNING" + + +def test_defaults_from_locations(): + CONFIG_LOCATIONS = ( + os.path.join(os.path.dirname(__file__), 'fixtures', 'test.conf'), + os.path.join(os.path.dirname(__file__), 'fixtures', 'test2.conf') + ) + + config = Config(*CONFIG_LOCATIONS) + assert config.ENDPOINT_URL == "https://example.org" + assert config.OAUTH2_CLIENT_ID == "client_id" + assert config.OAUTH2_CLIENT_SECRET == "client_secret" + assert config.OAUTH2_SCOPE == "account events" + assert config.OAUTH2_ACCESS_TOKEN == "access_token" + assert config.LOGGING_LOG_LEVEL == "DEBUG" + + +def test_defaults_from_environment(): + os.environ["PREDICTHQ_ENDPOINT_URL"] = "https://example.org/endpoint/" + try: + config = Config(os.path.join(os.path.dirname(__file__), 'fixtures', 'test.conf')) + assert config.ENDPOINT_URL == "https://example.org/endpoint/" + assert config.OAUTH2_CLIENT_ID == "client_id" + assert config.OAUTH2_CLIENT_SECRET == "client_secret" + assert config.OAUTH2_SCOPE == "scope" + assert config.OAUTH2_ACCESS_TOKEN == "access_token" + assert config.LOGGING_LOG_LEVEL == "log_level" + finally: + del os.environ["PREDICTHQ_ENDPOINT_URL"] + + +def test_get_config(): + config = Config() + assert config.ENDPOINT_URL == "https://api.predicthq.com" + + with pytest.raises(ConfigError): + assert config.INVALID_CONFIG + + +def test_set_config(): + config = Config() + config.ENDPOINT_URL = "https://example.org" + assert config.ENDPOINT_URL == "https://example.org" + + with pytest.raises(ConfigError): + config.INVALID_CONFIG = "invalid" diff --git a/tox.ini b/tox.ini index ee31605..d0bcb1f 100644 --- a/tox.ini +++ b/tox.ini @@ -2,4 +2,9 @@ envlist = py27,py34,py35,py36,py37 [testenv] -commands = python setup.py nosetests +deps = -rrequirements.txt +commands = + pytest --cov=predicthq tests/ + +[pytest] +testpaths = tests