Skip to content

Commit 883555c

Browse files
committed
Fixturify _credentials_basic_handler
1 parent dd5058a commit 883555c

File tree

5 files changed

+78
-62
lines changed

5 files changed

+78
-62
lines changed

openeo/rest/auth/testing.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
"""
44

55
import base64
6-
import contextlib
6+
import dataclasses
77
import json
88
import urllib.parse
99
import uuid
1010
from typing import List, Optional, Union
11-
from unittest import mock
1211

1312
import requests
1413
import requests_mock.request
@@ -290,3 +289,37 @@ def get_request_history(
290289
for r in self.requests_mock.request_history
291290
if (method is None or method.lower() == r.method.lower()) and (url is None or url == r.url)
292291
]
292+
293+
294+
def build_basic_auth_header(username: str, password: str) -> str:
295+
"""Generate basic auth header (per RFC 7617) from given username and password."""
296+
credentials = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")
297+
return f"Basic {credentials}"
298+
299+
300+
@dataclasses.dataclass(frozen=True)
301+
class SimpleBasicAuthMocker:
302+
"""
303+
Helper to create a test fixture for simple basic auth handling in openEO context:
304+
set up `/credentials/basic` handling with a fixed username/password/access_token combo.
305+
"""
306+
307+
username: str = "john"
308+
password: str = "j0hn!"
309+
access_token: str = "6cc3570k3n"
310+
311+
def expected_auth_header(self) -> str:
312+
return build_basic_auth_header(username=self.username, password=self.password)
313+
314+
def setup_credentials_basic_handler(self, *, api_root: str, requests_mock):
315+
"""Set up `requests_mock` handler for `/credentials/basic` endpoint."""
316+
expected_auth_header = self.expected_auth_header()
317+
318+
def credentials_basic_handler(request, context):
319+
assert request.headers["Authorization"] == expected_auth_header
320+
return json.dumps({"access_token": self.access_token})
321+
322+
return requests_mock.get(
323+
url_join(api_root, "/credentials/basic"),
324+
text=credentials_basic_handler,
325+
)

tests/rest/auth/test_testing.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
OidcClientInfo,
44
OidcProviderInfo,
55
)
6-
from openeo.rest.auth.testing import OidcMock
6+
from openeo.rest.auth.testing import OidcMock, build_basic_auth_header
77

88

99
class TestOidcMock:
@@ -33,3 +33,7 @@ def test_request_history(self, requests_mock):
3333
assert [r.url for r in oidc_mock.get_request_history("/token")] == [
3434
"https://oidc.test/token"
3535
]
36+
37+
38+
def test_build_basic_auth_header():
39+
assert build_basic_auth_header("john", "56(r61!?") == "Basic am9objo1NihyNjEhPw=="

tests/rest/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import contextlib
2+
import dataclasses
3+
import json
24
import re
35
import typing
46
from unittest import mock
@@ -7,7 +9,9 @@
79
import time_machine
810

911
from openeo.rest._testing import DummyBackend, build_capabilities
12+
from openeo.rest.auth.testing import SimpleBasicAuthMocker, build_basic_auth_header
1013
from openeo.rest.connection import Connection
14+
from openeo.util import url_join
1115

1216
API_URL = "https://oeo.test/"
1317

@@ -119,3 +123,14 @@ def another_dummy_backend(requests_mock) -> DummyBackend:
119123
another_dummy_backend.setup_file_format("GTiff")
120124
another_dummy_backend.setup_file_format("netCDF")
121125
return another_dummy_backend
126+
127+
128+
@pytest.fixture
129+
def basic_auth(requests_mock) -> SimpleBasicAuthMocker:
130+
"""
131+
Fixture for simple basic auth handling in openEO context:
132+
set up /credentials/basic handling with a fixed username/password/access_token combo.
133+
"""
134+
fixture = SimpleBasicAuthMocker()
135+
fixture.setup_credentials_basic_handler(api_root=API_URL, requests_mock=requests_mock)
136+
return fixture

tests/rest/test_connection.py

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pathlib import Path
1212

1313
import pytest
14-
import requests.auth
14+
import requests
1515
import requests_mock
1616
import shapely.geometry
1717

@@ -30,7 +30,7 @@
3030
from openeo.rest._testing import DummyBackend, build_capabilities
3131
from openeo.rest.auth.auth import BearerAuth, NullAuth
3232
from openeo.rest.auth.oidc import OidcException
33-
from openeo.rest.auth.testing import ABSENT, OidcMock
33+
from openeo.rest.auth.testing import ABSENT, OidcMock, SimpleBasicAuthMocker
3434
from openeo.rest.connection import (
3535
DEFAULT_TIMEOUT,
3636
DEFAULT_TIMEOUT_SYNCHRONOUS_EXECUTE,
@@ -564,10 +564,8 @@ def _get_capabilities_auth_dependent(request, context):
564564
return capabilities
565565

566566

567-
def test_capabilities_caching_after_authenticate_basic(requests_mock):
568-
user, pwd = "john262", "J0hndo3"
567+
def test_capabilities_caching_after_authenticate_basic(requests_mock, basic_auth):
569568
get_capabilities_mock = requests_mock.get(API_URL, json=_get_capabilities_auth_dependent)
570-
requests_mock.get(API_URL + 'credentials/basic', text=_credentials_basic_handler(user, pwd))
571569

572570
con = Connection(API_URL)
573571
assert con.capabilities().capabilities["endpoints"] == [
@@ -578,7 +576,7 @@ def test_capabilities_caching_after_authenticate_basic(requests_mock):
578576
con.capabilities()
579577
assert get_capabilities_mock.call_count == 1
580578

581-
con.authenticate_basic(username=user, password=pwd)
579+
con.authenticate_basic(username=basic_auth.username, password=basic_auth.password)
582580
assert get_capabilities_mock.call_count == 1
583581
assert con.capabilities().capabilities["endpoints"] == [
584582
{"methods": ["GET"], "path": "/credentials/basic"},
@@ -715,30 +713,17 @@ def test_api_error_non_json(requests_mock):
715713
assert exc.message == "olapola"
716714

717715

718-
def _credentials_basic_handler(username, password, access_token="w3lc0m3"):
719-
# TODO: better reuse of this helper
720-
expected_auth = requests.auth._basic_auth_str(username=username, password=password)
721-
722-
def handler(request, context):
723-
assert request.headers["Authorization"] == expected_auth
724-
return json.dumps({"access_token": access_token})
725-
726-
return handler
727-
728-
729-
def test_create_connection_lazy_auth_config(requests_mock, api_version):
730-
user, pwd = "john262", "J0hndo3"
716+
def test_create_connection_lazy_auth_config(requests_mock, api_version, basic_auth):
731717
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})
732-
requests_mock.get(API_URL + 'credentials/basic', text=_credentials_basic_handler(user, pwd))
733718

734719
with mock.patch('openeo.rest.connection.AuthConfig') as AuthConfig:
735720
# Don't create default AuthConfig when not necessary
736721
conn = Connection(API_URL)
737722
assert AuthConfig.call_count == 0
738-
conn.authenticate_basic(user, pwd)
723+
conn.authenticate_basic(basic_auth.username, basic_auth.password)
739724
assert AuthConfig.call_count == 0
740725
# call `authenticate_basic` so that fallback AuthConfig is created/used lazily
741-
AuthConfig.return_value.get_basic_auth.return_value = (user, pwd)
726+
AuthConfig.return_value.get_basic_auth.return_value = (basic_auth.username, basic_auth.password)
742727
conn.authenticate_basic()
743728
assert AuthConfig.call_count == 1
744729
conn.authenticate_basic()
@@ -785,29 +770,25 @@ def test_authenticate_basic_no_support(requests_mock, api_version):
785770
assert isinstance(conn.auth, NullAuth)
786771

787772

788-
def test_authenticate_basic(requests_mock, api_version):
789-
user, pwd = "john262", "J0hndo3"
773+
def test_authenticate_basic(requests_mock, api_version, basic_auth):
790774
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})
791-
requests_mock.get(API_URL + 'credentials/basic', text=_credentials_basic_handler(user, pwd))
792775

793776
conn = Connection(API_URL)
794777
assert isinstance(conn.auth, NullAuth)
795-
conn.authenticate_basic(username=user, password=pwd)
778+
conn.authenticate_basic(username=basic_auth.username, password=basic_auth.password)
796779
assert isinstance(conn.auth, BearerAuth)
797-
assert conn.auth.bearer == "basic//w3lc0m3"
780+
assert conn.auth.bearer == "basic//6cc3570k3n"
798781

799782

800-
def test_authenticate_basic_from_config(requests_mock, api_version, auth_config):
801-
user, pwd = "john281", "J0hndo3"
783+
def test_authenticate_basic_from_config(requests_mock, api_version, auth_config, basic_auth):
802784
requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS})
803-
requests_mock.get(API_URL + 'credentials/basic', text=_credentials_basic_handler(user, pwd))
804-
auth_config.set_basic_auth(backend=API_URL, username=user, password=pwd)
785+
auth_config.set_basic_auth(backend=API_URL, username=basic_auth.username, password=basic_auth.password)
805786

806787
conn = Connection(API_URL)
807788
assert isinstance(conn.auth, NullAuth)
808789
conn.authenticate_basic()
809790
assert isinstance(conn.auth, BearerAuth)
810-
assert conn.auth.bearer == "basic//w3lc0m3"
791+
assert conn.auth.bearer == "basic//6cc3570k3n"
811792

812793

813794
@pytest.mark.slow
@@ -3966,9 +3947,10 @@ def test_connect_auto_auth_from_config_basic(
39663947
"""))
39673948
user, pwd = "john", "j0hn"
39683949
for u, a in [(default, "Hell0!"), (other, "Wazz6!")]:
3969-
auth_config.set_basic_auth(backend=u, username=user, password=pwd)
3950+
basic_auth_mocker = SimpleBasicAuthMocker(username=user, password=pwd, access_token=a)
3951+
auth_config.set_basic_auth(backend=u, username=basic_auth_mocker.username, password=basic_auth_mocker.password)
39703952
requests_mock.get(u, json={"api_version": "1.0.0", "endpoints": BASIC_ENDPOINTS})
3971-
requests_mock.get(f"{u}/credentials/basic", text=_credentials_basic_handler(user, pwd, access_token=a))
3953+
basic_auth_mocker.setup_credentials_basic_handler(api_root=u, requests_mock=requests_mock)
39723954

39733955
if use_default:
39743956
# Without arguments: use default

tests/rest/test_job.py

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
from openeo.rest.job import BatchJob, ResultAsset
1414
from openeo.rest.models.general import Link
1515

16-
from .test_connection import _credentials_basic_handler
17-
1816
API_URL = "https://oeo.test"
1917

2018
TIFF_CONTENT = b'T1f7D6t6l0l' * 1000
@@ -719,26 +717,18 @@ def download_tiff(request, context):
719717
assert f.read() == TIFF_CONTENT
720718

721719

720+
722721
@pytest.mark.parametrize(
723722
["list_jobs_kwargs", "expected_qs"],
724723
[
725724
({}, {}),
726725
({"limit": 123}, {"limit": ["123"]}),
727726
],
728727
)
729-
def test_list_jobs(con100, requests_mock, list_jobs_kwargs, expected_qs):
730-
username = "john"
731-
password = "j0hn!"
732-
access_token = "6cc35!"
733-
requests_mock.get(
734-
API_URL + "/credentials/basic",
735-
text=_credentials_basic_handler(
736-
username=username, password=password, access_token=access_token
737-
),
738-
)
728+
def test_list_jobs(con100, requests_mock, list_jobs_kwargs, expected_qs, basic_auth):
739729

740730
def get_jobs(request, context):
741-
assert request.headers["Authorization"] == f"Bearer basic//{access_token}"
731+
assert request.headers["Authorization"] == f"Bearer basic//{basic_auth.access_token}"
742732
assert request.qs == expected_qs
743733
return {
744734
"jobs": [
@@ -757,26 +747,18 @@ def get_jobs(request, context):
757747

758748
requests_mock.get(API_URL + "/jobs", json=get_jobs)
759749

760-
con100.authenticate_basic(username, password)
750+
con100.authenticate_basic(basic_auth.username, basic_auth.password)
761751
jobs = con100.list_jobs(**list_jobs_kwargs)
762752
assert jobs == [
763753
{"id": "job123", "status": "running", "created": "2021-02-22T09:00:00Z"},
764754
{"id": "job456", "status": "created", "created": "2021-03-22T10:00:00Z"},
765755
]
766756

767757

768-
def test_list_jobs_extra_metadata(con100, requests_mock, caplog):
769-
# TODO: avoid this boilerplate duplication
770-
username = "john"
771-
password = "j0hn!"
772-
access_token = "6cc35!"
773-
requests_mock.get(
774-
API_URL + "/credentials/basic",
775-
text=_credentials_basic_handler(username=username, password=password, access_token=access_token),
776-
)
758+
def test_list_jobs_extra_metadata(con100, requests_mock, caplog, basic_auth):
777759

778760
def get_jobs(request, context):
779-
assert request.headers["Authorization"] == f"Bearer basic//{access_token}"
761+
assert request.headers["Authorization"] == f"Bearer basic//{basic_auth.access_token}"
780762
return {
781763
"jobs": [
782764
{
@@ -798,7 +780,7 @@ def get_jobs(request, context):
798780

799781
requests_mock.get(API_URL + "/jobs", json=get_jobs)
800782

801-
con100.authenticate_basic(username, password)
783+
con100.authenticate_basic(basic_auth.username, basic_auth.password)
802784
jobs = con100.list_jobs()
803785
assert jobs == [
804786
{"id": "job123", "status": "running", "created": "2021-02-22T09:00:00Z"},

0 commit comments

Comments
 (0)