diff --git a/.gitignore b/.gitignore index d7bc619..2efa43d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ htmlcov/* .mr.developer.cfg .project .pydevproject +.eggs/ +.pytest_cache/ diff --git a/.travis.yml b/.travis.yml index dc6a65b..4514692 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,3 @@ deploy: all_branches: true after_success: - codecov -env: - global: - - secure: YE1otsTR6GHacdYjBV+VCh3Yajtfi9QSykl5B3vFPLKdHW9Wgjw3DUlZ+tIDPU6bgGWdy06Ry5eeIDFp/dG7IN7WkP25YfhaoLfApiQ59eggzJ6F/3MB0aDJRpYQrln7CPuVyOEdKPiuTiVEzrMzsaNvwe8jv1oRESQIic1mS7M= - - secure: eZj+KsmL6QgH5u9p/+YP6oSb73s6m0BnaCOve+rl6qZzysrrTZdUZZ0GqxbDJT/d9RpvyhYKOSHKzjUw2Q81w0AY75vO8H0xF5/kOnFRjlr09m5ltPVlId0NRJdpY41/DYZiikwvxSPi8lBFAMVqfLhCwlJo9lBXG6RVJPPricE= - - secure: e7KEMfeZSO+lurgPI9F5n5k14OKgXkD97n5XdNnZJ2CwGSyvM6fd0I36QSGEbjOXaUNE4MhmQK4Ca7+PDY1HIBGT7mUgcE8etyqyA2c8TEnicDQ44Tl9jXBpoO3JZOK8akh6ycxNXS4K0fZrn/NGDvsxj0MjVrUcE9hPW5JpDNM= - - secure: Hzk+d2xJGYOpIe/wjthZ6JUGGW7ujq33l4rdOaVyTKGUojZyG6o2hXD8RdHSSubEEo4CbW4LGwKZSwdD7C1GWrHhmzzsKd/oUE2czmGM8kOI8MRFY3jZ/gTeD79PDoCcbElpQUKpxoJOc4L9TymdSxTcQR7gP81WGrPR72+w9qs= diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1d112ce --- /dev/null +++ b/AUTHORS @@ -0,0 +1,27 @@ +Achimsen +Berend Kapelle +Berend Kapelle +Berend Kapelle +Boris Erdmann +Deniz Saner +Dennis Lutter +FelixWittmann +Fredrik Melander +Ivan Vokhmin +Jan van Esdonk +Jan van Esdonk +Joachim Penk +Martin Domke +Matthias Jacob +Michael Haller +Moritz Pein +Stefan Löwen +Stefan Richter +Stefan Richter +achimsen +berend +denvercoder9 +felix@nochoffen.de +fricklerhandwerk +fricklerhandwerk +holger diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..f10e220 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,305 @@ +CHANGES +======= + +* most of pep8 +* clean up of conftest +* added a test for get\_bank +* clean conftest, moved all catalog and header tests to own file +* making tests green +* BAAS-31: Revert SSL +* BAAS-31: Fix string +* BAAS-31 Fix litreal +* BAAS-31: Add new API fingerprint +* BAAS-31: Move env vars to travis directly +* fix \_\_unicode\_\_ that didn't return unicode strings +* fix indentation +* Remove weird legacy unicode stuff +* fix unicode issue also for python3 +* fix unicode error +* use example.com as test URL +* drop support for old pythons +* fix broken tests +* add v3 to api\_endpoint +* improve console\_demo.py output and remove hard coded account id +* fix instance check +* re-format +* remove unnecessary docstrings +* Remove unnecessary returns +* shape up docstrings +* remove in-code comments, shape up the docstrings, re-format +* unify formatting, flake8 +* remove STRING\_TYPES +* flake8 + +1.7.4 +----- + +* Revert "use python3 compatible http status\_code const" +* Revert "comply to all the py3 versions" +* comply to all the py3 versions +* use python3 compatible http status\_code const +* remove httplib requirement in docs to make it compatible to python 3.x +* this shouldn't be an instance eq check +* include error code in tests +* get error keys with get() +* add test case for new and old error format +* remove redundant error code +* demobank has more than one account +* add test for invalid language +* fix catalog endpoint and exception formatting +* add tests +* add language to service model +* remove numbers from test names +* handle errors more flexibly + +1.7.3 +----- + +* remove 2.6 from travis, skip flaky test for now + +1.7.2 +----- + +* remove python 2.6, added python 3.6 to setup.cfg +* add language parameter + +1.7.1 +----- + +* remove yield\_fixture +* use relative import and also test py36 + +1.7.0 +----- + +* fix import +* fix import +* fix import +* add new transaction fields +* DRY \`x\_or\_x\_id\` pattern +* factor out demo token +* flake8 +* fix description string for pypi +* factor out credentials +* get version string from installed package +* flake8 + +1.6.3 +----- + +* changed fingerprint in test config + +1.6.2 +----- + +* version bump to 1.6.2 +* update readme +* use pbr +* align user agent version with sdk version +* fix fingerprint listing in one string +* Update setup.py +* Update figo.py + +1.6.1 +----- + +* reword readme: environment variables + +1.6.0 +----- + +* bump version +* fix raising generic exception on task +* implement change requests +* remove debug output plus tiny code fixes +* add sync\_account function and tests +* improve comment +* not asserting a fixed value for number of services. More than 10 seems ok +* expect the right error code in test +* add generic error data to FigoPinException +* use complete unicode strings +* Implemented changes on #37 requested by @fricklerhandwerk +* #37 - Fixed Integration tests: Added \`90000\` as attribute for http related errors and changed \`assert len(services) == 28\` to \`assert len(services) == 27\` as this was apperently altered +* Removed pragma no cover +* Added \`code\` in init function and extended \`from\_dict\` class method +* Added error code to FigoException +* flake8: ignore docstring warnings +* return scope in user login response +* enable flake8 on tests and reformat +* skip tests based on client scope instead of credentials +* add categories to transactions +* implement lad1337's comments +* use SHA256 fingerprint for certificate pinning +* fix return type for banks catalog +* remove star import, fix most linter errors +* fix \`account\_id\` check and comments to sphinx +* fix indentation +* add test for wrong pin on postbank +* use error code instead of string when raising pin exception +* hotfix unicode error in logging +* add methods to get banks catalog and service login settings +* use generator instead dict comprehension, docstring to sphinx +* split test fixtures in \`api\` and \`staging\` (#29) +* fix typos in comments +* pass CLIENT\_ID and CLIENT\_SECRET env to tox +* remove reassignment of \`sync\_poll\_retry\` +* fix typos in readme +* add \`scope\` option to \`credential\_login()\` +* pass CLIENT\_ID and CLIENT\_SECRET env to tox +* get client credentials from env +* added current staging.figo.me fingerprint into FIGO\_SSL\_FINGERPRINT env variable +* Remove IDE spam from file-header +* Create CONTRIB.md +* added CLIENT\_ID, CLIENT\_SECRET and FIGO\_API\_ENDPOINT to travis.yml +* fix typos in readme +* make query generation even more readable +* don't use \`eval\` +* make query generation more readable +* use explicit kwargs in \`get\_task\_state()\` +* remove stray comment line +* added sort argument to \`get\_transactions\` method +* fix typo in error message +* remove \`HTTP\_SECURE\` option, use https always +* add API endpoint and SSL fingerprints to constructors + +1.5.8 +----- + +* update package metadata + +1.5.7 +----- + +* adjust test case values + +1.5.6 +----- + +* update pypi password +* minor typos in ready + +1.5.5 +----- + +* fixed sync\_poll\_retry behaviour, added description of alternative fingerprint and endpoint handling in ready +* support for multi ssl fingerprints and other endpoint +* added newest fingerprint +* removed empty line in .md +* added coverage badge, all badges now have the same styling +* working on codecoverage integration +* added codecov to travis config + +1.5.4 +----- + +* added travis secure password, some setup info, version bump for travis + +1.5.3 +----- + +* version push +* python3 bytes vs. string issue fixed, +* trying to to get rid of race conditions during travis parallel version runs +* migrated tests for models and session to pytest, reformatting and PEP8 +* switched to requests and pytest, removed some state in unit tests. There are some stageful tests remaining +* added python 3.5 to travis +* why does it always rain on me .. +* travis, again +* some travis ci changes and code formatting, typos and PEP8 +* error code formatting +* fixed tests for 2.6 and 2.7 +* print statements now python3 style, create user now uses a uuid as name to avoid race conditions during parallel testing +* more pep8 stuff, switched to nose style skip for python 2.6 compatibility + +1.5.2 +----- + +* fixed pep8 + +1.5.1 +----- + +* fixed all other errors, nose tests are now passing +* adjusted Exception raising to new error message format +* Add "visited" and "additional\_info" to Transaction object + +1.4.1 +----- + +* bump version to 1.4.1 +* added new fingerprint for api.figo.me +* added detailed description to Payment Class +* updated more docstrings +* updated docstrings +* updated doc strings (reformatting and small fixes) +* added available tan schemes to account model +* changed version +* added add\_user\_and\_login and add\_account\_and\_sync +* added missing api calls, models and test cases +* Adding missing bank\_id to bank +* Remove some typos (non critical) and a debug output +* remove stray space (flake8) +* python 3 compat (and v1.3.1) +* move to new figo.io SDK API level +* increment to v1.2.1 +* adding tox.ini to filter out flake8 noise +* flake8 fixes +* adding flake8 to travis CI +* increment version +* adding demo apps +* python 2.6 compat +* fix a variety of typos +* adding pypi badge to readme +* updating SSL fingerprints, clean up and adding tests +* Fix a variety of minor issues +* Fixing missing parsing of value date in transaction. Bump to 1.1.3 +* Incrementing version and updating trove classifiers +* Fix relative import and usage of iteritems in spirit of Py3k +* Adding dependency installation to travis ci and adding nose as test dependency +* Use correct setuptools option +* Fix issue with Py2.6 +* Python 3 compatibility +* Updating SSL fingerprints and moving to new API DNS name. Incrementing Version +* pep8 + to string methods +* new api endpoints +* webhook notification parser +* webhook notification parser +* webhook notification parser +* add python-dateutil as dependency +* fix unittests +* fix unittests +* fix unittests +* refactoring and add new api endpoints +* revert eda0c2bbce273f1608ef4e9cfc24a0b9c0302e39 and fix it right +* bump version +* now balance is delivered inline and the helper method is no longer necessary +* adding SSL certificate fingerprints +* Increment version +* Fix typos +* Increment version +* PEP8 +* Adding str representations to models +* Fix missing import of FigoException in figo/models.py +* Building on top of PR to include the error code into output as well +* Add string representation for FigoException +* Add account type 'Cash book' +* Fix unit tests for Python 2.6 +* Add OAuth example +* Handle more HTTP error codes +* Fix unit tests +* some more tests and clean up +* use correct URLs for sync +* Removing strange property decorator +* Increment version +* typo +* Python 2.6 and below compat +* Adding certificate validation +* Adding build status image +* Adding PyPy to travis +* Python 3 compat +* adding travis +* change doc theme +* add login example +* Using code-block instead of code to make rtd happy +* first version of the python binding +* Initial commit diff --git a/README.md b/README.md index 42d13d6..6bc871a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-figo [![Build Status](https://img.shields.io/travis/figo-connect/python-figo.svg?style=flat-square)](https://travis-ci.org/figo-connect/python-figo) [![PyPi Version](http://img.shields.io/pypi/v/python-figo.svg?style=flat-square)](https://pypi.python.org/pypi/python-figo) [![Code Coverage](https://img.shields.io/codecov/c/github/figo-connect/python-figo.svg?style=flat-square)](https://codecov.io/github/figo-connect/python-figo) -Python bindings for the figo Connect API: http://docs.figo.io +Python bindings for the figo Connect API: http://docs.figo.io/v3/ # Usage diff --git a/figo/credentials.py b/figo/credentials.py index f59d5b6..5a5efc2 100644 --- a/figo/credentials.py +++ b/figo/credentials.py @@ -3,7 +3,7 @@ DEMO_CREDENTIALS = { 'client_id': 'C-9rtYgOP3mjHhw0qu6Tx9fgk9JfZGmbMqn-rnDZnZwI', 'client_secret': 'Sv9-vNfocFiTe_NoMRkvNLe_jRRFeESHo8A0Uhyp7e28', - 'api_endpoint': 'https://api.figo.me/v3', + 'api_endpoint': 'https://api.figo.me', # string containing comma-separated list of SSL fingerprints 'ssl_fingerprints': ('79:B2:A2:93:00:85:3B:06:92:B1:B5:F2:24:79:48:58:' '3A:A5:22:0F:C5:CD:E9:49:9A:C8:45:1E:DB:E0:DA:50'), diff --git a/figo/figo.py b/figo/figo.py index ed4464f..55c74aa 100644 --- a/figo/figo.py +++ b/figo/figo.py @@ -25,7 +25,6 @@ from figo.models import Notification from figo.models import Payment from figo.models import PaymentProposal -from figo.models import ProcessToken from figo.models import Security from figo.models import Service from figo.models import TaskState @@ -119,17 +118,18 @@ def _request_api(self, path, data=None, method="GET"): session.headers.update(self.headers) for fingerprint in self.fingerprints: - session.mount(self.api_endpoint, FingerprintAdapter(fingerprint)) + session.mount(self.api_endpoint, FingerprintAdapter(fingerprint.lower())) try: response = session.request(method, complete_path, json=data) - except SSLError as fingerprint_error: + except SSLError: logging.warn('Fingerprint "%s" was invalid', fingerprint) else: break finally: session.close() else: - raise fingerprint_error + raise SSLError + if 200 <= response.status_code < 300 or self._has_error(response.json()): if response.text == '': @@ -389,9 +389,12 @@ def convert_refresh_token(self, refresh_token): if refresh_token[0] != "R": raise Exception("Invalid refresh token") - response = self._request_api("/auth/token", data={ + + data = { 'refresh_token': refresh_token, 'redirect_uri': self.redirect_uri, - 'grant_type': 'refresh_token'}, method="POST") + 'grant_type': 'refresh_token'} + response = self._request_api("/auth/token", data=data, method="POST") + if 'error' in response: raise FigoException.from_dict(response) @@ -744,18 +747,6 @@ def get_service_login_settings(self, country_code, item_id): return self._query_api_object(LoginSettings, "/rest/catalog/services/%s/%s" % (country_code, item_id)) - def set_account_sort_order(self, accounts): - """Set the sort order of the user's accounts. - - Args: - accounts: List of Accounts - - Returns: - empty response if successful - """ - data = {"accounts": [{"account_id": account.account_id} for account in accounts]} - return self._request_with_exception("/rest/accounts", data, "POST") - @property def notifications(self): """An array of `Notification` objects, one for each registered notification.""" @@ -926,12 +917,18 @@ def get_payment_proposals(self): def start_task(self, task_token_obj): """Start the given task. + note:: Deprecated in 3.0.0 + `start_task` will be removed in 3.1.0, it is no longer necessary. Task will start + immediately on creation if creation is not deferred. For 3.0.0 start_task will call + task progress once to simulate old behavior for older API versions. + Args: task_token_obj: TaskToken object of the task to start """ - return self._request_with_exception("/task/start?id=%s" % task_token_obj.task_token) + self.get_task_state(task_token_obj) - def get_task_state(self, task_token, pin=None, continue_=None, save_pin=None, response=None): + def get_task_state(self, task_token_obj, pin=None, continue_=None, save_pin=None, + response=None): """Return the progress of the given task. The kwargs are used to submit additional content for the task. @@ -948,10 +945,10 @@ def get_task_state(self, task_token, pin=None, continue_=None, save_pin=None, re Returns: TaskState: Object that indicates the current status of the queried task """ - logger.debug('Geting task state for: %s', task_token) + logger.debug('Getting task state for: %s', task_token_obj) data = { - "id": task_token.task_token, + "id": task_token_obj.task_token, "pin": pin, "continue": continue_, "save_pin": save_pin, @@ -961,7 +958,7 @@ def get_task_state(self, task_token, pin=None, continue_=None, save_pin=None, re data = dict((k, v) for k, v in data.items() if v is not None) # noqa, py26 compatibility return self._query_api_object(TaskState, - "/task/progress?id=%s" % task_token.task_token, + "/task/progress?id=%s" % task_token_obj.task_token, data, "POST") def cancel_task(self, task_token_obj): @@ -975,22 +972,6 @@ def cancel_task(self, task_token_obj): data={"id": task_token_obj.task_token}, method="POST") - def start_process(self, process_token): - """Start the given process. - - Args: - process_token: ProcessToken object for the process to start - """ - return self._request_with_exception("/process/start?id=%s" % process_token.process_token) - - def create_process(self, process): - """Create a new process to be executed by the user. Returns a process token. - - Args: - process: Process object which will be sent to the API - """ - return self._query_api_object(ProcessToken, "/client/process", process.dump(), "POST") - @property def transactions(self): """An array of `Transaction` objects, one for each transaction of the user.""" @@ -1144,27 +1125,6 @@ def modify_user_securities(self, visited=None): """ return self._request_with_exception("/rest/securities", {"visited": visited}, "PUT") - def modify_transaction(self, account_or_account_id, transaction_or_transaction_id, - visited=None): - """Modify a specific transaction. - - Args: - account_or_account_id: account to be modified or its ID - transaction_or_transaction_id: Transactions or its ID to be modified - visited: new value of the visited field for the transaction - - Returns: - Nothing if the request was successful - """ - if isinstance(account_or_account_id, Account): - account_or_account_id = account_or_account_id.account_id - if isinstance(transaction_or_transaction_id, Transaction): - transaction_or_transaction_id = transaction_or_transaction_id.transaction_id - - query = "/rest/accounts/{0}/transactions/{1}".format(account_or_account_id, - transaction_or_transaction_id) - return self._query_api_object(Transaction, query, {"visited": visited}, "PUT") - def modify_account_transactions(self, account_or_account_id, visited=None): """Modify all transactions of a specific account. diff --git a/tests/conftest.py b/tests/conftest.py index b72fdd7..affd412 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,10 @@ import pytest import uuid +import time from logging import basicConfig from figo.credentials import CREDENTIALS -from figo.credentials import DEMO_CREDENTIALS -from figo.credentials import DEMO_TOKEN from figo import FigoConnection from figo import FigoSession @@ -14,7 +13,12 @@ PASSWORD = 'some_words' -@pytest.fixture(scope='session') +@pytest.fixture(scope='module') +def new_user_id(): + return "{0}testuser@example.com".format(uuid.uuid4()) + + +@pytest.fixture(scope='module') def figo_connection(): return FigoConnection(CREDENTIALS['client_id'], CREDENTIALS['client_secret'], @@ -23,12 +27,7 @@ def figo_connection(): fingerprints=CREDENTIALS['ssl_fingerprints']) -@pytest.fixture -def new_user_id(): - return "{0}testuser@example.com".format(uuid.uuid4()) - - -@pytest.fixture +@pytest.fixture(scope='module') def figo_session(figo_connection, new_user_id): figo_connection.add_user("Test", new_user_id, PASSWORD) response = figo_connection.credential_login(new_user_id, PASSWORD) @@ -39,7 +38,6 @@ def figo_session(figo_connection, new_user_id): 'accounts=rw', 'transactions=rw', 'user=rw', - 'categorization=rw', 'create_user', ] @@ -48,19 +46,44 @@ def figo_session(figo_connection, new_user_id): session = FigoSession(response['access_token']) + task_token = session.add_account("de", ("figo", "figo"), "90090042") + state = session.get_task_state(task_token) + + while not (state.is_ended or state.is_erroneous): + state = session.get_task_state(task_token) + time.sleep(2) + assert not state.is_erroneous + yield session session.remove_user() @pytest.fixture(scope='module') -def demo_session(): - # TODO(Valentin): we need to run `test_session` (both read-only) against production API - # using demo credentials, since there is no adequate client or data available - # on `staging`. we could: - # - drop these tests entirely and lose quite some code coverage - # - replace by write-then-read tests which cannot be run on external PRs - # - create a non-expiring demo session on `staging` - return FigoSession(DEMO_TOKEN, - api_endpoint=DEMO_CREDENTIALS['api_endpoint'], - fingerprints=DEMO_CREDENTIALS['ssl_fingerprints']) +def account_ids(figo_session): + accs = figo_session.accounts + + yield [a.account_id for a in accs] + + +@pytest.fixture(scope='module') +def giro_account(figo_session): + # returns the first account from the demo bank that is of type "Girokonto" + # and asserts there is at least one + accs = figo_session.accounts + giro_accs = [a for a in accs if a.type == "Giro account"] + assert len(giro_accs) >= 1 + + yield giro_accs[0] + + +@pytest.fixture(scope='module') +def access_token(figo_connection, new_user_id): + figo_connection.add_user("Test", new_user_id, PASSWORD) + response = figo_connection.credential_login(new_user_id, PASSWORD) + access_token = response['access_token'] + + yield access_token + + session = FigoSession(access_token) + session.remove_user() diff --git a/tests/test_catalog_and_language.py b/tests/test_catalog_and_language.py new file mode 100644 index 0000000..94f427e --- /dev/null +++ b/tests/test_catalog_and_language.py @@ -0,0 +1,61 @@ +import pytest + +from figo import FigoException +from figo import FigoSession +from figo.models import Service +from figo.models import LoginSettings + +CREDENTIALS = ["figo", "figo"] +BANK_CODE = "90090042" +CLIENT_ERROR = 1000 + + +@pytest.mark.parametrize('language', ['de', 'en']) +def test_get_catalog_en(access_token, language): + figo_session = FigoSession(access_token) + figo_session.language = language + catalog = figo_session.get_catalog() + for bank in catalog['banks']: + assert bank.language == language + + +def test_get_catalog_invalid_language(access_token): + figo_session = FigoSession(access_token) + figo_session.language = 'xy' + with pytest.raises(FigoException) as e: + figo_session.get_catalog() + assert e.value.code == CLIENT_ERROR + + +def test_get_supported_payment_services(access_token): + figo_session = FigoSession(access_token) + services = figo_session.get_supported_payment_services("de") + assert len(services) > 10 # this a changing value, this tests that at least some are returned + assert isinstance(services[0], Service) + + +# XXX(Valentin): Catalog needs `accounts=rw`, so it doesn't work with the demo session. +# Sounds silly at first, but actually there is no point to view the catalog if +# you can't add accounts. +def test_get_catalog(access_token): + figo_session = FigoSession(access_token) + catalog = figo_session.get_catalog() + assert len(catalog) == 2 + + +def test_get_login_settings(access_token): + figo_session = FigoSession(access_token) + login_settings = figo_session.get_login_settings("de", BANK_CODE) + assert isinstance(login_settings, LoginSettings) + assert login_settings.advice + assert login_settings.credentials + + +def test_set_unset_language(access_token): + figo_session = FigoSession(access_token) + assert figo_session.language is None + figo_session.language = 'de' + assert figo_session.language == 'de' + figo_session.language = '' + assert figo_session.language is None + figo_session.language = 'de' diff --git a/tests/test_models.py b/tests/test_models.py index 993744e..0b9f2bd 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -27,7 +27,7 @@ HTTP_NOT_ACCEPTABLE = 406 -def test_create_account_from_dict(demo_session): +def test_create_account_from_dict(figo_session): data = { "account_id": "A1.1", "name": "Girokonto", @@ -60,30 +60,30 @@ def test_create_account_from_dict(demo_session): "monthly_spending_limit": 0.0 } } - account = Account.from_dict(demo_session, data) + account = Account.from_dict(figo_session, data) assert isinstance(account, Account) -def test_create_bank_contact_from_dict(demo_session): +def test_create_bank_contact_from_dict(figo_session): data = {"bank_id": "B1.1", "sepa_creditor_id": "DE67900900424711951500", "save_pin": True} - bank_contact = BankContact.from_dict(demo_session, data) + bank_contact = BankContact.from_dict(figo_session, data) assert isinstance(bank_contact, BankContact) -def test_create_account_balance_from_dict(demo_session): +def test_create_account_balance_from_dict(figo_session): data = { "balance": 3250.30, "balance_date": "2013-09-11T00:00:00.000Z", "credit_line": 0.0, "monthly_spending_limit": 0.0 } - account_balance = AccountBalance.from_dict(demo_session, data) + account_balance = AccountBalance.from_dict(figo_session, data) assert isinstance(account_balance, AccountBalance) -def test_create_payment_from_dict(demo_session): +def test_create_payment_from_dict(figo_session): data = { "account_id": "A1.1", "account_number": "4711951501", @@ -105,11 +105,11 @@ def test_create_payment_from_dict(demo_session): "text_key_extension": 0, "type": "Transfer" } - payment = Payment.from_dict(demo_session, data) + payment = Payment.from_dict(figo_session, data) assert isinstance(payment, Payment) -def test_create_transaction_from_dict(demo_session): +def test_create_transaction_from_dict(figo_session): data = { "account_id": "A1.1", "account_number": "4711951501", @@ -129,11 +129,11 @@ def test_create_transaction_from_dict(demo_session): "value_date": "2013-04-11T12:00:00.000Z", "visited": True } - transaction = Transaction.from_dict(demo_session, data) + transaction = Transaction.from_dict(figo_session, data) assert isinstance(transaction, Transaction) -def test_create_transaction_with_categories(demo_session): +def test_create_transaction_with_categories(figo_session): data = { "account_id": "A1.1", "account_number": "4711951501", @@ -165,25 +165,25 @@ def test_create_transaction_with_categories(demo_session): "value_date": "2013-04-11T12:00:00.000Z", "visited": True } - transaction = Transaction.from_dict(demo_session, data) + transaction = Transaction.from_dict(figo_session, data) assert hasattr(transaction, 'categories') for category in transaction.categories: assert isinstance(category, Category) assert hasattr(category, 'id') -def test_create_notification_from_dict(demo_session): +def test_create_notification_from_dict(figo_session): data = { "notification_id": "N1.7", "notify_uri": "https://api.figo.me/callback", "observe_key": "/rest/transactions?include_pending=0", "state": "cjLaN3lONdeLJQH3" } - notification = Notification.from_dict(demo_session, data) + notification = Notification.from_dict(figo_session, data) assert isinstance(notification, Notification) -def test_create_sync_status_from_dict(demo_session): +def test_create_sync_status_from_dict(figo_session): data = { "code": -1, "message": "Cannot load credential 8f084858-e1c6-4642-87f8-540b530b6e0f: " @@ -191,11 +191,11 @@ def test_create_sync_status_from_dict(demo_session): "success_timestamp": "2013-09-11T00:00:00.000Z", "sync_timestamp": "2014-07-09T10:04:40.000Z" } - sync_status = SynchronizationStatus.from_dict(demo_session, data) + sync_status = SynchronizationStatus.from_dict(figo_session, data) assert isinstance(sync_status, SynchronizationStatus) -def test_create_user_from_dict(demo_session): +def test_create_user_from_dict(figo_session): data = { "address": { "city": "Berlin", @@ -214,11 +214,11 @@ def test_create_user_from_dict(demo_session): "user_id": "U12345", "verified_email": True } - user = User.from_dict(demo_session, data) + user = User.from_dict(figo_session, data) assert isinstance(user, User) -def test_create_service_from_dict(demo_session): +def test_create_service_from_dict(figo_session): data = { "additional_icons": { "48x48": "https://api.figo.me/assets/images/accounts/default-small@2x.png", @@ -235,11 +235,11 @@ def test_create_service_from_dict(demo_session): "current_language": "de", }, } - service = Service.from_dict(demo_session, data) + service = Service.from_dict(figo_session, data) assert isinstance(service, Service) -def test_create_login_settings_from_dict(demo_session): +def test_create_login_settings_from_dict(figo_session): data = { "additional_icons": { "48x48": "https://api.figo.me/assets/images/accounts/default-small@2x.png", @@ -260,28 +260,28 @@ def test_create_login_settings_from_dict(demo_session): "icon": "https://api.figo.me/assets/images/accounts/demokonto.png", "supported": True } - login_settings = LoginSettings.from_dict(demo_session, data) + login_settings = LoginSettings.from_dict(figo_session, data) assert isinstance(login_settings, LoginSettings) -def test_create_credential_from_dict(demo_session): +def test_create_credential_from_dict(figo_session): data = { "label": "Benutzername" } - credential = Credential.from_dict(demo_session, data) + credential = Credential.from_dict(figo_session, data) assert isinstance(credential, Credential) -def test_create_task_token_from_dict(demo_session): +def test_create_task_token_from_dict(figo_session): data = { "task_token": "YmB-BtvbWufLnbwgAVfP7XfLatwhrtu0sATfnZNR7LGP-aLXiZ7BKzLdZI--EqEPnwh_" "h6mCxToLEBhtA7LVd4uM4gTcZG8F6UJs47g6kWJ0" } - task_token = TaskToken.from_dict(demo_session, data) + task_token = TaskToken.from_dict(figo_session, data) assert isinstance(task_token, TaskToken) -def test_create_task_state_from_dict(demo_session): +def test_create_task_state_from_dict(figo_session): data = { "account_id": "A1.2", "is_ended": False, @@ -290,32 +290,32 @@ def test_create_task_state_from_dict(demo_session): "is_waiting_for_response": False, "message": "Getting balance..." } - task_state = TaskState.from_dict(demo_session, data) + task_state = TaskState.from_dict(figo_session, data) assert isinstance(task_state, TaskState) -def test_create_challenge_from_dict(demo_session): +def test_create_challenge_from_dict(figo_session): data = { "title": "Pin Eingabe", "label": "pin", "format": "Text", "data": "dummy" } - challenge = Challenge.from_dict(demo_session, data) + challenge = Challenge.from_dict(figo_session, data) assert isinstance(challenge, Challenge) -def test_create_payment_proposal_from_dict(demo_session): +def test_create_payment_proposal_from_dict(figo_session): data = { "account_number": "DE67900900424711951500", "bank_code": "DEMODE01", "name": "Girokonto" } - payment_proposal = PaymentProposal.from_dict(demo_session, data) + payment_proposal = PaymentProposal.from_dict(figo_session, data) assert isinstance(payment_proposal, PaymentProposal) -def test_create_process_from_dict(demo_session): +def test_create_process_from_dict(figo_session): data = { "email": "process.1@demo.figo.io", "password": "figofigo", @@ -339,11 +339,11 @@ def test_create_process_from_dict(demo_session): } ] } - process = Process.from_dict(demo_session, data) + process = Process.from_dict(figo_session, data) assert isinstance(process, Process) -def test_create_process_step_from_dict(demo_session): +def test_create_process_step_from_dict(figo_session): data = { "options": { "account_number": "100100100", @@ -356,11 +356,11 @@ def test_create_process_step_from_dict(demo_session): }, "type": "figo.steps.payment.submit" } - process_step = ProcessStep.from_dict(demo_session, data) + process_step = ProcessStep.from_dict(figo_session, data) assert isinstance(process_step, ProcessStep) -def test_create_process_options_from_dict(demo_session): +def test_create_process_options_from_dict(figo_session): data = { "account_number": "100100100", "amount": 99, @@ -370,20 +370,20 @@ def test_create_process_options_from_dict(demo_session): "purpose": "Yearly contribution", "type": "Transfer" } - process_options = ProcessOptions.from_dict(demo_session, data) + process_options = ProcessOptions.from_dict(figo_session, data) assert isinstance(process_options, ProcessOptions) -def test_create_process_token_from_dict(demo_session): +def test_create_process_token_from_dict(figo_session): data = { "task_token": "YmB-BtvbWufLnbwgAVfP7XfLatwhrtu0sATfnZNR7LGP-aLXiZ7BKzLdZI--EqEPnwh_" "h6mCxToLEBhtA7LVd4uM4gTcZG8F6UJs47g6kWJ0" } - task_token = TaskToken.from_dict(demo_session, data) + task_token = TaskToken.from_dict(figo_session, data) assert isinstance(task_token, TaskToken) -def test_create_security_from_dict(demo_session): +def test_create_security_from_dict(figo_session): data = { "account_id": "A1.4", "amount": 32.78, @@ -398,7 +398,7 @@ def test_create_security_from_dict(demo_session): "security_id": "S1.1", "trade_timestamp": "2014-07-29 15:00:00" } - security = Security.from_dict(demo_session, data) + security = Security.from_dict(figo_session, data) assert isinstance(security, Security) diff --git a/tests/test_session.py b/tests/test_session.py index c6b5c04..733b62e 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -7,89 +7,80 @@ from figo import FigoException from figo.models import Notification from figo.models import Payment -from figo.models import Process -from figo.models import ProcessToken from figo.models import TaskToken -def test_set_unset_language(demo_session): - assert demo_session.language is None - demo_session.language = 'de' - assert demo_session.language == 'de' - demo_session.language = '' - assert demo_session.language is None +def test_get_account(figo_session, account_ids): + account_id = account_ids[0] + account = figo_session.get_account(account_id) + assert account.account_id == account_id -def test_get_account(demo_session): - account = demo_session.get_account("A1.2") - assert account.account_id == "A1.2" +def test_get_account_tan_schemes(figo_session, giro_account): + account = figo_session.get_account(giro_account.account_id) + assert len(account.supported_tan_schemes) > 0 -def test_get_account_tan_schemes(demo_session): - account = demo_session.get_account("A1.1") - assert len(account.supported_tan_schemes) == 4 - - -def test_get_account_balance(demo_session): +def test_get_account_balance(figo_session, giro_account): # account sub-resources - balance = demo_session.get_account_balance(demo_session.get_account("A1.2")) + balance = figo_session.get_account_balance(figo_session.get_account(giro_account.account_id)) assert balance.balance assert balance.balance_date -def test_get_account_transactions(demo_session): - transactions = demo_session.get_account("A1.2").transactions +def test_get_account_transactions(figo_session, giro_account): + transactions = figo_session.get_account(giro_account.account_id).transactions assert len(transactions) > 0 -def test_get_account_payments(demo_session): - payments = demo_session.get_account("A1.2").payments +def test_get_account_payments(figo_session, giro_account): + payments = figo_session.get_account(giro_account.account_id).payments assert len(payments) >= 0 -def test_get_global_transactions(demo_session): - transactions = demo_session.transactions +def test_get_global_transactions(figo_session): + transactions = figo_session.transactions assert len(transactions) > 0 -def test_get_global_payments(demo_session): - payments = demo_session.payments +def test_get_global_payments(figo_session): + payments = figo_session.payments assert len(payments) >= 0 -def test_get_notifications(demo_session): - notifications = demo_session.notifications +def test_get_notifications(figo_session): + notifications = figo_session.notifications assert len(notifications) >= 0 -def test_get_missing_account(demo_session): +def test_get_missing_account(figo_session): with pytest.raises(FigoException): - demo_session.get_account("A1.22") + figo_session.get_account("A1.22") -def test_error_handling(demo_session): +def test_error_handling(figo_session): with pytest.raises(FigoException): - demo_session.get_sync_url('qwe', 'qew') + figo_session.get_sync_url('qwe', 'qew') -def test_sync_uri(demo_session): - demo_session.get_sync_url('some_state', 'http://example.com') +def test_sync_uri(figo_session): + figo_session.get_sync_url('some_state', 'http://example.com') -def test_get_mail_from_user(demo_session): - assert demo_session.user.email == "demo@figo.me" +def test_get_mail_from_user(figo_session): + assert figo_session.user.email.endswith("testuser@example.com") @pytest.mark.skip(reason="race condition on travis") -def test_create_update_delete_notification(demo_session): +def test_create_update_delete_notification(figo_session): """ This test sometimes fails, when run for different versions in parallel, e.g. on travis It happens because the notification id will always be the same for the demo client. This will be solved with running tests against an enhanced sandbox. """ state_version = "V{0}".format(platform.python_version()) - added_notification = demo_session.add_notification( - Notification.from_dict(demo_session, dict(observe_key="/rest/transactions", + added_notification = figo_session.add_notification( + Notification.from_dict(figo_session, dict(observe_key="/rest/transactions", notify_uri="http://figo.me/test", state=state_version))) @@ -101,24 +92,24 @@ def test_create_update_delete_notification(demo_session): print("id: {0}, {1}".format(added_notification.notification_id, added_notification.state)) added_notification.state = state_version + "_modified" - modified_notification = demo_session.modify_notification(added_notification) + modified_notification = figo_session.modify_notification(added_notification) assert modified_notification.observe_key == "/rest/transactions" assert modified_notification.notify_uri == "http://figo.me/test" assert modified_notification.state == state_version + "_modified" print("id: {0}, {1}".format(modified_notification.notification_id, modified_notification.state)) - demo_session.remove_notification(modified_notification.notification_id) + figo_session.remove_notification(modified_notification.notification_id) with pytest.raises(FigoException): - deleted_notification = demo_session.get_notification(modified_notification.notification_id) + deleted_notification = figo_session.get_notification(modified_notification.notification_id) print("id: {0}, {1}".format( deleted_notification.notification_id, deleted_notification.state)) print("#"*10) -def test_create_update_delete_payment(demo_session): - added_payment = demo_session.add_payment( - Payment.from_dict(demo_session, dict(account_id="A1.1", +def test_create_update_delete_payment(figo_session, giro_account): + added_payment = figo_session.add_payment( + Payment.from_dict(figo_session, dict(account_id=giro_account.account_id, type="Transfer", account_number="4711951501", bank_code="90090042", @@ -126,116 +117,61 @@ def test_create_update_delete_payment(demo_session): purpose="Thanks for all the fish.", amount=0.89))) - assert added_payment.account_id, "A1.1" + assert added_payment.account_id, giro_account.account_id assert added_payment.bank_name == "Demobank" assert added_payment.amount == 0.89 added_payment.amount = 2.39 - modified_payment = demo_session.modify_payment(added_payment) + modified_payment = figo_session.modify_payment(added_payment) assert modified_payment.payment_id == added_payment.payment_id - assert modified_payment.account_id == "A1.1" + assert modified_payment.account_id == giro_account.account_id assert modified_payment.bank_name == "Demobank" assert modified_payment.amount == 2.39 - demo_session.remove_payment(modified_payment) - with pytest.raises(FigoException): - demo_session.get_payment(modified_payment.account_id, modified_payment.payment_id) - - -def test_set_bank_account_order(demo_session): - # Access token with accounts=rw needed - accounts = [demo_session.get_account("A1.2"), demo_session.get_account("A1.1")] - with pytest.raises(FigoException): - demo_session.set_account_sort_order(accounts) - - -def test_get_supported_payment_services(demo_session): - # Access token with accounts=rw needed - with pytest.raises(FigoException): - demo_session.get_supported_payment_services("de") - - -def test_get_login_settings(demo_session): - # Access token with accounts=rw needed - with pytest.raises(FigoException): - demo_session.get_login_settings("de", "90090042") - - -def test_setup_new_bank_account(demo_session): - # Access token with accounts=rw needed + figo_session.remove_payment(modified_payment) with pytest.raises(FigoException): - demo_session.add_account("de", ["figo", "figo"], "90090042") + figo_session.get_payment(modified_payment.account_id, modified_payment.payment_id) -def test_modify_a_transaction(demo_session): - # Access token with transactions=rw needed - with pytest.raises(FigoException): - demo_session.modify_transaction("A1.1", "T1.24", False) - - -def test_modify_all_transactions_of_account(demo_session): - # Access token with transactions=rw needed - with pytest.raises(FigoException): - demo_session.modify_account_transactions("A1.1", visited=False) - - -def test_modify_all_transactions(demo_session): - # Access token with transactions=rw needed - with pytest.raises(FigoException): - demo_session.modify_user_transactions(visited=False) +def test_delete_transaction(figo_session, giro_account): + transaction = giro_account.transactions[0] + figo_session.delete_transaction(giro_account.account_id, transaction.transaction_id) -def test_delete_transaction(demo_session): - # Access token with transactions=rw needed - with pytest.raises(FigoException): - demo_session.delete_transaction("A1.1", "T1.24") - +def test_get_payment_proposals(figo_session): + proposals = figo_session.get_payment_proposals() + assert len(proposals) >= 1 -def test_get_payment_proposals(demo_session): - proposals = demo_session.get_payment_proposals() - assert len(proposals) == 12 - -def test_start_task(demo_session): +def test_start_task(figo_session): # Valid task token needed - task_token = TaskToken(demo_session) + task_token = TaskToken(figo_session) task_token.task_token = "invalidTaskToken" with pytest.raises(FigoException): - demo_session.start_task(task_token) + figo_session.start_task(task_token) -def test_poll_task_state(demo_session): +def test_poll_task_state(figo_session): # Valid task token needed - task_token = TaskToken(demo_session) + task_token = TaskToken(figo_session) task_token.task_token = "invalidTaskToken" with pytest.raises(FigoException): - demo_session.get_task_state(task_token) + figo_session.get_task_state(task_token) -def test_cancel_task(demo_session): +def test_cancel_task(figo_session): # Valid task token needed - task_token = TaskToken(demo_session) + task_token = TaskToken(figo_session) task_token.task_token = "invalidTaskToken" with pytest.raises(FigoException): - demo_session.cancel_task(task_token) + figo_session.cancel_task(task_token) -def test_start_process(demo_session): - # Valid process token needed - process_token = ProcessToken(demo_session) - process_token.process_token = "invalidProcessToken" - with pytest.raises(FigoException): - demo_session.start_process(process_token) +def test_sync_account(figo_session): + assert figo_session.sync_account(state="qweqwe") -def test_create_process(demo_session): - # Access token with process=rw needed - process = Process(demo_session, email="demo@demo.de", password="figo", - state="qwer", steps=["not_valid"]) - - with pytest.raises(FigoException): - demo_session.create_process(process) - +def test_get_bank(figo_session, giro_account): -def test_sync_account(demo_session): - assert demo_session.sync_account(state="qweqwe") + bank = figo_session.get_bank(giro_account.bank_id) + assert bank.bank_id diff --git a/tests/test_writing_methods.py b/tests/test_writing_methods.py index 24b8395..b2dc6d3 100644 --- a/tests/test_writing_methods.py +++ b/tests/test_writing_methods.py @@ -7,8 +7,7 @@ from figo import FigoException from figo import FigoPinException -from figo.models import LoginSettings -from figo.models import Service + from figo.models import TaskState from figo.models import TaskToken @@ -18,40 +17,6 @@ CLIENT_ERROR = 1000 -# XXX(Valentin): Catalog needs `accounts=rw`, so it doesn't work with the demo session. -# Sounds silly at first, but actually there is no point to view the catalog if -# you can't add accounts. -def test_get_catalog(figo_session): - catalog = figo_session.get_catalog() - assert len(catalog) == 2 - - -@pytest.mark.parametrize('language', ['de', 'en']) -def test_get_catalog_en(figo_session, language): - figo_session.language = language - catalog = figo_session.get_catalog() - for bank in catalog['banks']: - assert bank.language == language - - -def test_get_catalog_invalid_language(figo_session): - figo_session.language = 'xy' - with pytest.raises(FigoException) as e: - figo_session.get_catalog() - assert e.value.code == CLIENT_ERROR - - -def test_get_supported_payment_services(figo_session): - services = figo_session.get_supported_payment_services("de") - assert len(services) > 10 # this a changing value, this tests that at least some are returned - assert isinstance(services[0], Service) - - -def test_get_login_settings(figo_session): - login_settings = figo_session.get_login_settings("de", BANK_CODE) - assert isinstance(login_settings, LoginSettings) - - def test_add_account(figo_session): token = figo_session.add_account("de", CREDENTIALS, BANK_CODE) assert isinstance(token, TaskToken) @@ -66,7 +31,6 @@ def test_add_account_and_sync_wrong_pin(figo_session): try: with pytest.raises(FigoException): figo_session.add_account_and_sync("de", wrong_credentials, BANK_CODE) - assert len(figo_session.accounts) == 0 except FigoException as figo_exception: # BBB(Valentin): prevent demo account from complaining - it returns no code on error if "Please use demo account credentials" not in figo_exception.error_description: @@ -114,11 +78,10 @@ def test_add_account_and_sync_wrong_pin_postbank(figo_session): with pytest.raises(FigoPinException) as e: figo_session.add_account_and_sync("de", None, None) assert e.value.code == 10000 - assert len(figo_session.accounts) == 0 @pytest.mark.skip(reason="test is flaky as hell and should be rewritten completely") -def test_051_add_account_and_sync_wrong_and_correct_pin(figo_session): +def test_add_account_and_sync_wrong_and_correct_pin(figo_session): wrong_credentials = [CREDENTIALS[0], "123456"] figo_session.sync_poll_retry = 100 try: