diff --git a/.gitignore b/.gitignore index 2e23ec2..2f2cf84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ +.python *egg* -client.py +examples/client.py **/__pycache__ .coverage htmlcov diff --git a/Makefile b/Makefile index aa62141..3952e4a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: build -build: +build: venv python setup.py sdist .PHONY: isort @@ -16,13 +16,13 @@ flake8: .PHONY: test test: - pytest + pytest --ignore=examples/ .PHONY: ci -ci: isort black flake8 test +ci: venv isort black flake8 test .PHONY: ci-fix -ci-fix: +ci-fix: venv isort ./nordigen ./tests ./examples black ./nordigen ./tests ./examples @@ -32,7 +32,7 @@ dev: $(MAKE) ci .PHONY: install-pip -install-pip: +install-pip: venv python -m pip install --upgrade pip .PHONY: install-dev @@ -47,3 +47,6 @@ install-deploy: install-pip deploy: build twine upload --verbose dist/* +.PHONY: venv +venv: + . .python/bin/activate \ No newline at end of file diff --git a/README.md b/README.md index 0afbe44..fd661b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Nordigen API Client +[![GitHub](https://img.shields.io/github/license/dogmatic69/nordigen-python)](LICENSE) +[![CodeFactor](https://www.codefactor.io/repository/github/dogmatic69/nordigen-python/badge)](https://www.codefactor.io/repository/github/dogmatic69/nordigen-python) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dogmatic69_nordigen-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=dogmatic69_nordigen-python) +[![CI](https://github.com/dogmatic69/nordigen-python/actions/workflows/master.yaml/badge.svg)](https://github.com/dogmatic69/nordigen-python/actions/workflows/master.yaml) + Nordigen is a (always*) free banking API that takes advantage of the EU PSD2 regulations. They connect to banks in over 30 countries using real banking API's (no screen scraping). @@ -28,7 +33,7 @@ pip install nordigen-python ## Usage -Some more indepth working examples can be found in `./examples`. Also check out the test cases for usage examples. +Some more in-depth working examples can be found in `./examples`. Also check out the test cases for usage examples. Create a client instance diff --git a/VERSION b/VERSION index 8294c18..fdd0e66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.2 \ No newline at end of file +0.2.0b1 \ No newline at end of file diff --git a/nordigen/__init__.py b/nordigen/__init__.py index 7a2acb8..8a9bc00 100644 --- a/nordigen/__init__.py +++ b/nordigen/__init__.py @@ -1,13 +1,35 @@ -from nordigen.client import AccountClient, AgreementsClient, AspspsClient, RequisitionsClient +import warnings +from apiclient import HeaderAuthentication, NoAuthentication + +from nordigen.client import AccountClient, AgreementsClient, AspspsClient, AuthClient, RequisitionsClient +from nordigen.oauth import OAuthAuthentication + + +def Client(token=None, request_strategy=None, secret_id=None, secret_key=None, version=None): + if token: + warnings.warn("Use Client(secret_id=xxx, secret_key=xxx) instead of token", DeprecationWarning) + + if not token and (not secret_id or not secret_key): + raise ValueError("secret_id and secret_key must be provided") -def Client(token, request_strategy=None): def instance(): return instance - instance.aspsps = AspspsClient(token=token, request_strategy=request_strategy) - instance.agreements = AgreementsClient(token=token, request_strategy=request_strategy) - instance.account = AccountClient(token=token, request_strategy=request_strategy) - instance.requisitions = RequisitionsClient(token=token, request_strategy=request_strategy) + auth = HeaderAuthentication(scheme="Token", token=token) + if not token: + version = "v2" if not version else version + auth = OAuthAuthentication( + body={ + "secret_id": secret_id, + "secret_key": secret_key, + }, + client=AuthClient(auth=NoAuthentication(), request_strategy=request_strategy, version=version), + ) + + instance.aspsps = AspspsClient(auth=auth, request_strategy=request_strategy, version=version) + instance.agreements = AgreementsClient(auth=auth, request_strategy=request_strategy, version=version) + instance.account = AccountClient(auth=auth, request_strategy=request_strategy, version=version) + instance.requisitions = RequisitionsClient(auth=auth, request_strategy=request_strategy, version=version) return instance diff --git a/nordigen/client.py b/nordigen/client.py index 2001f81..2b5a96f 100644 --- a/nordigen/client.py +++ b/nordigen/client.py @@ -1,6 +1,6 @@ import urllib -from apiclient import APIClient, HeaderAuthentication, JsonResponseHandler +from apiclient import APIClient, JsonResponseHandler def next_page_by_url(response, previous_page_url): @@ -19,7 +19,7 @@ def next_page_by_url(response, previous_page_url): class NordigenClient(APIClient): - def __init__(self, token, scheme='https', host='ob.nordigen.com', base='/api', request_strategy=None): + def __init__(self, auth, scheme="https", host="ob.nordigen.com", base="/api", request_strategy=None, version="v2"): """Nordigen client base class. Args: @@ -32,10 +32,11 @@ def __init__(self, token, scheme='https', host='ob.nordigen.com', base='/api', r self.request_strategy = request_strategy self.scheme = scheme self.host = host - self.base = base + version = f"/{version}" if version else "" + self.base = f"{base}{version}" super(NordigenClient, self).__init__( - authentication_method=HeaderAuthentication(scheme='Token', token=token), + authentication_method=auth, response_handler=JsonResponseHandler, request_strategy=request_strategy, ) @@ -50,73 +51,97 @@ def url(self, fragment, url_args={}): Returns: str """ - url_args = ('?' + urllib.parse.urlencode(url_args)) if url_args else '' - return f'{self.scheme}://{self.host}{self.base}/{fragment}/{url_args}' + url_args = ("?" + urllib.parse.urlencode(url_args)) if url_args else "" + return f"{self.scheme}://{self.host}{self.base}/{fragment}/{url_args}" + + +class AuthClient(NordigenClient): + def token(self, secret_id, secret_key): + url = self.url(fragment="token/new") + return self.post( + url, + data={ + "secret_id": secret_id, + "secret_key": secret_key, + }, + ) + + def refresh(self, refresh_token): + url = self.url(fragment="token/refresh") + return self.post( + url, + data={ + "refresh_token": refresh_token, + }, + ) class AccountClient(NordigenClient): def info(self, id): - url = self.url(fragment=f'accounts/{id}') + url = self.url(fragment=f"accounts/{id}") return self.get(url) def balances(self, id): - url = self.url(fragment=f'accounts/{id}/balances') + url = self.url(fragment=f"accounts/{id}/balances") return self.get(url) def details(self, id): - url = self.url(fragment=f'accounts/{id}/details') + url = self.url(fragment=f"accounts/{id}/details") return self.get(url) def transactions(self, id): - url = self.url(fragment=f'accounts/{id}/transactions') + url = self.url(fragment=f"accounts/{id}/transactions") return self.get(url) class AgreementsClient(NordigenClient): def create(self, enduser_id, aspsp_id, historical_days=30): - url = self.url(fragment='agreements/enduser') - return self.post(url, data={ - "max_historical_days": historical_days, - "enduser_id": enduser_id, - "aspsp_id": aspsp_id, - }) + url = self.url(fragment="agreements/enduser") + return self.post( + url, + data={ + "max_historical_days": historical_days, + "enduser_id": enduser_id, + "aspsp_id": aspsp_id, + }, + ) def by_enduser_id(self, enduser_id, limit=None, offset=None): url_args = dict(enduser_id=enduser_id, limit=limit, offset=offset) url_args = {k: v for k, v in url_args.items() if v} - url = self.url(fragment='agreements/enduser', url_args=url_args) + url = self.url(fragment="agreements/enduser", url_args=url_args) return self.get(url) def by_id(self, id): - url = self.url(fragment=f'agreements/enduser/{id}') + url = self.url(fragment=f"agreements/enduser/{id}") return self.get(url) def remove(self, id): - url = self.url(fragment=f'agreements/enduser/{id}') + url = self.url(fragment=f"agreements/enduser/{id}") return self.delete(url) def accept(self, id): - url = self.url(fragment=f'agreements/enduser/{id}/accept') - return self.put(url, { - "user_agent": "user-agent", - "ip_address": "127.0.0.1" - }) + url = self.url(fragment=f"agreements/enduser/{id}/accept") + return self.put(url, {"user_agent": "user-agent", "ip_address": "127.0.0.1"}) def text(self, id): - url = self.url(fragment=f'agreements/enduser/{id}/text') + url = self.url(fragment=f"agreements/enduser/{id}/text") return self.get(url) class AspspsClient(NordigenClient): def by_country(self, country): - url = self.url(fragment='aspsps', url_args={ - 'country': country, - }) + url = self.url( + fragment="aspsps", + url_args={ + "country": country, + }, + ) return self.get(url) def by_id(self, id): - url = self.url(fragment=f'aspsps/{id}') + url = self.url(fragment=f"aspsps/{id}") return self.get(url) @@ -125,19 +150,19 @@ def list(self, limit=None, offset=None): url_args = dict(limit=limit, offset=offset) url_args = {k: v for k, v in url_args.items() if v} - url = self.url(fragment='requisitions', url_args=url_args) + url = self.url(fragment="requisitions", url_args=url_args) return self.get(url) def by_id(self, id): - url = self.url(fragment=f'requisitions/{id}') + url = self.url(fragment=f"requisitions/{id}") return self.get(url) def remove(self, id): - url = self.url(fragment=f'requisitions/{id}') + url = self.url(fragment=f"requisitions/{id}") return self.delete(url) def create(self, redirect, enduser_id, reference, agreements=[], language=None): - url = self.url(fragment='requisitions') + url = self.url(fragment="requisitions") data = { "redirect": redirect, "agreements": agreements, @@ -150,7 +175,10 @@ def create(self, redirect, enduser_id, reference, agreements=[], language=None): return self.post(url, data=data) def initiate(self, id, aspsp_id): - url = self.url(fragment=f'requisitions/{id}/links') - return self.post(url, { - "aspsp_id": aspsp_id, - }) + url = self.url(fragment=f"requisitions/{id}/links") + return self.post( + url, + { + "aspsp_id": aspsp_id, + }, + ) diff --git a/nordigen/oauth.py b/nordigen/oauth.py new file mode 100644 index 0000000..447154a --- /dev/null +++ b/nordigen/oauth.py @@ -0,0 +1,45 @@ +from time import time + +from apiclient.authentication_methods import BaseAuthenticationMethod + + +class OAuthAuthentication(BaseAuthenticationMethod): + """Authentication using secret_id and secret_key.""" + + _access_token = None + _token_expiration = None + _refresh_token = None + _refresh_expiration = None + + def __init__( + self, + body, + client, + expiry_margin=10, + ): + """Initialize OAuthAuthentication.""" + self._client = client + self._body = body + self._expiry_margin = expiry_margin + + def get_headers(self): + self.refresh_token() + return { + "Authorization": f"Bearer {self._access_token}", + } + + def refresh_token(self): + if self._token_expiration and self._token_expiration >= int(time()): + return True + + if self._refresh_expiration and self._refresh_expiration >= int(time()): + ret = self._client.refresh(refresh_token=self._refresh_token) + self._access_token = ret.get("access") + self._token_expiration = int(time()) + int(ret.get("access_expires")) - self._expiry_margin + return True + + ret = self._client.token(**self._body) + self._access_token = ret.get("access") + self._refresh_token = ret.get("refresh") + self._token_expiration = int(time()) + int(ret.get("access_expires")) - self._expiry_margin + self._refresh_expiration = int(time()) + int(ret.get("refresh_expires")) - self._expiry_margin diff --git a/pyproject.toml b/pyproject.toml index c18f3b5..2d88e8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [tool.black] line-length=120 -py36=true \ No newline at end of file +py36=true diff --git a/tests/__init__.py b/tests/__init__.py index 5c1ebd1..a213107 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,5 +5,16 @@ from nordigen import Client -def test_client(token="secret-token", request_strategy=Mock(spec=BaseRequestStrategy)): - return Client(token=token, request_strategy=request_strategy) +def test_client( + token=None, + request_strategy=Mock(spec=BaseRequestStrategy), + secret_id="secret-id", + secret_key="secret-key", +): + args = dict( + token=token, + request_strategy=request_strategy, + secret_id=secret_id, + secret_key=secret_key, + ) + return Client(**args) diff --git a/tests/test_account.py b/tests/test_account.py index ffa835b..8945895 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -1,4 +1,7 @@ import unittest +from unittest.mock import Mock + +from apiclient.request_strategies import BaseRequestStrategy from . import test_client @@ -7,25 +10,36 @@ class TestAccountClient(unittest.TestCase): def test_info(self): client = test_client().account client.info("foobar-id") - client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/accounts/foobar-id/", params=None) + client.request_strategy.get.assert_called_with( + "https://ob.nordigen.com/api/v2/accounts/foobar-id/", params=None + ) def test_balances(self): client = test_client().account client.balances("foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/accounts/foobar-id/balances/", params=None + "https://ob.nordigen.com/api/v2/accounts/foobar-id/balances/", params=None ) def test_details(self): client = test_client().account client.details("foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/accounts/foobar-id/details/", params=None + "https://ob.nordigen.com/api/v2/accounts/foobar-id/details/", params=None ) def test_transactions(self): client = test_client().account client.transactions("foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/accounts/foobar-id/transactions/", params=None + "https://ob.nordigen.com/api/v2/accounts/foobar-id/transactions/", params=None ) + + +class TestAccountClientErrors(unittest.TestCase): + def test_info(self): + client = test_client(request_strategy=Mock(spec=BaseRequestStrategy)).account + client.info("foobar-id") + + # mock urllib3.connectionpool._make_request and raise an exception + client.request_strategy.get.side_effect = Exception("test") diff --git a/tests/test_agreements.py b/tests/test_agreements.py index cf76f64..a5907d8 100644 --- a/tests/test_agreements.py +++ b/tests/test_agreements.py @@ -3,31 +3,31 @@ from . import test_client -class TestAspspsClient(unittest.TestCase): +class TestAgreementsClient(unittest.TestCase): def test_by_enduser_id(self): client = test_client().agreements client.by_enduser_id(enduser_id="foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/?enduser_id=foobar-id", params=None + "https://ob.nordigen.com/api/v2/agreements/enduser/?enduser_id=foobar-id", params=None ) def test_by_enduser_id_pagination(self): client = test_client().agreements client.by_enduser_id(enduser_id="foobar-id", limit=1) client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/?enduser_id=foobar-id&limit=1", params=None + "https://ob.nordigen.com/api/v2/agreements/enduser/?enduser_id=foobar-id&limit=1", params=None ) client.by_enduser_id(enduser_id="foobar-id", offset=5) client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/?enduser_id=foobar-id&offset=5", params=None + "https://ob.nordigen.com/api/v2/agreements/enduser/?enduser_id=foobar-id&offset=5", params=None ) def test_create(self): client = test_client().agreements client.create(enduser_id="foobar-id", aspsp_id="fizzbuzz-id", historical_days=45) client.request_strategy.post.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/", + "https://ob.nordigen.com/api/v2/agreements/enduser/", data={"max_historical_days": 45, "enduser_id": "foobar-id", "aspsp_id": "fizzbuzz-id"}, params=None, ) @@ -36,21 +36,21 @@ def test_by_id(self): client = test_client().agreements client.by_id("foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/foobar-id/", params=None + "https://ob.nordigen.com/api/v2/agreements/enduser/foobar-id/", params=None ) def test_remove(self): client = test_client().agreements client.remove("foobar-id") client.request_strategy.delete.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/foobar-id/", params=None + "https://ob.nordigen.com/api/v2/agreements/enduser/foobar-id/", params=None ) def test_accept(self): client = test_client().agreements client.accept("foobar-id") client.request_strategy.put.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/foobar-id/accept/", + "https://ob.nordigen.com/api/v2/agreements/enduser/foobar-id/accept/", data={"user_agent": "user-agent", "ip_address": "127.0.0.1"}, params=None, ) @@ -59,5 +59,5 @@ def test_text(self): client = test_client().agreements client.text("foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/agreements/enduser/foobar-id/text/", params=None + "https://ob.nordigen.com/api/v2/agreements/enduser/foobar-id/text/", params=None ) diff --git a/tests/test_aspsps.py b/tests/test_aspsps.py index ebac4ec..024a0a1 100644 --- a/tests/test_aspsps.py +++ b/tests/test_aspsps.py @@ -7,9 +7,9 @@ class TestAspspsClient(unittest.TestCase): def test_by_country(self): client = test_client().aspsps client.by_country("SE") - client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/aspsps/?country=SE", params=None) + client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/v2/aspsps/?country=SE", params=None) def test_by_id(self): client = test_client().aspsps client.by_id("foo-bar-id") - client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/aspsps/foo-bar-id/", params=None) + client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/v2/aspsps/foo-bar-id/", params=None) diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..d620b06 --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,28 @@ +import unittest + +from . import test_client + + +class TestAuthClient(unittest.TestCase): + def test_token(self): + client = test_client().aspsps.get_authentication_method()._client + client.token(secret_id="secret_id", secret_key="secret_key") + client.request_strategy.post.assert_called_with( + "https://ob.nordigen.com/api/v2/token/new/", + data={ + "secret_id": "secret_id", + "secret_key": "secret_key", + }, + params=None, + ) + + def test_refresh(self): + client = test_client().aspsps.get_authentication_method()._client + client.refresh(refresh_token="refresh_token") + client.request_strategy.post.assert_called_with( + "https://ob.nordigen.com/api/v2/token/refresh/", + data={ + "refresh_token": "refresh_token", + }, + params=None, + ) diff --git a/tests/test_base.py b/tests/test_base.py index a6685fb..597e043 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,12 +1,15 @@ import unittest -from nordigen.client import NordigenClient as Client -from nordigen.client import next_page_by_url +from apiclient import HeaderAuthentication + +from nordigen.client import NordigenClient, next_page_by_url + +header_auth = HeaderAuthentication(scheme="Token", token="fizz-buzz") class TestBaseAuth(unittest.TestCase): - def test_token(self): - client = Client(token="fizz-buzz") + def test_token_auth(self): + client = NordigenClient(auth=header_auth) self.assertEqual( client.get_default_headers(), @@ -24,42 +27,42 @@ def test_pagination(self): class TestBaseUrl(unittest.TestCase): def test_url_host(self): - client = Client(token="asdf", host="localhost") + client = NordigenClient(auth=None, host="localhost") result = client.url("foo") - self.assertEqual(result, "https://localhost/api/foo/") + self.assertEqual(result, "https://localhost/api/v2/foo/") def test_url_scheme(self): - client = Client(token="asdf", scheme="http") + client = NordigenClient(auth=None, scheme="sftp") result = client.url("foo") - self.assertEqual(result, "http://ob.nordigen.com/api/foo/") + self.assertEqual(result, "sftp://ob.nordigen.com/api/v2/foo/") def test_url_base(self): - client = Client(token="asdf", base="") + client = NordigenClient(auth=None, base="") result = client.url("foo") - self.assertEqual(result, "https://ob.nordigen.com/foo/") + self.assertEqual(result, "https://ob.nordigen.com/v2/foo/") - client = Client(token="asdf", base="/some/thing/here") + client = NordigenClient(auth=None, base="/some/thing/here") result = client.url("foo") - self.assertEqual(result, "https://ob.nordigen.com/some/thing/here/foo/") + self.assertEqual(result, "https://ob.nordigen.com/some/thing/here/v2/foo/") def test_url_basic(self): - client = Client(token="asdf") + client = NordigenClient(auth=None) result = client.url("foo") - self.assertEqual(result, "https://ob.nordigen.com/api/foo/") + self.assertEqual(result, "https://ob.nordigen.com/api/v2/foo/") result = client.url("foo/bar") - self.assertEqual(result, "https://ob.nordigen.com/api/foo/bar/") + self.assertEqual(result, "https://ob.nordigen.com/api/v2/foo/bar/") def test_url_args(self): - client = Client(token="asdf") + client = NordigenClient(auth=None) result = client.url("foo", url_args={}) - self.assertEqual(result, "https://ob.nordigen.com/api/foo/") + self.assertEqual(result, "https://ob.nordigen.com/api/v2/foo/") result = client.url("foo", url_args={"fizz": "buzz"}) - self.assertEqual(result, "https://ob.nordigen.com/api/foo/?fizz=buzz") + self.assertEqual(result, "https://ob.nordigen.com/api/v2/foo/?fizz=buzz") diff --git a/tests/test_client_wrapper.py b/tests/test_client_wrapper.py index 6229aa5..fa50de3 100644 --- a/tests/test_client_wrapper.py +++ b/tests/test_client_wrapper.py @@ -9,13 +9,33 @@ class TestClient(unittest.TestCase): def test_basic(self): - client = Client(token="whoo", request_strategy=Mock(spec=BaseRequestStrategy)) + client = Client(secret_id="secret", secret_key="secret", request_strategy=Mock(spec=BaseRequestStrategy)) self.assertEqual(client, client()) def test_instances(self): - client = Client(token="whoo", request_strategy=Mock(spec=BaseRequestStrategy)) + client = Client(secret_id="secret", secret_key="secret", request_strategy=Mock(spec=BaseRequestStrategy)) self.assertIsInstance(client.aspsps, NordigenClient) self.assertIsInstance(client.agreements, NordigenClient) self.assertIsInstance(client.account, NordigenClient) self.assertIsInstance(client.requisitions, NordigenClient) + + def test_pool(self): + client = Client(secret_id="secret", secret_key="secret", request_strategy=Mock(spec=BaseRequestStrategy)) + self.assertIsInstance(client.aspsps, NordigenClient) + + def test_warning_for_depreciated_config(self): + with self.assertWarns(DeprecationWarning): + Client(token="token") + + def test_exception_missing_secret_id(self): + with self.assertRaises(ValueError): + Client(secret_key="secret") + + def test_exception_missing_secret_key(self): + with self.assertRaises(ValueError): + Client(secret_id="secret") + + def test_exception_missing_secret_id_and_secret_key(self): + with self.assertRaises(ValueError): + Client() diff --git a/tests/test_oauth.py b/tests/test_oauth.py new file mode 100644 index 0000000..3dfb8df --- /dev/null +++ b/tests/test_oauth.py @@ -0,0 +1,106 @@ +import unittest +from unittest.mock import Mock, patch + +from nordigen.oauth import OAuthAuthentication + + +class TestOAuthAuthentication(unittest.TestCase): + def test_perform_initial_auth(self): + auth = OAuthAuthentication( + expiry_margin=999, + body={}, + client="client", + ) + + assert auth._client == "client" + + def test_valid_token_does_not_refresh(self): + mocked_client = Mock() + auth = OAuthAuthentication( + expiry_margin=999, + body={}, + client=mocked_client, + ) + + auth._token_expiration = 99999999999 + + assert auth.refresh_token() is True + + mocked_client.token.assert_not_called() + mocked_client.refresh.assert_not_called() + + @patch("nordigen.oauth.time") + def test_expired_token_refreshes(self, mocked_time): + mocked_time.return_value = 1234.567 + mocked_client = Mock() + mocked_client.refresh.return_value = { + "access": "access-token-yeahaw", + "access_expires": 50, # expires in 50 seconds + } + + auth = OAuthAuthentication( + expiry_margin=999, + body={"foo": "bar"}, + client=mocked_client, + ) + + auth._refresh_expiration = 1250 + auth._refresh_token = "super-refresh-token" + + assert auth._access_token is None + assert auth._token_expiration is None + + assert auth.refresh_token() is True + + mocked_client.refresh.assert_called_once_with(refresh_token="super-refresh-token") + + assert auth._access_token == "access-token-yeahaw" + assert auth._token_expiration == 1234 + 50 - 999 + + @patch("nordigen.oauth.time") + def test_expired_refresh_token(self, mocked_time): + mocked_time.return_value = 1234.567 + mocked_client = Mock() + mocked_client.token.return_value = { + "access": "access-token-yeahaw", + "access_expires": 50, # expires in 50 seconds + "refresh": "refresh-token-yeahaw", + "refresh_expires": 60, # expires in 60 seconds + } + + auth = OAuthAuthentication( + expiry_margin=1, + body={"foo": "bar"}, + client=mocked_client, + ) + + auth._refresh_expiration = 1200 # expired 34 seconds ago + auth._refresh_token = "super-old-refresh-token" + auth._token_expiration = 1200 # expired 34 seconds ago + auth._access_token = "super-old-access-token" + + assert auth.refresh_token() is None + + mocked_client.token.assert_called_once_with(foo="bar") + + assert auth._access_token == "access-token-yeahaw" + assert auth._token_expiration == 1234 + 50 - 1 + assert auth._refresh_token == "refresh-token-yeahaw" + assert auth._refresh_expiration == 1234 + 60 - 1 + + @patch("nordigen.oauth.time") + def test_get_headers(self, mocked_time): + mocked_time.return_value = 1234.567 + + auth = OAuthAuthentication( + expiry_margin=999, + body={}, + client="client", + ) + auth._token_expiration = 9999 + auth._access_token = "valid-access-token" + + expected = { + "Authorization": "Bearer valid-access-token", + } + assert auth.get_headers() == expected diff --git a/tests/test_requisitions.py b/tests/test_requisitions.py index 118388e..82ab6d5 100644 --- a/tests/test_requisitions.py +++ b/tests/test_requisitions.py @@ -8,14 +8,16 @@ def test_list(self): client = test_client().requisitions client.list() - client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/requisitions/", params=None) + client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/v2/requisitions/", params=None) client.list(limit=1) - client.request_strategy.get.assert_called_with("https://ob.nordigen.com/api/requisitions/?limit=1", params=None) + client.request_strategy.get.assert_called_with( + "https://ob.nordigen.com/api/v2/requisitions/?limit=1", params=None + ) client.list(offset=5) client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/requisitions/?offset=5", params=None + "https://ob.nordigen.com/api/v2/requisitions/?offset=5", params=None ) def test_by_id(self): @@ -23,7 +25,7 @@ def test_by_id(self): client.by_id("foobar-id") client.request_strategy.get.assert_called_with( - "https://ob.nordigen.com/api/requisitions/foobar-id/", params=None + "https://ob.nordigen.com/api/v2/requisitions/foobar-id/", params=None ) def test_remove(self): @@ -31,7 +33,7 @@ def test_remove(self): client.remove("foobar-id") client.request_strategy.delete.assert_called_with( - "https://ob.nordigen.com/api/requisitions/foobar-id/", params=None + "https://ob.nordigen.com/api/v2/requisitions/foobar-id/", params=None ) def test_create(self): @@ -48,7 +50,7 @@ def test_create(self): ) client.request_strategy.post.assert_called_with( - "https://ob.nordigen.com/api/requisitions/", + "https://ob.nordigen.com/api/v2/requisitions/", data={ "redirect": "redirect", "agreements": "agreements", @@ -64,7 +66,7 @@ def test_initiate(self): client.initiate("foobar-id", "aspsp_id") client.request_strategy.post.assert_called_with( - "https://ob.nordigen.com/api/requisitions/foobar-id/links/", + "https://ob.nordigen.com/api/v2/requisitions/foobar-id/links/", data={ "aspsp_id": "aspsp_id", },