diff --git a/packages/postgres-database/src/simcore_postgres_database/aiopg_errors.py b/packages/postgres-database/src/simcore_postgres_database/aiopg_errors.py index 730d6f630ac1..1beae1e1c8c2 100644 --- a/packages/postgres-database/src/simcore_postgres_database/aiopg_errors.py +++ b/packages/postgres-database/src/simcore_postgres_database/aiopg_errors.py @@ -21,6 +21,8 @@ SEE https://www.postgresql.org/docs/current/errcodes-appendix.html """ +import warnings + # NOTE: psycopg2.errors are created dynamically # pylint: disable=no-name-in-module from psycopg2 import ( @@ -46,6 +48,19 @@ assert issubclass(UniqueViolation, IntegrityError) # nosec + +warnings.warn( + ( + "DEPRECATED: The aiopg DBAPI exceptions in this module are no longer used. " + "Please use exceptions from the `sqlalchemy.exc` module instead. " + "See https://docs.sqlalchemy.org/en/21/core/exceptions.html for details. " + "This change is part of the migration to SQLAlchemy async support with asyncpg. " + "See migration issue: https://github.com/ITISFoundation/osparc-simcore/issues/4529" + ), + DeprecationWarning, + stacklevel=2, +) + __all__: tuple[str, ...] = ( "CheckViolation", "DatabaseError", diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/httpx_assert_checks.py b/packages/pytest-simcore/src/pytest_simcore/helpers/httpx_assert_checks.py index 4443241192ee..57d3b6701499 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/httpx_assert_checks.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/httpx_assert_checks.py @@ -85,5 +85,5 @@ def _do_assert_error( for msg in list_expected_msg: assert any( - re.search(msg, e) for e in details + msg == e or re.search(msg, e) for e in details ), f"could not find {msg=} in {details=}" diff --git a/services/payments/src/simcore_service_payments/db/payments_methods_repo.py b/services/payments/src/simcore_service_payments/db/payments_methods_repo.py index cea7b8e6158e..9595104c0102 100644 --- a/services/payments/src/simcore_service_payments/db/payments_methods_repo.py +++ b/services/payments/src/simcore_service_payments/db/payments_methods_repo.py @@ -1,6 +1,5 @@ import datetime -import simcore_postgres_database.aiopg_errors as db_errors import sqlalchemy as sa from arrow import utcnow from models_library.api_schemas_payments.errors import ( @@ -16,6 +15,7 @@ InitPromptAckFlowState, payments_methods, ) +from sqlalchemy.exc import IntegrityError from ..models.db import PaymentsMethodsDB from .base import BaseRepository @@ -42,7 +42,7 @@ async def insert_init_payment_method( ) return payment_method_id - except db_errors.UniqueViolation as err: + except IntegrityError as err: raise PaymentMethodUniqueViolationError( payment_method_id=payment_method_id ) from err diff --git a/services/payments/src/simcore_service_payments/db/payments_transactions_repo.py b/services/payments/src/simcore_service_payments/db/payments_transactions_repo.py index d7f6b893668e..fb5e7ec1e0b9 100644 --- a/services/payments/src/simcore_service_payments/db/payments_transactions_repo.py +++ b/services/payments/src/simcore_service_payments/db/payments_transactions_repo.py @@ -13,11 +13,11 @@ from models_library.users import UserID from models_library.wallets import WalletID from pydantic import HttpUrl, PositiveInt, TypeAdapter -from simcore_postgres_database import aiopg_errors as pg_errors from simcore_postgres_database.models.payments_transactions import ( PaymentTransactionState, payments_transactions, ) +from sqlalchemy.exc import IntegrityError from ..models.db import PaymentsTransactionsDB from .base import BaseRepository @@ -58,7 +58,7 @@ async def insert_init_payment_transaction( ) ) return payment_id - except pg_errors.UniqueViolation as exc: + except IntegrityError as exc: raise PaymentAlreadyExistsError(payment_id=f"{payment_id}") from exc async def update_ack_payment_transaction( diff --git a/services/storage/src/simcore_service_storage/exceptions/handlers.py b/services/storage/src/simcore_service_storage/exceptions/handlers.py index 78a93d8f46c8..f3e4e6406d7a 100644 --- a/services/storage/src/simcore_service_storage/exceptions/handlers.py +++ b/services/storage/src/simcore_service_storage/exceptions/handlers.py @@ -1,12 +1,12 @@ import logging -from asyncpg.exceptions import PostgresError from aws_library.s3 import S3AccessError, S3KeyNotFoundError from fastapi import FastAPI, status from servicelib.fastapi.http_error import ( make_http_error_handler_for_exception, set_app_default_http_error_handlers, ) +from sqlalchemy.exc import DBAPIError from ..modules.datcore_adapter.datcore_adapter_exceptions import ( DatcoreAdapterFileNotFoundError, @@ -70,7 +70,7 @@ def set_exception_handlers(app: FastAPI) -> None: ), ) for exc_3rd_party in ( - PostgresError, + DBAPIError, S3AccessError, ): app.add_exception_handler( diff --git a/services/storage/tests/unit/test_utils_handlers.py b/services/storage/tests/unit/test_utils_handlers.py index c91b34cb9a8f..9a83fc85de8e 100644 --- a/services/storage/tests/unit/test_utils_handlers.py +++ b/services/storage/tests/unit/test_utils_handlers.py @@ -9,7 +9,6 @@ import httpx import pytest -from asyncpg import PostgresError from aws_library.s3._errors import S3AccessError, S3KeyNotFoundError from fastapi import FastAPI, HTTPException, status from fastapi.exceptions import RequestValidationError @@ -28,6 +27,7 @@ DatcoreAdapterTimeoutError, ) from simcore_service_storage.modules.db.access_layer import InvalidFileIdentifierError +from sqlalchemy.exc import DBAPIError @pytest.fixture @@ -85,7 +85,12 @@ async def client(initialized_app: FastAPI) -> AsyncIterator[AsyncClient]: status.HTTP_422_UNPROCESSABLE_ENTITY, ), ( - PostgresError("pytest postgres error"), + DBAPIError.instance( + statement="pytest statement", + params={}, + orig=Exception("pytest original"), + dbapi_base_err=Exception, + ), status.HTTP_503_SERVICE_UNAVAILABLE, ), ( diff --git a/services/web/server/src/simcore_service_webserver/db/_aiopg.py b/services/web/server/src/simcore_service_webserver/db/_aiopg.py index bcd38ae9aac0..eb95b9bb5f04 100644 --- a/services/web/server/src/simcore_service_webserver/db/_aiopg.py +++ b/services/web/server/src/simcore_service_webserver/db/_aiopg.py @@ -5,6 +5,7 @@ """ import logging +import warnings from collections.abc import AsyncIterator from typing import Any, cast @@ -29,6 +30,16 @@ _logger = logging.getLogger(__name__) +warnings.warn( + ( + "simcore_service_webserver.db._aiopg is deprecated and will be removed in a future release. " + "Please use simcore_service_webserver.db._asyncpg instead. " + "See migration details: https://github.com/ITISFoundation/osparc-simcore/issues/4529" + ), + DeprecationWarning, + stacklevel=2, +) + @retry(**PostgresRetryPolicyUponInitialization(_logger).kwargs) async def _ensure_pg_ready( diff --git a/services/web/server/src/simcore_service_webserver/db/plugin.py b/services/web/server/src/simcore_service_webserver/db/plugin.py index f346bab921bf..3e7b9ba41937 100644 --- a/services/web/server/src/simcore_service_webserver/db/plugin.py +++ b/services/web/server/src/simcore_service_webserver/db/plugin.py @@ -35,7 +35,7 @@ ) def setup_db(app: web.Application): - # ensures keys exist + # ensures keys exist DEPRECATED app[APP_AIOPG_ENGINE_KEY] = None assert get_database_engine_legacy(app) is None # nosec diff --git a/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py b/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py index 0416dc441911..f112b49f7dc7 100644 --- a/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py +++ b/services/web/server/src/simcore_service_webserver/garbage_collector/_core_utils.py @@ -5,7 +5,8 @@ from models_library.groups import Group, GroupID, GroupType from models_library.projects import ProjectID from models_library.users import UserID -from simcore_postgres_database.aiopg_errors import DatabaseError +from simcore_postgres_database.aiopg_errors import DatabaseError as AiopgDatabaseError +from sqlalchemy.exc import DatabaseError from ..groups.api import get_group_by_gid from ..projects._projects_repository_legacy import ( @@ -187,8 +188,8 @@ async def replace_current_owner( ) except ( + AiopgDatabaseError, DatabaseError, - asyncpg.exceptions.PostgresError, ProjectNotFoundError, UserNotFoundError, ): diff --git a/services/web/server/src/simcore_service_webserver/security/_authz_policy.py b/services/web/server/src/simcore_service_webserver/security/_authz_policy.py index 20f5705a6bd0..e95577f0ba8c 100644 --- a/services/web/server/src/simcore_service_webserver/security/_authz_policy.py +++ b/services/web/server/src/simcore_service_webserver/security/_authz_policy.py @@ -15,8 +15,7 @@ from models_library.products import ProductName from models_library.users import UserID from servicelib.aiohttp.db_asyncpg_engine import get_async_engine -from simcore_postgres_database.aiopg_errors import DatabaseError as AiopgDatabaseError -from sqlalchemy.exc import DatabaseError as SQLAlchemyDatabaseError +from sqlalchemy.exc import DatabaseError from . import _authz_repository from ._authz_access_model import ( @@ -48,11 +47,12 @@ def _handle_exceptions_as_503(): try: yield - except (AiopgDatabaseError, SQLAlchemyDatabaseError) as err: + except DatabaseError as err: _logger.exception( **create_troubleshooting_log_kwargs( - "Auth unavailable due to database error", + f"{MSG_AUTH_NOT_AVAILABLE}: Auth unavailable due to database error.", error=err, + error_context={"origin": str(err.orig) if err.orig else None}, tip="Check database connection", ) ) diff --git a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_catalog.py b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_catalog.py index 30a9fbd35ecb..9f81102d4067 100644 --- a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_catalog.py +++ b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_catalog.py @@ -4,9 +4,6 @@ from dataclasses import dataclass import sqlalchemy as sa -from aiohttp import web -from aiopg.sa.connection import SAConnection -from aiopg.sa.engine import Engine from models_library.groups import EVERYONE_GROUP_ID from models_library.services import ServiceKey, ServiceVersion from models_library.services_constants import ( @@ -22,11 +19,12 @@ from simcore_postgres_database.models.services_consume_filetypes import ( services_consume_filetypes, ) +from simcore_postgres_database.utils_repos import pass_or_acquire_connection from simcore_postgres_database.utils_services import create_select_latest_services_query +from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine -from ..db.plugin import get_database_engine_legacy from ._errors import ServiceNotFoundError -from .settings import StudiesDispatcherSettings, get_plugin_settings +from .settings import StudiesDispatcherSettings LARGEST_PAGE_SIZE = 1000 @@ -44,7 +42,10 @@ class ServiceMetaData: file_extensions: list[str] -async def _get_service_filetypes(conn: SAConnection) -> dict[ServiceKey, list[str]]: +async def _get_service_filetypes( + engine: AsyncEngine, + connection: AsyncConnection | None = None, +) -> dict[ServiceKey, list[str]]: query = sa.select( services_consume_filetypes.c.service_key, sa.func.array_agg( @@ -52,14 +53,17 @@ async def _get_service_filetypes(conn: SAConnection) -> dict[ServiceKey, list[st ).label("list_of_file_types"), ).group_by(services_consume_filetypes.c.service_key) - result = await conn.execute(query) - rows = await result.fetchall() + async with pass_or_acquire_connection(engine, connection) as conn: + result = await conn.execute(query) + rows = result.fetchall() - return {row.service_key: row.list_of_file_types for row in rows} + return {row.service_key: row.list_of_file_types for row in rows} async def iter_latest_product_services( - app: web.Application, + settings: StudiesDispatcherSettings, + engine: AsyncEngine, + connection: AsyncConnection | None = None, *, product_name: str, page_number: PositiveInt = 1, # 1-based @@ -68,9 +72,6 @@ async def iter_latest_product_services( assert page_number >= 1 # nosec assert ((page_number - 1) * page_size) >= 0 # nosec - engine: Engine = get_database_engine_legacy(app) - settings: StudiesDispatcherSettings = get_plugin_settings(app) - # Select query for latest version of the service latest_services = create_select_latest_services_query().alias("latest_services") @@ -109,10 +110,10 @@ async def iter_latest_product_services( # pagination query = query.limit(page_size).offset((page_number - 1) * page_size) - async with engine.acquire() as conn: - service_filetypes = await _get_service_filetypes(conn) + async with pass_or_acquire_connection(engine, connection) as conn: + service_filetypes = await _get_service_filetypes(engine, conn) - async for row in await conn.execute(query): + async for row in await conn.stream(query): yield ServiceMetaData( key=row.key, version=row.version, @@ -135,14 +136,13 @@ class ValidService: @log_decorator(_logger, level=logging.DEBUG) async def validate_requested_service( - app: web.Application, + engine: AsyncEngine, + connection: AsyncConnection | None = None, *, service_key: ServiceKey, service_version: ServiceVersion, ) -> ValidService: - engine: Engine = get_database_engine_legacy(app) - - async with engine.acquire() as conn: + async with pass_or_acquire_connection(engine, connection) as conn: query = sa.select( services_meta_data.c.name, services_meta_data.c.key, @@ -153,7 +153,7 @@ async def validate_requested_service( ) result = await conn.execute(query) - row = await result.fetchone() + row = result.one_or_none() if row is None: raise ServiceNotFoundError( diff --git a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/nih.py b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/nih.py index 74174840fb38..9d09c07e73c7 100644 --- a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/nih.py +++ b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/nih.py @@ -9,10 +9,12 @@ ) from ...._meta import API_VTAG +from ....db.plugin import get_asyncpg_engine from ....products import products_web from ....utils_aiohttp import envelope_json_response from ... import _service from ..._catalog import iter_latest_product_services +from ...settings import get_plugin_settings from .nih_schemas import ServiceGet, Viewer _logger = logging.getLogger(__name__) @@ -26,9 +28,12 @@ async def list_latest_services(request: Request): """Returns a list latest version of services""" product_name = products_web.get_product_name(request) + plugin_settings = get_plugin_settings(request.app) + engine = get_asyncpg_engine(request.app) + services = [] async for service_data in iter_latest_product_services( - request.app, product_name=product_name + plugin_settings, engine, product_name=product_name ): try: service = ServiceGet.create(service_data, request) diff --git a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/redirects.py b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/redirects.py index 332d628bd196..84d055336cf2 100644 --- a/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/redirects.py +++ b/services/web/server/src/simcore_service_webserver/studies_dispatcher/_controller/rest/redirects.py @@ -7,6 +7,7 @@ from models_library.projects_nodes_io import NodeID from servicelib.aiohttp.requests_validation import parse_request_query_parameters_as +from ....db.plugin import get_asyncpg_engine from ....dynamic_scheduler import api as dynamic_scheduler_service from ....products import products_web from ....utils_aiohttp import create_redirect_to_page_response, get_api_base_url @@ -133,7 +134,7 @@ async def get_redirection_to_viewer(request: web.Request): service_params_ = query_params valid_service: ValidService = await validate_requested_service( - app=request.app, + get_asyncpg_engine(request.app), service_key=service_params_.viewer_key, service_version=service_params_.viewer_version, ) diff --git a/services/web/server/tests/unit/isolated/test_security__authz.py b/services/web/server/tests/unit/isolated/test_security__authz.py index e22d2e91fd2c..d82723784612 100644 --- a/services/web/server/tests/unit/isolated/test_security__authz.py +++ b/services/web/server/tests/unit/isolated/test_security__authz.py @@ -17,7 +17,6 @@ import pytest from aiocache.base import BaseCache from aiohttp import web -from psycopg2 import DatabaseError from pytest_mock import MockerFixture from simcore_service_webserver.projects.models import ProjectDict from simcore_service_webserver.security._authz_access_model import ( @@ -30,6 +29,7 @@ ) from simcore_service_webserver.security._authz_policy import AuthorizationPolicy from simcore_service_webserver.security._authz_repository import ActiveUserIdAndRole +from sqlalchemy.exc import DatabaseError @pytest.fixture @@ -277,7 +277,9 @@ async def _fake_db(engine, email): assert engine == "FAKE-ENGINE" if "db-failure" in email: - raise DatabaseError + raise DatabaseError( + statement="SELECT 1", params=None, orig=Exception("fake db error") + ) # inactive user or not found return copy.deepcopy(users_db.get(email)) diff --git a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/conftest.py b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/conftest.py new file mode 100644 index 000000000000..21069e0e5bbe --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/conftest.py @@ -0,0 +1,94 @@ +# pylint: disable=protected-access +# pylint: disable=redefined-outer-name +# pylint: disable=too-many-arguments +# pylint: disable=unused-argument +# pylint: disable=unused-variable + +from collections.abc import AsyncIterator + +import pytest +from models_library.groups import EVERYONE_GROUP_ID +from pytest_simcore.helpers.faker_factories import ( + random_service_access_rights, + random_service_consume_filetype, + random_service_meta_data, +) +from pytest_simcore.helpers.postgres_tools import insert_and_get_row_lifespan +from simcore_postgres_database.models.services import ( + services_access_rights, + services_meta_data, +) +from simcore_postgres_database.models.services_consume_filetypes import ( + services_consume_filetypes, +) +from sqlalchemy.ext.asyncio import AsyncEngine + + +@pytest.fixture +async def service_metadata_in_db(asyncpg_engine: AsyncEngine) -> AsyncIterator[dict]: + """Pre-populate services metadata table with test data.""" + service_data = random_service_meta_data( + key="simcore/services/dynamic/viewer", + version="1.0.0", + name="Test Viewer Service", + ) + # pylint: disable=contextmanager-generator-missing-cleanup + async with insert_and_get_row_lifespan( + asyncpg_engine, + table=services_meta_data, + values=service_data, + pk_col=services_meta_data.c.key, + pk_value=service_data["key"], + ) as row: + yield row + # cleanup happens automatically + + +@pytest.fixture +async def consume_filetypes_in_db( + asyncpg_engine: AsyncEngine, service_metadata_in_db: dict +): + """Pre-populate services consume filetypes table with test data.""" + consume_data = random_service_consume_filetype( + service_key=service_metadata_in_db["key"], + service_version=service_metadata_in_db["version"], + filetype="CSV", + service_display_name="CSV Viewer", + service_input_port="input_1", + preference_order=1, + is_guest_allowed=True, + ) + + # pylint: disable=contextmanager-generator-missing-cleanup + async with insert_and_get_row_lifespan( + asyncpg_engine, + table=services_consume_filetypes, + values=consume_data, + pk_col=services_consume_filetypes.c.service_key, + pk_value=consume_data["service_key"], + ) as row: + yield row + + +@pytest.fixture +async def service_access_rights_in_db( + asyncpg_engine: AsyncEngine, service_metadata_in_db: dict +): + """Pre-populate services access rights table with test data.""" + access_data = random_service_access_rights( + key=service_metadata_in_db["key"], + version=service_metadata_in_db["version"], + gid=EVERYONE_GROUP_ID, + execute_access=True, + product_name="osparc", + ) + + # pylint: disable=contextmanager-generator-missing-cleanup + async with insert_and_get_row_lifespan( + asyncpg_engine, + table=services_access_rights, + values=access_data, + pk_col=services_access_rights.c.key, + pk_value=access_data["key"], + ) as row: + yield row diff --git a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/test_studies_dispatcher_catalog.py b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/test_studies_dispatcher_catalog.py new file mode 100644 index 000000000000..da2a35897f95 --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/test_studies_dispatcher_catalog.py @@ -0,0 +1,113 @@ +# pylint: disable=protected-access +# pylint: disable=redefined-outer-name +# pylint: disable=too-many-arguments +# pylint: disable=unused-argument +# pylint: disable=unused-variable + + +import pytest +from models_library.services import ServiceKey, ServiceVersion +from simcore_service_webserver.studies_dispatcher._catalog import ( + ServiceMetaData, + ValidService, + iter_latest_product_services, + validate_requested_service, +) +from simcore_service_webserver.studies_dispatcher._errors import ServiceNotFoundError +from simcore_service_webserver.studies_dispatcher.settings import ( + StudiesDispatcherSettings, +) +from sqlalchemy.ext.asyncio import AsyncEngine + + +@pytest.fixture +def studies_dispatcher_settings() -> StudiesDispatcherSettings: + return StudiesDispatcherSettings( + STUDIES_DEFAULT_SERVICE_THUMBNAIL="https://example.com/default-thumbnail.png" + ) + + +async def test_iter_latest_product_services( + asyncpg_engine: AsyncEngine, + studies_dispatcher_settings: StudiesDispatcherSettings, + service_metadata_in_db: dict, + service_access_rights_in_db: dict, + consume_filetypes_in_db: dict, +): + """Test iterating through latest product services.""" + # Act + services = [] + async for service in iter_latest_product_services( + studies_dispatcher_settings, asyncpg_engine, product_name="osparc" + ): + services.append(service) + + # Assert + assert len(services) == 1 + service = services[0] + assert isinstance(service, ServiceMetaData) + assert service.key == service_metadata_in_db["key"] + assert service.version == service_metadata_in_db["version"] + assert service.title == service_metadata_in_db["name"] + assert service.description == service_metadata_in_db["description"] + assert service.file_extensions == [consume_filetypes_in_db["filetype"]] + + +async def test_iter_latest_product_services_with_pagination( + asyncpg_engine: AsyncEngine, + studies_dispatcher_settings: StudiesDispatcherSettings, + service_metadata_in_db: dict, + service_access_rights_in_db: dict, +): + """Test iterating through services with pagination.""" + # Act + services = [] + async for service in iter_latest_product_services( + studies_dispatcher_settings, + asyncpg_engine, + product_name="osparc", + page_number=1, + page_size=1, + ): + services.append(service) + + # Assert + assert len(services) == 1 + + +async def test_validate_requested_service_success( + asyncpg_engine: AsyncEngine, + service_metadata_in_db: dict, + consume_filetypes_in_db: dict, +): + """Test validating a service that exists and is valid.""" + # Act + valid_service = await validate_requested_service( + engine=asyncpg_engine, + service_key=ServiceKey(service_metadata_in_db["key"]), + service_version=ServiceVersion(service_metadata_in_db["version"]), + ) + + # Assert + assert isinstance(valid_service, ValidService) + assert valid_service.key == service_metadata_in_db["key"] + assert valid_service.version == service_metadata_in_db["version"] + assert valid_service.title == service_metadata_in_db["name"] + assert valid_service.is_public == consume_filetypes_in_db["is_guest_allowed"] + assert str(valid_service.thumbnail) == str(service_metadata_in_db["thumbnail"]) + + +async def test_validate_requested_service_not_found( + asyncpg_engine: AsyncEngine, +): + """Test validating a service that doesn't exist.""" + # Act & Assert + with pytest.raises(ServiceNotFoundError) as exc_info: + await validate_requested_service( + asyncpg_engine, + service_key=ServiceKey("simcore/services/dynamic/nonexistent"), + service_version=ServiceVersion("1.0.0"), + ) + + assert exc_info.value.service_key == "simcore/services/dynamic/nonexistent" + assert exc_info.value.service_version == "1.0.0" diff --git a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_repository.py b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/test_studies_dispatcher_repository.py similarity index 71% rename from services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_repository.py rename to services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/test_studies_dispatcher_repository.py index 8a508f360179..5b6f47db70b4 100644 --- a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_repository.py +++ b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/repository/test_studies_dispatcher_repository.py @@ -4,18 +4,8 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -from collections.abc import AsyncIterator import pytest -from pytest_simcore.helpers.faker_factories import ( - random_service_consume_filetype, - random_service_meta_data, -) -from pytest_simcore.helpers.postgres_tools import insert_and_get_row_lifespan -from simcore_postgres_database.models.services import services_meta_data -from simcore_postgres_database.models.services_consume_filetypes import ( - services_consume_filetypes, -) from simcore_service_webserver.studies_dispatcher._models import ViewerInfo from simcore_service_webserver.studies_dispatcher._repository import ( StudiesDispatcherRepository, @@ -23,52 +13,6 @@ from sqlalchemy.ext.asyncio import AsyncEngine -@pytest.fixture -async def service_metadata_in_db(asyncpg_engine: AsyncEngine) -> AsyncIterator[dict]: - """Pre-populate services metadata table with test data.""" - service_data = random_service_meta_data( - key="simcore/services/dynamic/viewer", - version="1.0.0", - name="Test Viewer Service", - ) - # pylint: disable=contextmanager-generator-missing-cleanup - async with insert_and_get_row_lifespan( - asyncpg_engine, - table=services_meta_data, - values=service_data, - pk_col=services_meta_data.c.key, - pk_value=service_data["key"], - ) as row: - yield row - # cleanup happens automatically - - -@pytest.fixture -async def consume_filetypes_in_db( - asyncpg_engine: AsyncEngine, service_metadata_in_db: dict -): - """Pre-populate services consume filetypes table with test data.""" - consume_data = random_service_consume_filetype( - service_key=service_metadata_in_db["key"], - service_version=service_metadata_in_db["version"], - filetype="CSV", - service_display_name="CSV Viewer", - service_input_port="input_1", - preference_order=1, - is_guest_allowed=True, - ) - - # pylint: disable=contextmanager-generator-missing-cleanup - async with insert_and_get_row_lifespan( - asyncpg_engine, - table=services_consume_filetypes, - values=consume_data, - pk_col=services_consume_filetypes.c.service_key, - pk_value=consume_data["service_key"], - ) as row: - yield row - - @pytest.fixture def studies_dispatcher_repository( asyncpg_engine: AsyncEngine,