Skip to content

Commit

Permalink
Merge branch 'master' into feature/add-categories-to-transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
fricklerhandwerk authored May 30, 2017
2 parents 493ae8e + b1852de commit d80c806
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 34 deletions.
57 changes: 35 additions & 22 deletions figo/figo.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@


ERROR_MESSAGES = {
400: {'message': "bad request", 'description': "Bad request"},
401: {'message': "unauthorized", 'description': "Missing, invalid or expired access token."},
403: {'message': "forbidden", 'description': "Insufficient permission."},
404: {'message': "not_found", 'description': "Not found."},
405: {'message': "method_not_allowed", 'description': "Unexpected request method."},
503: {'message': "service_unavailable", 'description': "Exceeded rate limit."}

400: {'message': "bad request", 'description': "Bad request", 'code': 90000},
401: {'message': "unauthorized", 'description': "Missing, invalid or expired access token.", 'code': 90000},
403: {'message': "forbidden", 'description': "Insufficient permission.", 'code': 90000},
404: {'message': "not_found", 'description': "Not found.", 'code': 90000},
405: {'message': "method_not_allowed", 'description': "Unexpected request method.", 'code': 90000},
503: {'message': "service_unavailable", 'description': "Exceeded rate limit.", 'code': 90000},
}

USER_AGENT = "python_figo/1.5.4"
Expand Down Expand Up @@ -122,7 +121,8 @@ def _request_api(self, path, data=None, method="GET"):

return {'error': {
'message': "internal_server_error",
'description': "We are very sorry, but something went wrong"}}
'description': "We are very sorry, but something went wrong",
'code': 90000}}

def _request_with_exception(self, path, data=None, method="GET"):

Expand Down Expand Up @@ -153,30 +153,40 @@ class FigoException(Exception):
They consist of a code-like `error` and a human readable `error_description`.
"""

def __init__(self, error, error_description):
def __init__(self, error, error_description, code=None):
"""Create a Exception with a error code and error description."""
message = u"%s (%s)" % (error_description, error)
super(FigoException, self).__init__(message)
super(FigoException, self).__init__()

# XXX(dennis.lutter): not needed internally but left here for backwards compatibility
self.code = code
self.error = error
self.error_description = error_description
self.code = code

def __str__(self):
"""String representation of the FigoException."""
return "FigoException: {}({})" .format(self.error_description, self.error)

@classmethod
def from_dict(cls, dictionary):
"""Helper function creating an exception instance from the dictionary returned
by the server."""
return cls(dictionary['error']['message'], dictionary['error']['description'])
return cls(dictionary['error']['message'],
dictionary['error']['description'],
dictionary['error'].get('code'))


class FigoPinException(FigoException):
"""This exception is thrown if the wrong pin was submitted to a task. It contains
information about current state of the task."""

def __init__(self, country, credentials, bank_code, iban, save_pin):
def __init__(self, country, credentials, bank_code, iban, save_pin,
error="Wrong PIN",
error_description="You've entered a wrong PIN, please provide a new one.",
code=None,
):
"""Initialiase an Exception for a wrong PIN which contains information about the task."""
self.error = "Wrong PIN"
self.error_description = "You've entered a wrong PIN, please provide a new one."
super(FigoPinException, self).__init__(error, error_description, code)

self.country = country
self.credentials = credentials
Expand All @@ -186,7 +196,7 @@ def __init__(self, country, credentials, bank_code, iban, save_pin):

def __str__(self):
"""String representation of the FigoPinException."""
return "FigoPinException: %s(%s)" % (repr(self.error_description), repr(self.error))
return "FigoPinException: {}({})".format(self.error_description, self.error)


class FigoConnection(FigoObject):
Expand Down Expand Up @@ -503,20 +513,23 @@ def add_account_and_sync(self, country, credentials, bank_code=None, iban=None,
task_token = self.add_account(country, credentials, bank_code, iban, save_pin)
for _ in range(self.sync_poll_retry):
task_state = self.get_task_state(task_token)
logger.info("Adding account {0}/{1}: {2}".format(bank_code,iban,task_state.message))
logger.info("Adding account {0}/{1}: {2}".format(bank_code, iban, task_state.message))
logger.debug(str(task_state))
if task_state.is_ended or task_state.is_erroneous:
break
sleep(0.5)
sleep(2)
else:
raise FigoException(
'could not sync',
'task was not finished after {0} tries'.format(self.sync_poll_retry)
"could not sync",
"task was not finished after {0} tries".format(self.sync_poll_retry)
)

if task_state.is_erroneous:
if any([msg in task_state.message for msg in ["Zugangsdaten", "credentials"]]):
raise FigoPinException(country, credentials, bank_code, iban, save_pin)
if task_state.error and task_state.error['code'] == 10000:
raise FigoPinException(country, credentials, bank_code, iban, save_pin,
error=task_state.error['name'],
error_description=task_state.error['description'],
code=task_state.error['code'])
raise FigoException("", task_state.message)
return task_state

Expand Down
11 changes: 7 additions & 4 deletions figo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ class TaskState(ModelBase):

__dump_attributes__ = ["account_id", "message", "is_waiting_for_pin",
"is_waiting_for_response", "is_erroneous",
"is_ended", "challenge"]
"is_ended", "challenge", "error"]

account_id = None
"""Account ID of currently processed account"""
Expand All @@ -718,18 +718,21 @@ class TaskState(ModelBase):
challenge = None
"""Challenge object"""

error = None
"""Dict populated in case of an error"""

def __str__(self, *args, **kwargs):
"""Short String representation of a TaskState."""
string = (u"TaskState: '{self.message}' "
"(is_erroneous: {self.is_erroneous}, "
"is_ended: {self.is_ended})")
u"(is_erroneous: {self.is_erroneous}, "
u"is_ended: {self.is_ended})")

# BBB(Valentin): All strings come in UTF-8 from JSON. But:
# - python2.6: encode knows no kwargs
# - python2.7: `u"{0}".format(x)` returns `unicode`, `__str__()` excpects `str` (ASCII)
# - python3.x: encode returns `bytes`,`__str__` expects `str` (UTF-8)
# This is really ugly, but works in all pythons.
return str(string.format(self=self).encode('ascii','replace'))
return str(string.format(self=self).encode('ascii', 'replace'))


class Challenge(ModelBase):
Expand Down
1 change: 0 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

PASSWORD = 'some_words'


DEMO_TOKEN = ('ASHWLIkouP2O6_bgA2wWReRhletgWKHYjLqDaqb0LFfamim9RjexT'
'o22ujRIP_cjLiRiSyQXyt2kM1eXU2XLFZQ0Hro15HikJQT_eNeT_9XQ')

Expand Down
1 change: 1 addition & 0 deletions tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def test_sync_uri(demo_session):
def test_get_mail_from_user(demo_session):
assert demo_session.user.email == "[email protected]"


@pytest.mark.skip(reason="race condition on travis")
def test_create_update_delete_notification(demo_session):
"""
Expand Down
68 changes: 61 additions & 7 deletions tests/test_writing_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import pytest
import time

from mock import patch

from figo.figo import FigoException
from figo.figo import FigoPinException
from figo.models import LoginSettings
Expand All @@ -17,7 +19,7 @@

def test_03_get_supported_payment_services(figo_session):
services = figo_session.get_supported_payment_services("de")
assert len(services) == 28
assert len(services) > 10 # this a changing value, this tests that at least some are returned
assert isinstance(services[0], Service)


Expand All @@ -37,9 +39,58 @@ def t_05_add_account(figo_session):

def test_050_add_account_and_sync_wrong_pin(figo_session):
wrong_credentials = [CREDENTIALS[0], "123456"]
with pytest.raises(FigoPinException):
figo_session.add_account_and_sync("de", wrong_credentials, BANK_CODE)
assert len(figo_session.accounts) == 0
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:
raise


def test_add_account_and_sync_wrong_pin_postbank(figo_session):
"""
Check that `FigoPinException` is raised correctly on given task state, which occurs
when attempting to add an account to Postbank with syntactically correct (9-digit login), but
invalid credentials. Note that syntactically incorrect credentials return code `20000` and a
different message.
"""

mock_task_state = {
"is_ended": True,
"account_id": u"A2248267.0",
"is_waiting_for_pin": False,
"is_erroneous": True,
"message": u"Die Anmeldung zum Online-Zugang Ihrer Bank ist fehlgeschlagen. "
u"Bitte überprüfen Sie Ihre Benutzerkennung.",
"error": {
"code": 10000,
"group": u"user",
"name": u"Login credentials are invalid",
"message": u"9050 Die Nachricht enthält Fehler.; 9800 Dialog abgebrochen; "
u"9010 Initialisierung fehlgeschlagen, Auftrag nicht bearbeitet.; "
u"3920 Zugelassene Zwei-Schritt-Verfahren für den Benutzer.; "
u"9010 PIN/TAN Prüfung fehlgeschlagen; "
u"9931 Anmeldename oder PIN ist falsch.",
"data": {},
"description": u"Die Anmeldung zum Online-Zugang Ihrer Bank ist fehlgeschlagen. "
u"Bitte überprüfen Sie Ihre Benutzerkennung."
},
"challenge": {},
"is_waiting_for_response": False
}

with patch.object(figo_session, 'get_task_state') as mock_state:
with patch.object(figo_session, 'add_account') as mock_account:

mock_state.return_value = TaskState.from_dict(figo_session, mock_task_state)
mock_account.return_value = None

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


def test_051_add_account_and_sync_wrong_and_correct_pin(figo_session):
Expand All @@ -49,9 +100,12 @@ def test_051_add_account_and_sync_wrong_and_correct_pin(figo_session):
task_state = figo_session.add_account_and_sync("de", wrong_credentials, BANK_CODE)
except FigoPinException as pin_exception:
task_state = figo_session.add_account_and_sync_with_new_pin(pin_exception, CREDENTIALS[1])
time.sleep(5)
assert isinstance(task_state, TaskState)
assert len(figo_session.accounts) == 3
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:
raise


@pytest.mark.skip(reason="test expects state of account, that are not prepared at the moment")
Expand Down

0 comments on commit d80c806

Please sign in to comment.