diff --git a/figo/figo.py b/figo/figo.py index ca3b6eb..7ef78a7 100644 --- a/figo/figo.py +++ b/figo/figo.py @@ -316,6 +316,7 @@ def credential_login(self, username, password, scope=None): Returns: Dictionary which contains an access token and a refresh token. """ + data = {"grant_type": "password", "username": username, "password": password} @@ -327,9 +328,12 @@ def credential_login(self, username, password, scope=None): if 'error' in response: raise FigoException.from_dict(response) - return {'access_token': response['access_token'], - 'refresh_token': response['refresh_token'] if 'refresh_token' in response else None, - 'expires': datetime.now() + timedelta(seconds=response['expires_in'])} + return { + 'access_token': response['access_token'], + 'refresh_token': response['refresh_token'] if 'refresh_token' in response else None, + 'expires': datetime.now() + timedelta(seconds=response['expires_in']), + 'scope': response['scope'], + } def convert_refresh_token(self, refresh_token): """Convert a refresh token (granted for offline access and returned by @@ -524,7 +528,8 @@ def add_account_and_sync(self, country, credentials, bank_code=None, iban=None, error=task_state.error['name'], error_description=task_state.error['description'], code=task_state.error['code']) - raise FigoException("", task_state.message) + raise FigoException("", error_description=task_state.error['message'], + code=task_state.error['code']) return task_state def add_account_and_sync_with_new_pin(self, pin_exception, new_pin): diff --git a/figo/models.py b/figo/models.py index 2045051..d80e399 100644 --- a/figo/models.py +++ b/figo/models.py @@ -363,7 +363,7 @@ class Transaction(ModelBase): __dump_attributes__ = ["transaction_id", "account_id", "name", "account_number", "bank_code", "bank_name", "amount", "currency", "booking_date", "value_date", "purpose", - "type", "booking_text", "booked", "creation_timestamp", + "type", "booking_text", "booked", "categories", "creation_timestamp", "modification_timestamp", "visited", "additional_info"] transaction_id = None @@ -416,6 +416,9 @@ class Transaction(ModelBase): booked = None """This flag indicates whether the transaction is booked or pending""" + categories = None + """List of categories assigned to this transaction, ordered from general to specific""" + creation_timestamp = None """creation date""" @@ -443,12 +446,31 @@ def __init__(self, session, **kwargs): if self.value_date: self.value_date = dateutil.parser.parse(self.value_date) + if self.categories: + self.categories = [Category.from_dict(session, c) for c in self.categories] + def __str__(self): """Short String representation of a Transaction.""" return "Transaction: %d %s to %s at %s" % (self.amount, self.currency, self.name, str(self.value_date)) +class Category(ModelBase): + + """Object representing a category for a transaction""" + + __dump_attributes__ = ["id", "parent_id", "name"] + + id = None + + parent_id = None + + name = None + + def __str__(self): + return self.name + + class Notification(ModelBase): """Object representing a configured notification, e.g a webhook or email hook.""" diff --git a/tests/conftest.py b/tests/conftest.py index 2ef7d47..4478f88 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,11 +49,22 @@ def new_user_id(): @pytest.yield_fixture def figo_session(figo_connection, new_user_id): - if is_demo(CREDENTIALS): - pytest.skip("The demo client has no write access to the servers.") - figo_connection.add_user("Test", new_user_id, PASSWORD) response = figo_connection.credential_login(new_user_id, PASSWORD) + + scope = response['scope'] + + required_scopes = [ + 'accounts=rw', + 'transactions=rw', + 'user=rw', + 'categorization=rw', + 'create_user', + ] + + if any(s not in scope for s in required_scopes): + pytest.skip("The client ID needs write access to the servers.") + session = FigoSession(response['access_token']) yield session diff --git a/tests/test_models.py b/tests/test_models.py index a595f85..b40306b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,23 @@ -from figo.models import Account, BankContact, AccountBalance, Payment, \ - Transaction, Notification, SynchronizationStatus, User, Service, \ - LoginSettings, Credential, TaskToken, TaskState, Challenge, PaymentProposal, \ - Process, ProcessStep, ProcessOptions, Security +from figo.models import Account +from figo.models import AccountBalance +from figo.models import BankContact +from figo.models import Category +from figo.models import Challenge +from figo.models import Credential +from figo.models import LoginSettings +from figo.models import Notification +from figo.models import Payment +from figo.models import PaymentProposal +from figo.models import Process +from figo.models import ProcessOptions +from figo.models import ProcessStep +from figo.models import Security +from figo.models import Service +from figo.models import SynchronizationStatus +from figo.models import TaskState +from figo.models import TaskToken +from figo.models import Transaction +from figo.models import User def test_create_account_from_dict(demo_session): @@ -107,6 +123,45 @@ def test_create_transaction_from_dict(demo_session): assert isinstance(transaction, Transaction) +def test_create_transaction_with_categories(demo_session): + data = { + "account_id": "A1.1", + "account_number": "4711951501", + "amount": -17.89, + "bank_code": "90090042", + "bank_name": "Demobank", + "booked": False, + "booking_date": "2013-04-11T12:00:00.000Z", + "booking_text": "Ueberweisung", + "creation_timestamp": "2013-04-11T13:54:02.000Z", + "currency": "EUR", + "modification_timestamp": "2013-04-11T13:54:02.000Z", + "name": "Rogers Shipping, Inc.", + "purpose": "Ihre Sendung 0815 vom 01.03.2012, Vielen Dank", + "transaction_id": "T1.1.25", + "type": "Transfer", + "categories": [ + { + "parent_id": None, + "id": 150, + "name": "Lebenshaltung" + }, + { + "parent_id": 150, + "id": 162, + "name": "Spende" + } + ], + "value_date": "2013-04-11T12:00:00.000Z", + "visited": True + } + transaction = Transaction.from_dict(demo_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): data = { "notification_id": "N1.7", diff --git a/tests/test_writing_methods.py b/tests/test_writing_methods.py index 3d6ab7f..146801a 100644 --- a/tests/test_writing_methods.py +++ b/tests/test_writing_methods.py @@ -1,12 +1,17 @@ # coding:utf-8 +import pytest import time -import pytest from mock import patch -from figo.figo import FigoException, FigoPinException -from figo.models import TaskToken, TaskState, Service, LoginSettings +from figo.figo import FigoException +from figo.figo import FigoPinException +from figo.models import LoginSettings +from figo.models import Service +from figo.models import TaskState +from figo.models import TaskToken + CREDENTIALS = ["figo", "figo"] BANK_CODE = "90090042" @@ -98,8 +103,8 @@ def test_051_add_account_and_sync_wrong_and_correct_pin(figo_session): assert isinstance(task_state, TaskState) assert len(figo_session.accounts) == 3 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: + # XXXValentin): prevent demo account from complaining + if figo_exception.code != 90000: raise diff --git a/tox.ini b/tox.ini index 0cc46b6..bd066cd 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,5 @@ commands = py.test --cov=figo tests/ --cov-report html --junit-xml test-results. # # For other warning/error codes, please see: # http://flake8.readthedocs.org/en/latest/warnings.html -ignore = E123,E133,E226,E241,E242,F403 -exclude = tests/* +ignore = E123,E133,E226,E241,E242,H404,H405 max-line-length = 100