...
 
Commits (2)
......@@ -115,12 +115,20 @@ class Account:
return '{} <{}>'.format(self.label, self.ident)
def __repr__(self):
float_precision = 'initialbalance={}:.0{}f{})'.format(
'{', self.currency.exponent, '}'
)
return (
'Account(' +
', '.join([
'ident={!r}'.format(self.ident),
'label={!r}'.format(self.label),
'currency={!r}'.format(self.currency),
'initialbalance={!r})'.format(self.initialbalance)
float_precision.format(
round(
self.initialbalance / (10 ** self.currency.exponent),
self.currency.exponent
)
)
])
)
......@@ -88,10 +88,10 @@ class Memory(storages.Storage):
self._accounts = {}
self._operations = {}
def register_account(self, account):
def register_account(self, account, force=False):
logger.get().info('Register new account %r', account)
logger.get().debug('%r', self._accounts)
if account.ident in self._accounts:
if not force and account.ident in self._accounts:
logger.get().error('Already registered account %s', account.ident)
raise exceptions.Conflict('Memory-Storage', account)
self._accounts[account.ident] = account
......
......@@ -27,12 +27,15 @@ class Storage(metaclass=abc.ABCMeta):
"""
@abc.abstractmethod
def register_account(self, account):
def register_account(self, account, force=False):
"""
Store a new account in repository
:param account: Account to register
:type account: ``openbudget.core.domain.accounts.Account``
:param force: Force account registering by updating all field of
account.
:type force: ``bool``
:raises openbudget.core.exceptions.Conflict: When account is already
stored in repository.
......
......@@ -258,3 +258,99 @@ class Delete(base.UseCaseBase):
self._repo.delete_account(request.ident)
return base.ResponseSuccess()
class UpdateRequest(base.ValidRequest):
"""
Request use as input in Update use cases
"""
def __init__(self, ident, **kwargs):
self.ident = ident
self.label = kwargs.get('label', None)
self.currency = kwargs.get('currency', None)
self.initialbalance = kwargs.get('initialbalance', None)
@classmethod
def from_dict(cls, adict):
"""
:adict Keys:
* *ident* (``str``) --
_mandatory_, String that identify accoutn to modify.
* *label* (``str``) --
_optional_, New Human readable account description.
* *currency* (``str``) --
_optional_, New account currency, ISO-4217 code
* *initialbalance* (``float``) --
_optional_, Initial balance of account.
"""
errors = base.InvalidRequest()
if 'ident' not in adict:
errors.add_error('ident', 'Required argument')
if 'currency' in adict:
try:
currency.from_code(adict['currency'])
except exceptions.UnknownCurrency:
errors.add_error(
'currency',
'Must be a valid ISO-4217 currency code'
)
if 'initialbalance' in adict:
try:
adict['initialbalance'] = float(adict['initialbalance'])
except ValueError:
errors.add_error(
'initialbalance',
'Must be a float'
)
if errors.has_errors():
return errors
return cls(**adict)
def to_dict(self):
"""
Export request information to a dictionnary with account ident and all
modified information.
:return: Account modification dict
:rtype: ``dict``
"""
account = {
'ident': self.ident
}
if self.label is not None:
account['label'] = self.label
if self.currency is not None:
account['currency'] = self.currency
if self.initialbalance is not None:
account['initialbalance'] = self.initialbalance
return account
class Update(base.UseCaseBase):
"""
Usecase to allow account modification
"""
@base.safe_exec
def execute(self, request):
self._log.debug('Execute update')
if not request:
return base.ResponseFailure.build_from_invalid_request(request)
self._log.debug('Update account from request %s', request)
updated_list = []
required_account = self._repo.get_account(request.ident).to_dict()
self._log.info('Found accout to update: %r', required_account)
for key, value in request.to_dict().items():
if key != 'ident':
self._log.info('Set %s with %r', key, value)
required_account[key] = value
updated_list.append(key)
self._log.debug('Force register account')
self._repo.register_account(
accounts.Account.from_dict(required_account),
force=True
)
return base.ResponseSuccess(updated_list)
......@@ -25,6 +25,30 @@ import os
import uuid
import pytest
from openbudget.core import logger
@pytest.fixture(scope='session', autouse=True)
def set_log_level(request):
"""
Configure OpenBudget Logger to display all traces
"""
if request.config.getoption('--log'):
logger.configure_logger({
'level': 'debug',
'verbose': 'true',
'color': 'true'
})
def pytest_addoption(parser):
"""
Understand `--log` option to PyTest to enable openbudget logs
"""
parser.addoption(
"--log", action='store_true'
)
@pytest.fixture
def clean_local_file():
......
......@@ -174,5 +174,5 @@ def test_account_representations():
assert representation.startswith(
"Account(ident='MyAccount', label='MyLabel', currency="
)
assert representation.endswith(', initialbalance=1200)')
assert representation.endswith(', initialbalance=12.00)')
assert 'EUR' in representation
......@@ -110,6 +110,19 @@ def test_accounts_registration_conflict(single_account):
single_account['memory'].register_account(single_account['account'])
def test_accounts_registration_force_update(single_account):
"""
Generate conflict when registering a new account
"""
single_account['account'].label = 'Updated Label'
single_account['memory'].register_account(
single_account['account'], force=True
)
assert single_account['memory'].get_account(
single_account['account'].ident
).label == 'Updated Label'
def test_single_operation_registering(single_account):
"""
Register a new operation and retriev it
......
......@@ -319,3 +319,80 @@ def test_account_delete_invalid_request(ucdelete):
assert not bool(response)
repo.delete_account.assert_not_called()
@pytest.fixture
def ucupdate(repo):
"""
Build a UseCase Update tight to repo
"""
ucupdate = ucaccount.Update(repo)
stored_account = mock.MagicMock()
stored_account.ident = 'MyAccount'
yield repo, ucupdate, stored_account
def test_account_update_valid_request(ucupdate):
"""
Use case to update account
"""
repo, uc_update, account = ucupdate
repo.get_account.return_value.to_dict.return_value = {
'ident': account.ident,
'label': 'foobar'
}
request = mock.MagicMock()
request.__bool__.return_value = True
request.to_dict.return_value = {
'ident': account.ident,
'label': 'new_label'
}
with mock.patch('openbudget.core.domains.accounts.Account') as mock_acc:
response = uc_update.execute(request)
repo.register_account.assert_called_with(
mock_acc.from_dict.return_value, force=True
)
mock_acc.from_dict.assert_called_with({'ident': account.ident,
'label': 'new_label'})
repo.get_account.assert_called_with(request.ident)
assert bool(response)
assert response.value == ['label']
def test_account_update_invalid_request(ucupdate):
"""
Use case to update account but with invalid request
"""
repo, uc_update, _account = ucupdate
request = mock.MagicMock()
request.__bool__.return_value = False
response = uc_update.execute(request)
assert not bool(response)
repo.register_account.assert_not_called()
repo.get_account.assert_not_called()
def test_account_not_found(ucupdate):
"""
Use case to update account on unknown request
"""
repo, uc_update, _account = ucupdate
request = mock.MagicMock()
request.__bool__.return_value = True
repo.get_account.side_effect = exceptions.ItemNotFound
response = uc_update.execute(request)
assert not bool(response)
repo.register_account.assert_not_called()
......@@ -342,3 +342,77 @@ def test_delete_request_factory_invalid():
'message': 'Required argument'
}
]
def test_update_request_ident():
"""
Create an update request
"""
req = ucaccount.UpdateRequest('foobar')
assert bool(req)
assert req.ident == 'foobar'
assert req.label is None
assert req.currency is None
assert req.initialbalance is None
assert req.to_dict() == {
'ident': 'foobar'
}
def test_update_request_factory_valid():
"""
Create an update request from factory
"""
req = ucaccount.UpdateRequest.from_dict({
'ident': 'baz',
'label': 'account label',
'currency': 'USD',
'initialbalance': '12.34'
})
assert bool(req)
assert req.ident == 'baz'
assert req.label == 'account label'
assert req.currency == 'USD'
assert req.initialbalance == 12.34
assert req.to_dict() == {
'ident': 'baz',
'label': 'account label',
'currency': 'USD',
'initialbalance': 12.34
}
def test_update_request_no_ident():
"""
Create an update request without ident
"""
req = ucaccount.UpdateRequest.from_dict({})
assert not bool(req)
assert isinstance(req, base.InvalidRequest)
assert req.errors == [{
'parameter': 'ident',
'message': 'Required argument'
}]
def test_update_request_invalid_currency_balance():
"""
Create an update request with invalid currency and balance
"""
req = ucaccount.UpdateRequest.from_dict({
'ident': 'bar',
'currency': 'FOO',
'initialbalance': '12AZ'
})
assert not bool(req)
assert isinstance(req, base.InvalidRequest)
assert req.errors == [{
'parameter': 'currency',
'message': 'Must be a valid ISO-4217 currency code'
}, {
'parameter': 'initialbalance',
'message': 'Must be a float'
}]