diff --git a/services/payments/src/simcore_service_payments/_constants.py b/services/payments/src/simcore_service_payments/_constants.py index d2ac1f2b03c..a9bfd404c56 100644 --- a/services/payments/src/simcore_service_payments/_constants.py +++ b/services/payments/src/simcore_service_payments/_constants.py @@ -4,3 +4,6 @@ PAG: Final[str] = "Payments Gateway service" PGDB: Final[str] = "Postgres service" RUT: Final[str] = "Resource Usage Tracker service" + + +MSG_GATEWAY_UNAVAILABLE_ERROR = "Our payments provider is temporary innoperative" diff --git a/services/payments/src/simcore_service_payments/core/errors.py b/services/payments/src/simcore_service_payments/core/errors.py index 3a649829130..35a200138da 100644 --- a/services/payments/src/simcore_service_payments/core/errors.py +++ b/services/payments/src/simcore_service_payments/core/errors.py @@ -13,9 +13,9 @@ def get_full_class_name(cls) -> str: # -class PaymentsGatewayError(_BaseAppError): +class BasePaymentsGatewayError(_BaseAppError): ... -class PaymentsGatewayNotReadyError(PaymentsGatewayError): +class PaymentsGatewayNotReadyError(BasePaymentsGatewayError): msg_template = "Payments-Gateway is unresponsive: {checks}" diff --git a/services/payments/src/simcore_service_payments/core/settings.py b/services/payments/src/simcore_service_payments/core/settings.py index d8b89accb2e..9ae4b8a0793 100644 --- a/services/payments/src/simcore_service_payments/core/settings.py +++ b/services/payments/src/simcore_service_payments/core/settings.py @@ -53,7 +53,10 @@ class ApplicationSettings(_BaseApplicationSettings): """ PAYMENTS_GATEWAY_URL: HttpUrl = Field( - ..., description="Base url to the payment gateway" + ..., + description="Base url to the payment gateway." + "Used for both internal communication and " + "to get an external link to the gateway for the payment form", ) PAYMENTS_GATEWAY_API_SECRET: SecretStr = Field( diff --git a/services/payments/src/simcore_service_payments/db/payment_users_repo.py b/services/payments/src/simcore_service_payments/db/payment_users_repo.py index 2ebb951f130..7c98add9e50 100644 --- a/services/payments/src/simcore_service_payments/db/payment_users_repo.py +++ b/services/payments/src/simcore_service_payments/db/payment_users_repo.py @@ -1,5 +1,6 @@ import sqlalchemy as sa from models_library.users import GroupID, UserID +from pydantic import EmailStr from simcore_postgres_database.models.users import users from .base import BaseRepository @@ -20,3 +21,14 @@ async def get_primary_group_id(self, user_id: UserID) -> GroupID: msg = f"{user_id=} not found" raise ValueError(msg) return GroupID(row.primary_gid) + + async def get_name_and_email(self, user_id: UserID) -> tuple[str, EmailStr]: + async with self.db_engine.begin() as conn: + result = await conn.execute( + sa.select(users.c.name, users.c.email).where(users.c.id == user_id) + ) + row = result.first() + if row is None: + msg = f"{user_id=} not found" + raise ValueError(msg) + return row.name, EmailStr(row.email) diff --git a/services/payments/src/simcore_service_payments/models/utils.py b/services/payments/src/simcore_service_payments/models/utils.py index af5cbfe537e..d99736a07e8 100644 --- a/services/payments/src/simcore_service_payments/models/utils.py +++ b/services/payments/src/simcore_service_payments/models/utils.py @@ -6,6 +6,7 @@ def merge_models(got: GetPaymentMethod, acked: PaymentsMethodsDB) -> PaymentMethodGet: assert acked.completed_at # nosec + assert got.id == acked.payment_method_id # nosec return PaymentMethodGet( idr=acked.payment_method_id, diff --git a/services/payments/src/simcore_service_payments/services/auto_recharge_process_message.py b/services/payments/src/simcore_service_payments/services/auto_recharge_process_message.py index a4dae4eefe7..866615dac91 100644 --- a/services/payments/src/simcore_service_payments/services/auto_recharge_process_message.py +++ b/services/payments/src/simcore_service_payments/services/auto_recharge_process_message.py @@ -1,4 +1,5 @@ import logging +from contextlib import suppress from datetime import datetime, timedelta, timezone from decimal import Decimal from typing import cast @@ -16,6 +17,7 @@ from models_library.wallets import WalletID from pydantic import EmailStr, parse_obj_as, parse_raw_as from simcore_service_payments.db.auto_recharge_repo import AutoRechargeRepo +from simcore_service_payments.db.payment_users_repo import PaymentsUsersRepo from simcore_service_payments.db.payments_methods_repo import PaymentsMethodsRepo from simcore_service_payments.db.payments_transactions_repo import ( PaymentsTransactionsRepo, @@ -149,6 +151,15 @@ async def _perform_auto_recharge( payments_gateway = PaymentsGatewayApi.get_from_app_state(app) payments_transactions_repo = PaymentsTransactionsRepo(db_engine=app.state.engine) rut_api = ResourceUsageTrackerApi.get_from_app_state(app) + users_repo = PaymentsUsersRepo(db_engine=app.state.engine) + + # NOTE: this will probably be removed https://github.com/ITISFoundation/appmotion-exchange/issues/21 + user_name = f"id={payment_method_db.user_id}" + user_email = EmailStr(f"placeholder_{payment_method_db.user_id}@example.itis") + with suppress(ValueError): + user_name, user_email = await users_repo.get_name_and_email( + payment_method_db.user_id + ) await pay_with_payment_method( gateway=payments_gateway, @@ -162,7 +173,7 @@ async def _perform_auto_recharge( wallet_id=rabbit_message.wallet_id, wallet_name=f"id={rabbit_message.wallet_id}", user_id=payment_method_db.user_id, - user_name=f"id={payment_method_db.user_id}", - user_email=EmailStr(f"placeholder_{payment_method_db.user_id}@example.itis"), + user_name=user_name, + user_email=user_email, comment="Payment generated by auto recharge", ) diff --git a/services/payments/src/simcore_service_payments/services/healthchecks.py b/services/payments/src/simcore_service_payments/services/healthchecks.py index 98774700f44..680f2232917 100644 --- a/services/payments/src/simcore_service_payments/services/healthchecks.py +++ b/services/payments/src/simcore_service_payments/services/healthchecks.py @@ -1,6 +1,7 @@ import asyncio import logging +from fastapi import FastAPI from models_library.healthchecks import LivenessResult from sqlalchemy.ext.asyncio import AsyncEngine @@ -25,3 +26,32 @@ async def create_health_report( "resource_usage_tracker": rut_liveness, "postgres": db_liveness, } + + +async def _monitor_liveness(): + # + # + # logs with specific format so graylog can send alarm if found + # + # + raise NotImplementedError + + +async def _periodic(): + while True: + # do something + await _monitor_liveness() + # what if fails?, wait&repeat or stop-forever or cleanup&restart ? + + +def setup_healthchecks(app: FastAPI): + # setup _monitor_liveness as a periodic task in only one of the replicas + + async def _on_startup() -> None: + ... + + async def _on_shutdown() -> None: + ... + + app.add_event_handler("startup", _on_startup) + app.add_event_handler("shutdown", _on_shutdown) diff --git a/services/payments/src/simcore_service_payments/services/payments_gateway.py b/services/payments/src/simcore_service_payments/services/payments_gateway.py index e6dbc126f40..de72274184f 100644 --- a/services/payments/src/simcore_service_payments/services/payments_gateway.py +++ b/services/payments/src/simcore_service_payments/services/payments_gateway.py @@ -10,14 +10,15 @@ import logging from collections.abc import Callable from contextlib import suppress +from typing import Coroutine import httpx from fastapi import FastAPI from fastapi.encoders import jsonable_encoder from httpx import URL, HTTPStatusError +from models_library.api_schemas_payments.errors import PaymentServiceUnavailableError from models_library.api_schemas_webserver.wallets import PaymentID, PaymentMethodID from pydantic import ValidationError, parse_raw_as -from pydantic.errors import PydanticErrorMixin from servicelib.fastapi.app_state import SingletonInAppStateMixin from servicelib.fastapi.http_client import ( AttachLifespanMixin, @@ -28,7 +29,12 @@ from simcore_service_payments.models.schemas.acknowledgements import ( AckPaymentWithPaymentMethod, ) +from tenacity import AsyncRetrying, stop_after_delay, wait_exponential +from tenacity.retry import retry_if_exception_type +from tenacity.wait import wait_exponential +from .._constants import MSG_GATEWAY_UNAVAILABLE_ERROR, PAG +from ..core.errors import BasePaymentsGatewayError, PaymentsGatewayNotReadyError from ..core.settings import ApplicationSettings from ..models.payments_gateway import ( BatchGetPaymentMethods, @@ -52,13 +58,13 @@ def _parse_raw_as_or_none(cls: type, text: str | None): return None -class PaymentsGatewayError(PydanticErrorMixin, ValueError): +class PaymentsGatewayApiError(BasePaymentsGatewayError): msg_template = "{operation_id} error {status_code}: {reason}" @classmethod def from_http_status_error( cls, err: HTTPStatusError, operation_id: str - ) -> "PaymentsGatewayError": + ) -> "PaymentsGatewayApiError": return cls( operation_id=f"PaymentsGatewayApi.{operation_id}", reason=f"{err}", @@ -81,22 +87,37 @@ def get_detailed_message(self) -> str: @contextlib.contextmanager -def _raise_as_payments_gateway_error(operation_id: str): +def _reraise_as_service_errors_context(operation_id: str): try: yield - except HTTPStatusError as err: - error = PaymentsGatewayError.from_http_status_error( + except httpx.RequestError as err: + _logger.exception("%s: request error", PAG) + raise PaymentServiceUnavailableError( + human_readable_detail=MSG_GATEWAY_UNAVAILABLE_ERROR + ) from err + + except httpx.HTTPStatusError as err: + error = PaymentsGatewayApiError.from_http_status_error( err, operation_id=operation_id ) - _logger.warning(error.get_detailed_message()) - raise error from err + if err.response.is_client_error: + _logger.warning(error.get_detailed_message()) + raise error from err + + if err.response.is_server_error: + # 5XX in server -> turn into unavailable + _logger.exception(error.get_detailed_message()) + raise PaymentServiceUnavailableError( + human_readable_detail=MSG_GATEWAY_UNAVAILABLE_ERROR + ) from err -def _handle_status_errors(coro: Callable): + +def _handle_httpx_errors(coro: Callable): @functools.wraps(coro) async def _wrapper(self, *args, **kwargs): - with _raise_as_payments_gateway_error(operation_id=coro.__name__): + with _reraise_as_service_errors_context(operation_id=coro.__name__): return await coro(self, *args, **kwargs) return _wrapper @@ -120,7 +141,7 @@ class PaymentsGatewayApi( # api: one-time-payment workflow # - @_handle_status_errors + @_handle_httpx_errors async def init_payment(self, payment: InitPayment) -> PaymentInitiated: response = await self.client.post( "/init", @@ -132,7 +153,7 @@ async def init_payment(self, payment: InitPayment) -> PaymentInitiated: def get_form_payment_url(self, id_: PaymentID) -> URL: return self.client.base_url.copy_with(path="/pay", params={"id": f"{id_}"}) - @_handle_status_errors + @_handle_httpx_errors async def cancel_payment( self, payment_initiated: PaymentInitiated ) -> PaymentCancelled: @@ -147,7 +168,7 @@ async def cancel_payment( # api: payment method workflows # - @_handle_status_errors + @_handle_httpx_errors async def init_payment_method( self, payment_method: InitPaymentMethod, @@ -166,7 +187,7 @@ def get_form_payment_method_url(self, id_: PaymentMethodID) -> URL: # CRUD - @_handle_status_errors + @_handle_httpx_errors async def get_many_payment_methods( self, ids_: list[PaymentMethodID] ) -> list[GetPaymentMethod]: @@ -179,18 +200,18 @@ async def get_many_payment_methods( response.raise_for_status() return PaymentMethodsBatch.parse_obj(response.json()).items - @_handle_status_errors + @_handle_httpx_errors async def get_payment_method(self, id_: PaymentMethodID) -> GetPaymentMethod: response = await self.client.get(f"/payment-methods/{id_}") response.raise_for_status() return GetPaymentMethod.parse_obj(response.json()) - @_handle_status_errors + @_handle_httpx_errors async def delete_payment_method(self, id_: PaymentMethodID) -> None: response = await self.client.delete(f"/payment-methods/{id_}") response.raise_for_status() - @_handle_status_errors + @_handle_httpx_errors async def pay_with_payment_method( self, id_: PaymentMethodID, payment: InitPayment ) -> AckPaymentWithPaymentMethod: @@ -202,6 +223,27 @@ async def pay_with_payment_method( return AckPaymentWithPaymentMethod.parse_obj(response.json()) +def _create_start_policy(api: PaymentsGatewayApi) -> Callable[[], Coroutine]: + # Start policy: + # - this service will not be able to start if payments-gateway is alive + # + async def _(): + results = [] + async for attempt in AsyncRetrying( + wait=wait_exponential(max=3), + stop=stop_after_delay(max_delay=6), + retry=retry_if_exception_type(PaymentsGatewayNotReadyError), + reraise=True, + ): + with attempt: + alive = await api.check_liveness() + results.append(alive) + if not alive: + raise PaymentsGatewayNotReadyError(checks=results) + + return _ + + def setup_payments_gateway(app: FastAPI): assert app.state # nosec settings: ApplicationSettings = app.state.settings @@ -216,3 +258,5 @@ def setup_payments_gateway(app: FastAPI): ) api.attach_lifespan_to(app) api.set_to_app_state(app) + + app.add_event_handler("startup", _create_start_policy(api)) diff --git a/services/payments/src/simcore_service_payments/services/payments_methods.py b/services/payments/src/simcore_service_payments/services/payments_methods.py index 63a117e58ef..5d3ca24c737 100644 --- a/services/payments/src/simcore_service_payments/services/payments_methods.py +++ b/services/payments/src/simcore_service_payments/services/payments_methods.py @@ -172,9 +172,12 @@ async def list_payment_methods( [acked.payment_method_id for acked in acked_many] ) + # FIXME: if out-of-sync w/ gateway, then this code will raise! because it has strict=True! + # FIXME: is order correct? is one-to-one ? is order preserved? + return [ merge_models(got, acked) - for acked, got in zip(acked_many, got_many, strict=True) + for got, acked in zip(got_many, acked_many, strict=True) ] diff --git a/services/payments/tests/unit/api/test__one_time_payment_workflows.py b/services/payments/tests/unit/api/test__one_time_payment_workflows.py index 57ca6594e94..69fccffea15 100644 --- a/services/payments/tests/unit/api/test__one_time_payment_workflows.py +++ b/services/payments/tests/unit/api/test__one_time_payment_workflows.py @@ -60,11 +60,11 @@ def app_environment( ) async def test_successful_one_time_payment_workflow( is_pdb_enabled: bool, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, client: httpx.AsyncClient, faker: Faker, rpc_client: RabbitMQRPCClient, - mock_payments_gateway_service_or_none: MockRouter | None, wallet_id: WalletID, wallet_name: IDStr, user_id: UserID, diff --git a/services/payments/tests/unit/api/test__payment_method_workflows.py b/services/payments/tests/unit/api/test__payment_method_workflows.py index 15cedd186d9..440d5170221 100644 --- a/services/payments/tests/unit/api/test__payment_method_workflows.py +++ b/services/payments/tests/unit/api/test__payment_method_workflows.py @@ -63,11 +63,11 @@ def app_environment( ) async def test_successful_create_payment_method_workflow( is_pdb_enabled: bool, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, client: httpx.AsyncClient, faker: Faker, rpc_client: RabbitMQRPCClient, - mock_payments_gateway_service_or_none: MockRouter | None, wallet_id: WalletID, wallet_name: IDStr, user_id: UserID, diff --git a/services/payments/tests/unit/conftest.py b/services/payments/tests/unit/conftest.py index 23c67c3a64c..74f71e98276 100644 --- a/services/payments/tests/unit/conftest.py +++ b/services/payments/tests/unit/conftest.py @@ -59,22 +59,40 @@ def disable_rabbitmq_and_rpc_setup(mocker: MockerFixture) -> Callable: def _(): # The following services are affected if rabbitmq is not in place - mocker.patch("simcore_service_payments.core.application.setup_notifier") - mocker.patch("simcore_service_payments.core.application.setup_socketio") - mocker.patch("simcore_service_payments.core.application.setup_rabbitmq") - mocker.patch("simcore_service_payments.core.application.setup_rpc_api_routes") mocker.patch( - "simcore_service_payments.core.application.setup_auto_recharge_listener" + "simcore_service_payments.core.application.setup_notifier", autospec=True + ) + mocker.patch( + "simcore_service_payments.core.application.setup_socketio", autospec=True + ) + mocker.patch( + "simcore_service_payments.core.application.setup_rabbitmq", autospec=True + ) + mocker.patch( + "simcore_service_payments.core.application.setup_rpc_api_routes", + autospec=True, + ) + mocker.patch( + "simcore_service_payments.core.application.setup_auto_recharge_listener", + autospec=True, ) return _ @pytest.fixture -def with_disabled_rabbitmq_and_rpc(disable_rabbitmq_and_rpc_setup: Callable): +def with_disabled_rabbitmq_and_rpc(disable_rabbitmq_and_rpc_setup: Callable) -> None: disable_rabbitmq_and_rpc_setup() +@pytest.fixture +def with_disabled_gateway(mocker: MockerFixture) -> None: + mocker.patch( + "simcore_service_payments.core.application.setup_payments_gateway", + autospec=True, + ) + + @pytest.fixture async def rpc_client( faker: Faker, rabbitmq_rpc_client: Callable[[str], Awaitable[RabbitMQRPCClient]] @@ -190,15 +208,16 @@ async def app( @pytest.fixture -def mock_payments_gateway_service_api_base(app: FastAPI) -> Iterator[MockRouter]: +def mock_payments_gateway_service_api_base( + app_environment: EnvVarsDict, +) -> Iterator[MockRouter]: """ If external_environment is present, then this mock is not really used and instead the test runs against some real services """ - settings: ApplicationSettings = app.state.settings with respx.mock( - base_url=settings.PAYMENTS_GATEWAY_URL, + base_url=app_environment["PAYMENTS_GATEWAY_URL"], assert_all_called=False, assert_all_mocked=True, # IMPORTANT: KEEP always True! ) as respx_mock: @@ -422,6 +441,9 @@ def mock_payments_gateway_service_or_none( return None # OR tests against mock payments-gateway + mock_payments_gateway_service_api_base.get("/", name="healthcheck").mock( + return_value=httpx.Response(status_code=status.HTTP_200_OK, text="OK") + ) mock_payments_routes(mock_payments_gateway_service_api_base) mock_payments_methods_routes(mock_payments_gateway_service_api_base) return mock_payments_gateway_service_api_base @@ -466,7 +488,7 @@ def mock_resource_usage_tracker_service_api_base( @pytest.fixture -def mock_resoruce_usage_tracker_service_api( +def mock_resource_usage_tracker_service_api( faker: Faker, mock_resource_usage_tracker_service_api_base: MockRouter, rut_service_openapi_specs: dict[str, Any], diff --git a/services/payments/tests/unit/test_db_payments_methods_repo.py b/services/payments/tests/unit/test_db_payments_methods_repo.py index 76166c5d0be..e762c49a57c 100644 --- a/services/payments/tests/unit/test_db_payments_methods_repo.py +++ b/services/payments/tests/unit/test_db_payments_methods_repo.py @@ -26,6 +26,7 @@ def app_environment( app_environment: EnvVarsDict, postgres_env_vars_dict: EnvVarsDict, with_disabled_rabbitmq_and_rpc: None, + with_disabled_gateway: None, wait_for_postgres_ready_and_db_migrated: None, ): # set environs diff --git a/services/payments/tests/unit/test_db_payments_transactions_repo.py b/services/payments/tests/unit/test_db_payments_transactions_repo.py index ccab0fed110..47ea8101425 100644 --- a/services/payments/tests/unit/test_db_payments_transactions_repo.py +++ b/services/payments/tests/unit/test_db_payments_transactions_repo.py @@ -31,6 +31,7 @@ def app_environment( app_environment: EnvVarsDict, postgres_env_vars_dict: EnvVarsDict, with_disabled_rabbitmq_and_rpc: None, + with_disabled_gateway: None, wait_for_postgres_ready_and_db_migrated: None, ): # set environs diff --git a/services/payments/tests/unit/test_db_payments_users_repo.py b/services/payments/tests/unit/test_db_payments_users_repo.py index 82087105003..122420c598e 100644 --- a/services/payments/tests/unit/test_db_payments_users_repo.py +++ b/services/payments/tests/unit/test_db_payments_users_repo.py @@ -32,6 +32,7 @@ def app_environment( app_environment: EnvVarsDict, postgres_env_vars_dict: EnvVarsDict, with_disabled_rabbitmq_and_rpc: None, + with_disabled_gateway: None, wait_for_postgres_ready_and_db_migrated: None, ): # set environs diff --git a/services/payments/tests/unit/test_rpc_payments.py b/services/payments/tests/unit/test_rpc_payments.py index 62345b701e4..e64a499d6f6 100644 --- a/services/payments/tests/unit/test_rpc_payments.py +++ b/services/payments/tests/unit/test_rpc_payments.py @@ -9,14 +9,18 @@ import pytest from faker import Faker from fastapi import FastAPI -from models_library.api_schemas_payments.errors import PaymentNotFoundError +from models_library.api_schemas_payments.errors import ( + PaymentNotFoundError, + PaymentServiceUnavailableError, +) from models_library.api_schemas_webserver.wallets import WalletPaymentInitiated from models_library.rabbitmq_basic_types import RPCMethodName from pydantic import parse_obj_as +from pytest_mock import MockerFixture from pytest_simcore.helpers.typing_env import EnvVarsDict from pytest_simcore.helpers.utils_envs import setenvs_from_dict from respx import MockRouter -from servicelib.rabbitmq import RabbitMQRPCClient, RPCServerError +from servicelib.rabbitmq import RabbitMQRPCClient from servicelib.rabbitmq._constants import RPC_REQUEST_DEFAULT_TIMEOUT_S from simcore_service_payments.api.rpc.routes import PAYMENTS_RPC_NAMESPACE @@ -68,7 +72,17 @@ def init_payment_kwargs(faker: Faker) -> dict[str, Any]: } +@pytest.fixture +def _with_disabled_payments_gateway_startup(mocker: MockerFixture): + mocker.patch( + "simcore_service_payments.services.payments_gateway._create_start_policy", + return_value=lambda: print("on-startup"), + ) + + async def test_rpc_init_payment_fail( + is_pdb_enabled: bool, + _with_disabled_payments_gateway_startup: None, app: FastAPI, rpc_client: RabbitMQRPCClient, init_payment_kwargs: dict[str, Any], @@ -76,26 +90,22 @@ async def test_rpc_init_payment_fail( ): assert app - with pytest.raises(RPCServerError) as exc_info: + with pytest.raises(PaymentServiceUnavailableError) as exc_info: await rpc_client.request( PAYMENTS_RPC_NAMESPACE, parse_obj_as(RPCMethodName, "init_payment"), **init_payment_kwargs, + timeout_s=None if is_pdb_enabled else 5, ) - error = exc_info.value - assert isinstance(error, RPCServerError) - assert error.exc_type == "httpx.ConnectError" - assert error.method_name == "init_payment" - assert error.exc_message - assert error.traceback + assert isinstance(exc_info.value, PaymentServiceUnavailableError) async def test_webserver_one_time_payment_workflow( is_pdb_enabled: bool, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, rpc_client: RabbitMQRPCClient, - mock_payments_gateway_service_or_none: MockRouter | None, init_payment_kwargs: dict[str, Any], payments_clean_db: None, ): @@ -129,9 +139,9 @@ async def test_webserver_one_time_payment_workflow( async def test_cancel_invalid_payment_id( is_pdb_enabled: bool, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, rpc_client: RabbitMQRPCClient, - mock_payments_gateway_service_or_none: MockRouter | None, init_payment_kwargs: dict[str, Any], faker: Faker, payments_clean_db: None, diff --git a/services/payments/tests/unit/test_rpc_payments_methods.py b/services/payments/tests/unit/test_rpc_payments_methods.py index 61816ef633f..efc6bdd997b 100644 --- a/services/payments/tests/unit/test_rpc_payments_methods.py +++ b/services/payments/tests/unit/test_rpc_payments_methods.py @@ -52,7 +52,8 @@ def app_environment( postgres_env_vars_dict: EnvVarsDict, wait_for_postgres_ready_and_db_migrated: None, external_environment: EnvVarsDict, -): +) -> EnvVarsDict: + # set environs monkeypatch.delenv("PAYMENTS_RABBITMQ", raising=False) monkeypatch.delenv("PAYMENTS_POSTGRES", raising=False) @@ -71,9 +72,9 @@ def app_environment( async def test_webserver_init_and_cancel_payment_method_workflow( is_pdb_enabled: bool, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, rpc_client: RabbitMQRPCClient, - mock_payments_gateway_service_or_none: MockRouter | None, user_id: UserID, user_name: IDStr, user_email: EmailStr, @@ -119,9 +120,9 @@ async def test_webserver_init_and_cancel_payment_method_workflow( async def test_webserver_crud_payment_method_workflow( is_pdb_enabled: bool, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, rpc_client: RabbitMQRPCClient, - mock_payments_gateway_service_or_none: MockRouter | None, user_id: UserID, user_name: IDStr, user_email: EmailStr, @@ -200,10 +201,10 @@ async def test_webserver_crud_payment_method_workflow( async def test_webserver_pay_with_payment_method_workflow( is_pdb_enabled: bool, + mock_resource_usage_tracker_service_api: None, + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, rpc_client: RabbitMQRPCClient, - mock_resoruce_usage_tracker_service_api: None, - mock_payments_gateway_service_or_none: MockRouter | None, faker: Faker, product_name: ProductName, user_id: UserID, diff --git a/services/payments/tests/unit/test_services_auto_recharge_listener.py b/services/payments/tests/unit/test_services_auto_recharge_listener.py index bc54d8297e4..e7f3a1dac09 100644 --- a/services/payments/tests/unit/test_services_auto_recharge_listener.py +++ b/services/payments/tests/unit/test_services_auto_recharge_listener.py @@ -4,10 +4,9 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -from collections.abc import Callable +from collections.abc import Awaitable, Callable, Iterator from datetime import datetime, timedelta, timezone from decimal import Decimal -from typing import Awaitable, Iterator from unittest import mock import pytest @@ -228,7 +227,7 @@ async def test_process_message__whole_autorecharge_flow_success( mocked_pay_with_payment_method: mock.AsyncMock, mock_rpc_server: RabbitMQRPCClient, mock_rpc_client: RabbitMQRPCClient, - mock_resoruce_usage_tracker_service_api: MockRouter, + mock_resource_usage_tracker_service_api: MockRouter, postgres_db: sa.engine.Engine, ): publisher = create_rabbitmq_client("publisher") @@ -241,7 +240,7 @@ async def test_process_message__whole_autorecharge_flow_success( assert row.wallet_id == wallet_id assert row.state == PaymentTransactionState.SUCCESS assert row.comment == "Payment generated by auto recharge" - assert len(mock_resoruce_usage_tracker_service_api.calls) == 1 + assert len(mock_resource_usage_tracker_service_api.calls) == 1 @pytest.mark.parametrize( diff --git a/services/payments/tests/unit/test_services_payments.py b/services/payments/tests/unit/test_services_payments.py index 5790b5f53fa..514941f7c4c 100644 --- a/services/payments/tests/unit/test_services_payments.py +++ b/services/payments/tests/unit/test_services_payments.py @@ -59,12 +59,12 @@ def app_environment( async def test_fails_to_pay_with_payment_method_without_funds( + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, create_fake_payment_method_in_db: Callable[ [PaymentMethodID, WalletID, UserID], Awaitable[PaymentsMethodsDB] ], no_funds_payment_method_id: PaymentMethodID, - mock_payments_gateway_service_or_none: MockRouter | None, wallet_id: WalletID, wallet_name: IDStr, user_id: UserID, diff --git a/services/payments/tests/unit/test_services_payments_gateway.py b/services/payments/tests/unit/test_services_payments_gateway.py index 698acba7b9d..4a0006f8cd3 100644 --- a/services/payments/tests/unit/test_services_payments_gateway.py +++ b/services/payments/tests/unit/test_services_payments_gateway.py @@ -17,8 +17,8 @@ ) from simcore_service_payments.services.payments_gateway import ( PaymentsGatewayApi, - PaymentsGatewayError, - _raise_as_payments_gateway_error, + PaymentsGatewayApiError, + _reraise_as_service_errors_context, setup_payments_gateway, ) @@ -89,9 +89,9 @@ def amount_dollars(request: pytest.FixtureRequest) -> float: "https://github.com/ITISFoundation/osparc-simcore/pull/4715" ) async def test_one_time_payment_workflow( + mock_payments_gateway_service_or_none: MockRouter | None, app: FastAPI, faker: Faker, - mock_payments_gateway_service_or_none: MockRouter | None, amount_dollars: float, ): payment_gateway_api = PaymentsGatewayApi.get_from_app_state(app) @@ -187,7 +187,7 @@ async def test_payment_methods_workflow( # delete payment-method await payments_gateway_api.delete_payment_method(payment_method_id) - with pytest.raises(PaymentsGatewayError) as err_info: + with pytest.raises(PaymentsGatewayApiError) as err_info: await payments_gateway_api.get_payment_method(payment_method_id) assert str(err_info.value) @@ -205,7 +205,7 @@ async def test_payment_methods_workflow( async def test_payments_gateway_error_exception(): async def _go(): - with _raise_as_payments_gateway_error(operation_id="foo"): + with _reraise_as_service_errors_context(operation_id="foo"): async with httpx.AsyncClient( app=FastAPI(), base_url="http://payments.testserver.io", @@ -213,10 +213,10 @@ async def _go(): response = await client.post("/foo", params={"x": "3"}, json={"y": 12}) response.raise_for_status() - with pytest.raises(PaymentsGatewayError) as err_info: + with pytest.raises(PaymentsGatewayApiError) as err_info: await _go() err = err_info.value - assert isinstance(err, PaymentsGatewayError) + assert isinstance(err, PaymentsGatewayApiError) assert "curl -X POST" in err.get_detailed_message() diff --git a/services/payments/tests/unit/test_services_resource_usage_tracker.py b/services/payments/tests/unit/test_services_resource_usage_tracker.py index 868cdf65661..cc3b255c619 100644 --- a/services/payments/tests/unit/test_services_resource_usage_tracker.py +++ b/services/payments/tests/unit/test_services_resource_usage_tracker.py @@ -56,7 +56,7 @@ def app( async def test_add_credits_to_wallet( - app: FastAPI, faker: Faker, mock_resoruce_usage_tracker_service_api: MockRouter + app: FastAPI, faker: Faker, mock_resource_usage_tracker_service_api: MockRouter ): # test rut_api = ResourceUsageTrackerApi.get_from_app_state(app)