budget/tests/test_psd2.py
Luís Murta 01df97ed46
back_populates option on category relationship
Due to the use of the dataclasses mixin on the SQLAlchemy types, a
back_populates creates a RecursiveError when comparing two types. This
occurs because the dataclass will overwrite the __eq__ operator, and it
doesn't know when to stop comparing relationships.

Removing the dataclasses isn't the best approach, since then __init__,
__eq__ and __repr__ methods would have to be added to all types. Thus
the solution was to remove the relationship on the child (on a
one-to-one relationship) from the __eq__ operation, with the use of the
compare parameter.

Took the opportunity to define more logical __init__ methods on the
`Rule` and child classes.
Also revised the parameter options on some DB types.
2024-01-22 21:45:49 +00:00

100 lines
3.1 KiB
Python

import datetime as dt
from decimal import Decimal
from typing import Any, Optional
import pytest
import requests
import mocks.nordigen as mock
from pfbudget.db.model import AccountType, Bank, BankTransaction, Nordigen
from pfbudget.extract.exceptions import BankError, CredentialsError
from pfbudget.extract.extract import Extractor
from pfbudget.extract.nordigen import NordigenClient, NordigenCredentials
from pfbudget.extract.psd2 import PSD2Extractor
class MockGet:
def __init__(self, mock_exception: Optional[Exception] = None):
self._status_code = 200
self._mock_exception = mock_exception
def __call__(self, *args: Any, **kwargs: Any):
if self._mock_exception:
raise self._mock_exception
self._headers: dict[str, str] = kwargs["headers"]
if "Authorization" not in self._headers or not self._headers["Authorization"]:
self._status_code = 401
self.url: str = kwargs["url"]
return self
@property
def ok(self):
return True if self._status_code < 400 else False
@property
def status_code(self):
return self._status_code
def json(self):
if self.url.endswith("accounts/" + mock.id + "/"):
return mock.accounts_id
elif self.url.endswith("accounts/" + mock.id + "/transactions/"):
return mock.accounts_id_transactions
elif self.url.endswith("requisitions/"):
return mock.requisitions
elif self.url.endswith("requisitions/" + mock.id + "/"):
return mock.requisitions_id
@pytest.fixture(autouse=True)
def mock_requests(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr("requests.get", MockGet())
monkeypatch.delattr("requests.post")
monkeypatch.delattr("requests.put")
monkeypatch.delattr("requests.delete")
@pytest.fixture
def extractor() -> Extractor:
credentials = NordigenCredentials("ID", "KEY", "TOKEN")
return PSD2Extractor(NordigenClient(credentials))
@pytest.fixture
def bank() -> Bank:
bank = Bank("Bank#1", "", AccountType.checking)
bank.nordigen = Nordigen("", "", mock.id, False)
return bank
class TestExtractPSD2:
def test_empty_credentials(self):
cred = NordigenCredentials("", "")
with pytest.raises(CredentialsError):
NordigenClient(cred)
def test_no_psd2_bank(self, extractor: Extractor):
with pytest.raises(BankError):
extractor.extract(Bank("", "", AccountType.checking))
def test_timeout(
self, monkeypatch: pytest.MonkeyPatch, extractor: Extractor, bank: Bank
):
monkeypatch.setattr(
"requests.get", MockGet(mock_exception=requests.ReadTimeout())
)
with pytest.raises(requests.Timeout):
extractor.extract(bank)
def test_extract(self, extractor: Extractor, bank: Bank):
assert extractor.extract(bank) == [
BankTransaction(
dt.date(2023, 1, 14), "string", Decimal("328.18"), bank="Bank#1"
),
BankTransaction(
dt.date(2023, 2, 14), "string", Decimal("947.26"), bank="Bank#1"
),
]