From 7d4d09f95380d13fca03a01149996c425bbd0329 Mon Sep 17 00:00:00 2001 From: Marco Antonio Gil Date: Mon, 6 Apr 2026 15:07:21 +0200 Subject: [PATCH 1/3] PTHMINT-112: Improve E2E test environment isolation Use a dedicated E2E_API_KEY, centralize E2E SDK bootstrap in shared fixtures, and keep end-to-end tests isolated from the general SDK environment configuration. Update the E2E documentation and environment example accordingly. --- .env.example | 3 +- README.md | 13 +++++ tests/multisafepay/e2e/conftest.py | 50 +++++++++++++++++-- .../auth_manager/test_get_api_token.py | 9 +--- .../test_capture_reservation_cancel.py | 8 +-- .../category_manager/test_get_categories.py | 9 +--- .../gateway_manager/test_get_by_code.py | 9 +--- .../gateway_manager/test_get_gateways.py | 9 +--- .../test_get_issuers_by_gateway_code.py | 9 +--- .../e2e/examples/me_manager/test_get_me.py | 9 +--- .../examples/order_manager/test_capture.py | 9 +--- .../e2e/examples/order_manager/test_create.py | 9 +--- .../examples/order_manager/test_get_order.py | 9 +--- .../e2e/examples/order_manager/test_refund.py | 9 +--- .../order_manager/test_refund_by_item.py | 9 +--- .../test_refund_by_shopping_cart.py | 9 +--- .../e2e/examples/order_manager/test_update.py | 9 +--- .../test_get_by_gateway_code.py | 9 +--- .../test_get_payment_methods.py | 9 +--- .../recurring_manager/test_recurring.py | 8 +-- .../test_get_transactions.py | 9 +--- .../transport/test_custom_httpx_transport.py | 20 +++----- .../test_custom_requests_session_transport.py | 12 ++--- .../test_custom_urllib3_transport.py | 20 +++----- 24 files changed, 114 insertions(+), 164 deletions(-) diff --git a/.env.example b/.env.example index 2387a6f..9a4d503 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -API_KEY= \ No newline at end of file +API_KEY= +E2E_API_KEY= \ No newline at end of file diff --git a/README.md b/README.md index f49eae8..9e2b9ee 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,19 @@ make lint make test ``` +### E2E target environment + +E2E tests always target `https://testapi.multisafepay.com/v1/`. + +Use a dedicated API key for them instead of the general SDK key: + +```bash +export E2E_API_KEY="" +make test-e2e +``` + +The e2e suite does not use the shared `API_KEY` variable. + ## Support Create an issue on this repository or email diff --git a/tests/multisafepay/e2e/conftest.py b/tests/multisafepay/e2e/conftest.py index 2f6e08f..dba891b 100644 --- a/tests/multisafepay/e2e/conftest.py +++ b/tests/multisafepay/e2e/conftest.py @@ -1,29 +1,71 @@ """Configuration for end-to-end tests.""" import os +from collections.abc import Callable +from typing import Optional import pytest from dotenv import load_dotenv +from multisafepay.sdk import Sdk +from multisafepay.transport import HTTPTransport + +E2E_API_KEY_ENV = "E2E_API_KEY" + # Load .env file from the project root load_dotenv() +def _get_e2e_api_key() -> str: + return os.getenv(E2E_API_KEY_ENV, "").strip() + + +@pytest.fixture(scope="session") +def e2e_api_key() -> str: + """Return the dedicated API key used by E2E tests.""" + api_key = _get_e2e_api_key() + if not api_key: + pytest.skip(f"E2E tests require {E2E_API_KEY_ENV} (not set)") + + return api_key + + +@pytest.fixture(scope="session") +def e2e_sdk_factory(e2e_api_key: str) -> Callable[..., Sdk]: + """Create SDK instances for E2E tests with a shared configuration.""" + + def create_sdk(*, transport: Optional[HTTPTransport] = None) -> Sdk: + return Sdk( + api_key=e2e_api_key, + is_production=False, + transport=transport, + ) + + return create_sdk + + +@pytest.fixture(scope="session") +def e2e_sdk(e2e_sdk_factory: Callable[..., Sdk]) -> Sdk: + """Return the default SDK instance used by E2E tests.""" + return e2e_sdk_factory() + + def pytest_collection_modifyitems( config: pytest.Config, # noqa: ARG001 items: list[pytest.Item], ) -> None: """ - Skip all e2e tests when API_KEY is missing. + Skip all e2e tests when E2E_API_KEY is missing. These tests perform real API calls. In most local/CI environments the secret isn't present, so we prefer a clean skip over hard errors during fixture setup. """ - api_key = os.getenv("API_KEY") - if api_key and api_key.strip(): + if _get_e2e_api_key(): return - skip = pytest.mark.skip(reason="E2E tests require API_KEY (not set)") + skip = pytest.mark.skip( + reason=f"E2E tests require {E2E_API_KEY_ENV} (not set)", + ) for item in items: # This hook runs for the whole session (all collected tests), even when # this conftest is only loaded due to e2e tests being present/deselected. diff --git a/tests/multisafepay/e2e/examples/auth_manager/test_get_api_token.py b/tests/multisafepay/e2e/examples/auth_manager/test_get_api_token.py index 4c61511..9bab06b 100644 --- a/tests/multisafepay/e2e/examples/auth_manager/test_get_api_token.py +++ b/tests/multisafepay/e2e/examples/auth_manager/test_get_api_token.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, @@ -21,12 +19,9 @@ @pytest.fixture(scope="module") -def auth_manager() -> AuthManager: +def auth_manager(e2e_sdk: Sdk) -> AuthManager: """Fixture that provides an AuthManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_auth_manager() + return e2e_sdk.get_auth_manager() def test_get_api_token(auth_manager: AuthManager): diff --git a/tests/multisafepay/e2e/examples/capture_manager/test_capture_reservation_cancel.py b/tests/multisafepay/e2e/examples/capture_manager/test_capture_reservation_cancel.py index bfc7c33..49fc99a 100644 --- a/tests/multisafepay/e2e/examples/capture_manager/test_capture_reservation_cancel.py +++ b/tests/multisafepay/e2e/examples/capture_manager/test_capture_reservation_cancel.py @@ -8,12 +8,10 @@ """Test module for e2e testing.""" -import os import time import pytest from typing import TYPE_CHECKING -from dotenv import load_dotenv from multisafepay.api.paths.capture.response.capture import CancelReservation from multisafepay.api.paths.capture.request.capture_request import ( CaptureRequest, @@ -49,11 +47,9 @@ @pytest.fixture(scope="module") -def sdk() -> Sdk: +def sdk(e2e_sdk: Sdk) -> Sdk: """Fixture that provides an SDK instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - return Sdk(api_key, False) + return e2e_sdk def test_capture_reservation_cancel(sdk: Sdk): diff --git a/tests/multisafepay/e2e/examples/category_manager/test_get_categories.py b/tests/multisafepay/e2e/examples/category_manager/test_get_categories.py index 3f8019a..5969ff1 100644 --- a/tests/multisafepay/e2e/examples/category_manager/test_get_categories.py +++ b/tests/multisafepay/e2e/examples/category_manager/test_get_categories.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, @@ -24,12 +22,9 @@ @pytest.fixture(scope="module") -def category_manager() -> "CategoryManager": +def category_manager(e2e_sdk: Sdk) -> "CategoryManager": """Fixture that provides a CategoryManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_category_manager() + return e2e_sdk.get_category_manager() def test_get_categories(category_manager: CategoryManager): diff --git a/tests/multisafepay/e2e/examples/gateway_manager/test_get_by_code.py b/tests/multisafepay/e2e/examples/gateway_manager/test_get_by_code.py index 733b27f..0746a10 100644 --- a/tests/multisafepay/e2e/examples/gateway_manager/test_get_by_code.py +++ b/tests/multisafepay/e2e/examples/gateway_manager/test_get_by_code.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, ) @@ -20,12 +18,9 @@ @pytest.fixture(scope="module") -def gateway_manager() -> GatewayManager: +def gateway_manager(e2e_sdk: Sdk) -> GatewayManager: """Fixture that provides a GatewayManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_gateway_manager() + return e2e_sdk.get_gateway_manager() def test_get_by_code(gateway_manager: GatewayManager): diff --git a/tests/multisafepay/e2e/examples/gateway_manager/test_get_gateways.py b/tests/multisafepay/e2e/examples/gateway_manager/test_get_gateways.py index 66f3062..9542088 100644 --- a/tests/multisafepay/e2e/examples/gateway_manager/test_get_gateways.py +++ b/tests/multisafepay/e2e/examples/gateway_manager/test_get_gateways.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, ) @@ -20,12 +18,9 @@ @pytest.fixture(scope="module") -def gateway_manager() -> GatewayManager: +def gateway_manager(e2e_sdk: Sdk) -> GatewayManager: """Fixture that provides a GatewayManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_gateway_manager() + return e2e_sdk.get_gateway_manager() def test_get_gateways(gateway_manager: GatewayManager): diff --git a/tests/multisafepay/e2e/examples/issuer_manager/test_get_issuers_by_gateway_code.py b/tests/multisafepay/e2e/examples/issuer_manager/test_get_issuers_by_gateway_code.py index 8170258..eefae7d 100644 --- a/tests/multisafepay/e2e/examples/issuer_manager/test_get_issuers_by_gateway_code.py +++ b/tests/multisafepay/e2e/examples/issuer_manager/test_get_issuers_by_gateway_code.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.paths.issuers.response.issuer import Issuer from multisafepay.api.base.response.custom_api_response import ( @@ -21,12 +19,9 @@ @pytest.fixture(scope="module") -def issuer_manager() -> IssuerManager: +def issuer_manager(e2e_sdk: Sdk) -> IssuerManager: """Fixture that provides an IssuerManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_issuer_manager() + return e2e_sdk.get_issuer_manager() def test_get_issuers_by_gateway_code(issuer_manager: IssuerManager): diff --git a/tests/multisafepay/e2e/examples/me_manager/test_get_me.py b/tests/multisafepay/e2e/examples/me_manager/test_get_me.py index 57a6b8e..b529bb1 100644 --- a/tests/multisafepay/e2e/examples/me_manager/test_get_me.py +++ b/tests/multisafepay/e2e/examples/me_manager/test_get_me.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.paths.me.response.me import Me @@ -24,12 +22,9 @@ @pytest.fixture(scope="module") -def me_manager() -> "MeManager": +def me_manager(e2e_sdk: Sdk) -> "MeManager": """Fixture that provides a MeManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_me_manager() + return e2e_sdk.get_me_manager() def test_get_me(me_manager: MeManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_capture.py b/tests/multisafepay/e2e/examples/order_manager/test_capture.py index 124ad18..73362dc 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_capture.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_capture.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from typing import TYPE_CHECKING @@ -45,12 +43,9 @@ @pytest.fixture(scope="module") -def order_manager() -> OrderManager: +def order_manager(e2e_sdk: Sdk) -> OrderManager: """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_capture(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_create.py b/tests/multisafepay/e2e/examples/order_manager/test_create.py index 88ef015..10db37c 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_create.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_create.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.value_object.weight import Weight from multisafepay.api.shared.cart.cart_item import CartItem @@ -43,12 +41,9 @@ @pytest.fixture(scope="module") -def order_manager() -> "OrderManager": +def order_manager(e2e_sdk: Sdk) -> "OrderManager": """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_create_order(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_get_order.py b/tests/multisafepay/e2e/examples/order_manager/test_get_order.py index 0f3ce3c..e62240a 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_get_order.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_get_order.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.api.paths.orders.order_manager import OrderManager from multisafepay.value_object.weight import Weight from multisafepay.api.shared.cart.cart_item import CartItem @@ -40,12 +38,9 @@ @pytest.fixture(scope="module") -def order_manager() -> OrderManager: +def order_manager(e2e_sdk: Sdk) -> OrderManager: """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_get_order(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_refund.py b/tests/multisafepay/e2e/examples/order_manager/test_refund.py index 0c7d344..c804b73 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_refund.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_refund.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.api.paths.orders.order_manager import OrderManager from multisafepay.api.paths.orders.request.components.payment_options import ( PaymentOptions, @@ -37,12 +35,9 @@ @pytest.fixture(scope="module") -def order_manager() -> OrderManager: +def order_manager(e2e_sdk: Sdk) -> OrderManager: """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_refund(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_refund_by_item.py b/tests/multisafepay/e2e/examples/order_manager/test_refund_by_item.py index 1503a4e..626cb32 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_refund_by_item.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_refund_by_item.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.api.paths.orders.order_manager import OrderManager from multisafepay.value_object.weight import Weight from multisafepay.api.shared.cart.cart_item import CartItem @@ -40,12 +38,9 @@ @pytest.fixture(scope="module") -def order_manager() -> OrderManager: +def order_manager(e2e_sdk: Sdk) -> OrderManager: """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_refund_by_item(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_refund_by_shopping_cart.py b/tests/multisafepay/e2e/examples/order_manager/test_refund_by_shopping_cart.py index 3a56268..7b48d23 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_refund_by_shopping_cart.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_refund_by_shopping_cart.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.api.paths.orders.order_manager import OrderManager from multisafepay.value_object.weight import Weight from multisafepay.api.shared.cart.cart_item import CartItem @@ -40,12 +38,9 @@ @pytest.fixture(scope="module") -def order_manager() -> OrderManager: +def order_manager(e2e_sdk: Sdk) -> OrderManager: """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_refund_by_shopping_cart(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/order_manager/test_update.py b/tests/multisafepay/e2e/examples/order_manager/test_update.py index 459da18..fe59cb4 100644 --- a/tests/multisafepay/e2e/examples/order_manager/test_update.py +++ b/tests/multisafepay/e2e/examples/order_manager/test_update.py @@ -8,10 +8,8 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.api.paths.orders.order_manager import OrderManager from multisafepay.value_object.weight import Weight from multisafepay.api.shared.cart.cart_item import CartItem @@ -43,12 +41,9 @@ @pytest.fixture(scope="module") -def order_manager() -> OrderManager: +def order_manager(e2e_sdk: Sdk) -> OrderManager: """Fixture that provides an OrderManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_order_manager() + return e2e_sdk.get_order_manager() def test_update(order_manager: OrderManager): diff --git a/tests/multisafepay/e2e/examples/payment_method_manager/test_get_by_gateway_code.py b/tests/multisafepay/e2e/examples/payment_method_manager/test_get_by_gateway_code.py index 7361c6e..3406685 100644 --- a/tests/multisafepay/e2e/examples/payment_method_manager/test_get_by_gateway_code.py +++ b/tests/multisafepay/e2e/examples/payment_method_manager/test_get_by_gateway_code.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.paths.payment_methods.response.payment_method import ( PaymentMethod, ) @@ -25,12 +23,9 @@ @pytest.fixture(scope="module") -def payment_method_manager() -> PaymentMethodManager: +def payment_method_manager(e2e_sdk: Sdk) -> PaymentMethodManager: """Fixture that provides a PaymentMethodManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_payment_method_manager() + return e2e_sdk.get_payment_method_manager() def test_get_by_gateway_code(payment_method_manager: PaymentMethodManager): diff --git a/tests/multisafepay/e2e/examples/payment_method_manager/test_get_payment_methods.py b/tests/multisafepay/e2e/examples/payment_method_manager/test_get_payment_methods.py index 26e998a..37ce344 100644 --- a/tests/multisafepay/e2e/examples/payment_method_manager/test_get_payment_methods.py +++ b/tests/multisafepay/e2e/examples/payment_method_manager/test_get_payment_methods.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, ) @@ -24,12 +22,9 @@ @pytest.fixture(scope="module") -def payment_method_manager(): +def payment_method_manager(e2e_sdk: Sdk): """Fixture that provides a PaymentMethodManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_payment_method_manager() + return e2e_sdk.get_payment_method_manager() def test_get_payment_methods(payment_method_manager: PaymentMethodManager): diff --git a/tests/multisafepay/e2e/examples/recurring_manager/test_recurring.py b/tests/multisafepay/e2e/examples/recurring_manager/test_recurring.py index f15b444..a5a02ac 100644 --- a/tests/multisafepay/e2e/examples/recurring_manager/test_recurring.py +++ b/tests/multisafepay/e2e/examples/recurring_manager/test_recurring.py @@ -8,11 +8,9 @@ """Test module for e2e testing.""" -import os import time import pytest -from dotenv import load_dotenv from multisafepay.api.paths.orders.request.components.payment_options import ( PaymentOptions, ) @@ -35,11 +33,9 @@ @pytest.fixture(scope="module") -def sdk() -> Sdk: +def sdk(e2e_sdk: Sdk) -> Sdk: """Fixture that provides an SDK instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - return Sdk(api_key, False) + return e2e_sdk def test_recurring(sdk: Sdk): diff --git a/tests/multisafepay/e2e/examples/transaction_manager/test_get_transactions.py b/tests/multisafepay/e2e/examples/transaction_manager/test_get_transactions.py index 96bb10a..b344e9a 100644 --- a/tests/multisafepay/e2e/examples/transaction_manager/test_get_transactions.py +++ b/tests/multisafepay/e2e/examples/transaction_manager/test_get_transactions.py @@ -8,9 +8,7 @@ """Test module for e2e testing.""" -import os import pytest -from dotenv import load_dotenv from multisafepay.api.base.listings.listing_pager import ListingPager from multisafepay.api.base.response.custom_api_response import ( @@ -26,12 +24,9 @@ @pytest.fixture(scope="module") -def transaction_manager(): +def transaction_manager(e2e_sdk: Sdk): """Fixture that provides a TransactionManager instance for testing.""" - load_dotenv() - api_key = os.getenv("API_KEY") - multisafepay_sdk = Sdk(api_key, False) - return multisafepay_sdk.get_transaction_manager() + return e2e_sdk.get_transaction_manager() def test_retrieves_all_transactions(transaction_manager: TransactionManager): diff --git a/tests/multisafepay/e2e/examples/transport/test_custom_httpx_transport.py b/tests/multisafepay/e2e/examples/transport/test_custom_httpx_transport.py index bfc45a2..4f940f1 100644 --- a/tests/multisafepay/e2e/examples/transport/test_custom_httpx_transport.py +++ b/tests/multisafepay/e2e/examples/transport/test_custom_httpx_transport.py @@ -9,7 +9,7 @@ E2E: injected transport using httpx (sync). This test performs a REAL call against the MultiSafepay test environment. -It runs only when `API_KEY` is set (see `tests/multisafepay/e2e/conftest.py`). +It runs only when `E2E_API_KEY` is set (see `tests/multisafepay/e2e/conftest.py`). What this validates ------------------- @@ -24,42 +24,36 @@ from __future__ import annotations -import os from typing import TYPE_CHECKING import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, ) from multisafepay.api.paths.gateways.response.gateway import Gateway -from multisafepay.sdk import Sdk - from tests.support.alt_http_transports import HttpxTransport if TYPE_CHECKING: + from collections.abc import Callable + from multisafepay.api.paths.gateways.gateway_manager import GatewayManager + from multisafepay.sdk import Sdk @pytest.fixture(scope="module") -def gateway_manager() -> GatewayManager: +def gateway_manager(e2e_sdk_factory: Callable[..., Sdk]) -> GatewayManager: """ Create a GatewayManager using an injected httpx-backed transport. - Skips if `httpx` is not installed or if `API_KEY` is not set. + Skips if `httpx` is not installed or if `E2E_API_KEY` is not set. """ pytest.importorskip("httpx") - load_dotenv() - api_key = os.getenv("API_KEY") - if not api_key: - pytest.skip("API_KEY env var not set") - # Ensure resources are cleaned up after the module. transport = HttpxTransport() try: - sdk = Sdk(api_key=api_key, is_production=False, transport=transport) + sdk = e2e_sdk_factory(transport=transport) yield sdk.get_gateway_manager() finally: transport.close() diff --git a/tests/multisafepay/e2e/examples/transport/test_custom_requests_session_transport.py b/tests/multisafepay/e2e/examples/transport/test_custom_requests_session_transport.py index 0dea939..16d446b 100644 --- a/tests/multisafepay/e2e/examples/transport/test_custom_requests_session_transport.py +++ b/tests/multisafepay/e2e/examples/transport/test_custom_requests_session_transport.py @@ -12,10 +12,9 @@ `RequestsTransport` and used end-to-end against the MultiSafepay test API. """ -import os +from collections.abc import Callable import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, @@ -27,20 +26,15 @@ @pytest.fixture(scope="module") -def gateway_manager() -> GatewayManager: +def gateway_manager(e2e_sdk_factory: Callable[..., Sdk]) -> GatewayManager: """Fixture that provides a GatewayManager instance using a custom requests.Session.""" requests = pytest.importorskip("requests") - load_dotenv() - api_key = os.getenv("API_KEY") - if not api_key: - pytest.skip("API_KEY env var not set") - session = requests.Session() session.headers.update({"User-Agent": "multisafepay-sdk-tests"}) transport = RequestsTransport(session=session) - multisafepay_sdk = Sdk(api_key, False, transport) + multisafepay_sdk = e2e_sdk_factory(transport=transport) try: yield multisafepay_sdk.get_gateway_manager() diff --git a/tests/multisafepay/e2e/examples/transport/test_custom_urllib3_transport.py b/tests/multisafepay/e2e/examples/transport/test_custom_urllib3_transport.py index 103c2e6..e25f687 100644 --- a/tests/multisafepay/e2e/examples/transport/test_custom_urllib3_transport.py +++ b/tests/multisafepay/e2e/examples/transport/test_custom_urllib3_transport.py @@ -9,7 +9,7 @@ E2E: injected transport using urllib3. This test performs a REAL call against the MultiSafepay test environment. -It runs only when `API_KEY` is set (see `tests/multisafepay/e2e/conftest.py`). +It runs only when `E2E_API_KEY` is set (see `tests/multisafepay/e2e/conftest.py`). What this validates ------------------- @@ -26,40 +26,34 @@ from __future__ import annotations -import os from typing import TYPE_CHECKING import pytest -from dotenv import load_dotenv from multisafepay.api.base.response.custom_api_response import ( CustomApiResponse, ) from multisafepay.api.paths.gateways.response.gateway import Gateway -from multisafepay.sdk import Sdk - from tests.support.alt_http_transports import Urllib3Transport if TYPE_CHECKING: + from collections.abc import Callable + from multisafepay.api.paths.gateways.gateway_manager import GatewayManager + from multisafepay.sdk import Sdk @pytest.fixture(scope="module") -def gateway_manager() -> GatewayManager: +def gateway_manager(e2e_sdk_factory: Callable[..., Sdk]) -> GatewayManager: """ Create a GatewayManager using an injected urllib3-backed transport. - Skips if `urllib3` is not installed or if `API_KEY` is not set. + Skips if `urllib3` is not installed or if `E2E_API_KEY` is not set. """ pytest.importorskip("urllib3") - load_dotenv() - api_key = os.getenv("API_KEY") - if not api_key: - pytest.skip("API_KEY env var not set") - transport = Urllib3Transport() - sdk = Sdk(api_key=api_key, is_production=False, transport=transport) + sdk = e2e_sdk_factory(transport=transport) return sdk.get_gateway_manager() From 213eaa9f5d3336dc0ce0f8d1269fbb51b8930159 Mon Sep 17 00:00:00 2001 From: Marco Antonio Gil Date: Mon, 13 Apr 2026 10:38:16 +0200 Subject: [PATCH 2/3] PTHMINT-112: Force E2E SDK to use test base URL Ensure E2E fixtures remain isolated from local custom-base-url overrides. The E2E SDK factory now temporarily clears MSP_SDK_BUILD_PROFILE, MSP_SDK_ALLOW_CUSTOM_BASE_URL and MSP_SDK_CUSTOM_BASE_URL while instantiating the SDK, then restores previous values. Added a guard to assert the resulting client URL is Client.TEST_URL, so E2E always targets the test environment when using E2E_API_KEY. --- tests/multisafepay/e2e/conftest.py | 48 ++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/tests/multisafepay/e2e/conftest.py b/tests/multisafepay/e2e/conftest.py index dba891b..9f07b62 100644 --- a/tests/multisafepay/e2e/conftest.py +++ b/tests/multisafepay/e2e/conftest.py @@ -1,16 +1,21 @@ """Configuration for end-to-end tests.""" import os -from collections.abc import Callable +from collections.abc import Callable, Iterator +from contextlib import contextmanager from typing import Optional import pytest from dotenv import load_dotenv +from multisafepay.client.client import Client from multisafepay.sdk import Sdk from multisafepay.transport import HTTPTransport E2E_API_KEY_ENV = "E2E_API_KEY" +BUILD_PROFILE_ENV = "MSP_SDK_BUILD_PROFILE" +ALLOW_CUSTOM_BASE_URL_ENV = "MSP_SDK_ALLOW_CUSTOM_BASE_URL" +CUSTOM_BASE_URL_ENV = "MSP_SDK_CUSTOM_BASE_URL" # Load .env file from the project root load_dotenv() @@ -20,6 +25,29 @@ def _get_e2e_api_key() -> str: return os.getenv(E2E_API_KEY_ENV, "").strip() +@contextmanager +def _without_custom_base_url_env() -> Iterator[None]: + """Temporarily disable custom base URL overrides for E2E SDK creation.""" + env_names = ( + BUILD_PROFILE_ENV, + ALLOW_CUSTOM_BASE_URL_ENV, + CUSTOM_BASE_URL_ENV, + ) + previous_values = {name: os.getenv(name) for name in env_names} + + for name in env_names: + os.environ.pop(name, None) + + try: + yield + finally: + for name, value in previous_values.items(): + if value is None: + os.environ.pop(name, None) + else: + os.environ[name] = value + + @pytest.fixture(scope="session") def e2e_api_key() -> str: """Return the dedicated API key used by E2E tests.""" @@ -35,11 +63,19 @@ def e2e_sdk_factory(e2e_api_key: str) -> Callable[..., Sdk]: """Create SDK instances for E2E tests with a shared configuration.""" def create_sdk(*, transport: Optional[HTTPTransport] = None) -> Sdk: - return Sdk( - api_key=e2e_api_key, - is_production=False, - transport=transport, - ) + with _without_custom_base_url_env(): + sdk = Sdk( + api_key=e2e_api_key, + is_production=False, + transport=transport, + ) + + if sdk.get_client().url != Client.TEST_URL: + raise RuntimeError( + "E2E SDK must use the test API base URL.", + ) + + return sdk return create_sdk From b94cd0213f469ee6563455604ca7f2ca6764a467 Mon Sep 17 00:00:00 2001 From: Marco Antonio Gil Date: Mon, 13 Apr 2026 12:20:23 +0200 Subject: [PATCH 3/3] PTHMINT-112: Isolate E2E config with dedicated E2E_BASE_URL --- .env.example | 3 +- README.md | 11 +++-- tests/multisafepay/e2e/conftest.py | 70 ++++++++++++++---------------- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/.env.example b/.env.example index 9a4d503..7a2af63 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ API_KEY= -E2E_API_KEY= \ No newline at end of file +E2E_API_KEY= +E2E_BASE_URL=https://testapi.multisafepay.com/v1/ diff --git a/README.md b/README.md index 9e2b9ee..a51384f 100644 --- a/README.md +++ b/README.md @@ -95,16 +95,21 @@ make test ### E2E target environment -E2E tests always target `https://testapi.multisafepay.com/v1/`. +By default, E2E tests target `https://testapi.multisafepay.com/v1/`. -Use a dedicated API key for them instead of the general SDK key: +Use dedicated E2E variables instead of the general SDK variables: ```bash export E2E_API_KEY="" +export E2E_BASE_URL="https://testapi.multisafepay.com/v1/" # optional make test-e2e ``` -The e2e suite does not use the shared `API_KEY` variable. +`E2E_BASE_URL` is optional and can point to any HTTPS base URL used for E2E. +When omitted, E2E defaults to `testapi.multisafepay.com`. + +The e2e suite does not use the shared `API_KEY` variable or the shared `MSP_SDK_*` +custom base URL settings. ## Support diff --git a/tests/multisafepay/e2e/conftest.py b/tests/multisafepay/e2e/conftest.py index 9f07b62..66d1a96 100644 --- a/tests/multisafepay/e2e/conftest.py +++ b/tests/multisafepay/e2e/conftest.py @@ -1,21 +1,19 @@ """Configuration for end-to-end tests.""" import os -from collections.abc import Callable, Iterator -from contextlib import contextmanager +from collections.abc import Callable from typing import Optional +from urllib.parse import urlparse import pytest from dotenv import load_dotenv -from multisafepay.client.client import Client +from multisafepay.client import Client from multisafepay.sdk import Sdk from multisafepay.transport import HTTPTransport E2E_API_KEY_ENV = "E2E_API_KEY" -BUILD_PROFILE_ENV = "MSP_SDK_BUILD_PROFILE" -ALLOW_CUSTOM_BASE_URL_ENV = "MSP_SDK_ALLOW_CUSTOM_BASE_URL" -CUSTOM_BASE_URL_ENV = "MSP_SDK_CUSTOM_BASE_URL" +E2E_BASE_URL_ENV = "E2E_BASE_URL" # Load .env file from the project root load_dotenv() @@ -25,27 +23,21 @@ def _get_e2e_api_key() -> str: return os.getenv(E2E_API_KEY_ENV, "").strip() -@contextmanager -def _without_custom_base_url_env() -> Iterator[None]: - """Temporarily disable custom base URL overrides for E2E SDK creation.""" - env_names = ( - BUILD_PROFILE_ENV, - ALLOW_CUSTOM_BASE_URL_ENV, - CUSTOM_BASE_URL_ENV, - ) - previous_values = {name: os.getenv(name) for name in env_names} +def _get_e2e_base_url() -> str: + base_url = os.getenv(E2E_BASE_URL_ENV, "").strip() + return base_url or Client.TEST_URL + - for name in env_names: - os.environ.pop(name, None) +def _validate_e2e_base_url(base_url: str) -> str: + parsed = urlparse(base_url) + if parsed.scheme != "https" or not parsed.netloc: + msg = f"{E2E_BASE_URL_ENV} must be a valid https URL" + raise pytest.UsageError(msg) - try: - yield - finally: - for name, value in previous_values.items(): - if value is None: - os.environ.pop(name, None) - else: - os.environ[name] = value + parsed = urlparse(base_url) + path = parsed.path.rstrip("/") + normalized_path = "/" if not path else f"{path}/" + return f"{parsed.scheme}://{parsed.netloc}{normalized_path}" @pytest.fixture(scope="session") @@ -59,21 +51,25 @@ def e2e_api_key() -> str: @pytest.fixture(scope="session") -def e2e_sdk_factory(e2e_api_key: str) -> Callable[..., Sdk]: +def e2e_base_url() -> str: + """Return the dedicated base URL used by E2E tests.""" + return _validate_e2e_base_url(_get_e2e_base_url()) + + +@pytest.fixture(scope="session") +def e2e_sdk_factory( + e2e_api_key: str, + e2e_base_url: str, +) -> Callable[..., Sdk]: """Create SDK instances for E2E tests with a shared configuration.""" def create_sdk(*, transport: Optional[HTTPTransport] = None) -> Sdk: - with _without_custom_base_url_env(): - sdk = Sdk( - api_key=e2e_api_key, - is_production=False, - transport=transport, - ) - - if sdk.get_client().url != Client.TEST_URL: - raise RuntimeError( - "E2E SDK must use the test API base URL.", - ) + sdk = Sdk( + api_key=e2e_api_key, + is_production=False, + transport=transport, + ) + sdk.get_client().url = e2e_base_url return sdk