diff --git a/.env-devel b/.env-devel index cc6609460da..9ba17baebb7 100644 --- a/.env-devel +++ b/.env-devel @@ -348,10 +348,10 @@ SIMCORE_VCS_RELEASE_TAG=latest STUDIES_ACCESS_ANONYMOUS_ALLOWED=0 STUDIES_DEFAULT_SERVICE_THUMBNAIL=https://via.placeholder.com/170x120.png TRACING_OPENTELEMETRY_COLLECTOR_BATCH_SIZE=2 -TRACING_OPENTELEMETRY_COLLECTOR_PORT=4318 TRACING_OPENTELEMETRY_COLLECTOR_ENDPOINT=http://opentelemetry-collector -TRACING_OPENTELEMETRY_COLLECTOR_SAMPLING_PERCENTAGE=100 TRACING_OPENTELEMETRY_COLLECTOR_EXPORTER_ENDPOINT=http://jaeger:4318 +TRACING_OPENTELEMETRY_COLLECTOR_PORT=4318 +TRACING_OPENTELEMETRY_COLLECTOR_SAMPLING_PERCENTAGE=100 TRAEFIK_SIMCORE_ZONE=internal_simcore_stack TWILIO_ACCOUNT_SID=DUMMY TWILIO_AUTH_TOKEN=DUMMY @@ -365,8 +365,8 @@ WEBSERVER_DEV_FEATURES_ENABLED=0 WEBSERVER_DIAGNOSTICS={} WEBSERVER_EMAIL={} WEBSERVER_EXPORTER={} -WEBSERVER_FRONTEND={} WEBSERVER_FOLDERS=1 +WEBSERVER_FRONTEND={} WEBSERVER_GARBAGE_COLLECTOR=null WEBSERVER_GROUPS=1 WEBSERVER_GUNICORN_CMD_ARGS=--timeout=180 diff --git a/api/specs/web-server/_projects_crud.py b/api/specs/web-server/_projects_crud.py index 31f26d6425e..62fe7684c68 100644 --- a/api/specs/web-server/_projects_crud.py +++ b/api/specs/web-server/_projects_crud.py @@ -30,7 +30,7 @@ from models_library.rest_pagination import Page from pydantic import BaseModel from simcore_service_webserver._meta import API_VTAG -from simcore_service_webserver.projects._common_models import ProjectPathParams +from simcore_service_webserver.projects._common.models import ProjectPathParams from simcore_service_webserver.projects._crud_handlers import ProjectCreateParams from simcore_service_webserver.projects._crud_handlers_models import ( ProjectActiveQueryParams, diff --git a/api/specs/web-server/_projects_groups.py b/api/specs/web-server/_projects_groups.py index 88432bb3cfa..cfc0870d6a8 100644 --- a/api/specs/web-server/_projects_groups.py +++ b/api/specs/web-server/_projects_groups.py @@ -9,7 +9,7 @@ from fastapi import APIRouter, Depends, status from models_library.generics import Envelope from simcore_service_webserver._meta import API_VTAG -from simcore_service_webserver.projects._common_models import ProjectPathParams +from simcore_service_webserver.projects._common.models import ProjectPathParams from simcore_service_webserver.projects._groups_api import ProjectGroupGet from simcore_service_webserver.projects._groups_handlers import ( _ProjectsGroupsBodyParams, diff --git a/api/specs/web-server/_projects_wallet.py b/api/specs/web-server/_projects_wallet.py index e079dd33e17..c9502393b97 100644 --- a/api/specs/web-server/_projects_wallet.py +++ b/api/specs/web-server/_projects_wallet.py @@ -16,7 +16,7 @@ from models_library.projects import ProjectID from models_library.wallets import WalletID from simcore_service_webserver._meta import API_VTAG -from simcore_service_webserver.projects._common_models import ProjectPathParams +from simcore_service_webserver.projects._common.models import ProjectPathParams router = APIRouter( prefix=f"/{API_VTAG}", diff --git a/services/web/server/setup.cfg b/services/web/server/setup.cfg index 9a74fe46b3d..486fe83406d 100644 --- a/services/web/server/setup.cfg +++ b/services/web/server/setup.cfg @@ -12,13 +12,13 @@ commit_args = --no-verify [tool:pytest] addopts = --strict-markers asyncio_mode = auto -markers = +markers = slow: marks tests as slow (deselect with '-m "not slow"') acceptance_test: "marks tests as 'acceptance tests' i.e. does the system do what the user expects? Typically those are workflows." testit: "marks test to run during development" heavy_load: "mark tests that require large amount of data" [mypy] -plugins = +plugins = pydantic.mypy sqlalchemy.ext.mypy.plugin diff --git a/services/web/server/src/simcore_service_webserver/application_settings.py b/services/web/server/src/simcore_service_webserver/application_settings.py index 07941e1e92c..676725f65b8 100644 --- a/services/web/server/src/simcore_service_webserver/application_settings.py +++ b/services/web/server/src/simcore_service_webserver/application_settings.py @@ -175,8 +175,13 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): json_schema_extra={"auto_default_from_env": True}, description="director-v2 service client's plugin", ) + + WEBSERVER_DYNAMIC_SCHEDULER: DynamicSchedulerSettings | None = Field( + json_schema_extra={"auto_default_from_env": True}, + ) + WEBSERVER_EMAIL: SMTPSettings | None = Field( - json_schema_extra={"auto_default_from_env": True}, description="email plugin" + json_schema_extra={"auto_default_from_env": True} ) WEBSERVER_EXPORTER: ExporterSettings | None = Field( json_schema_extra={"auto_default_from_env": True}, description="exporter plugin" @@ -204,9 +209,8 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): description="payments plugin settings", ) - WEBSERVER_DYNAMIC_SCHEDULER: DynamicSchedulerSettings | None = Field( - description="dynamic-scheduler plugin settings", - json_schema_extra={"auto_default_from_env": True}, + WEBSERVER_PROJECTS: ProjectsSettings | None = Field( + json_schema_extra={"auto_default_from_env": True} ) WEBSERVER_REDIS: RedisSettings | None = Field( @@ -254,15 +258,19 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): description="tracing plugin", json_schema_extra={"auto_default_from_env": True} ) - WEBSERVER_PROJECTS: ProjectsSettings | None = Field( - description="projects plugin", json_schema_extra={"auto_default_from_env": True} - ) - WEBSERVER_RABBITMQ: RabbitSettings | None = Field( - description="rabbitmq plugin", json_schema_extra={"auto_default_from_env": True} - ) - WEBSERVER_USERS: UsersSettings | None = Field( - description="users plugin", json_schema_extra={"auto_default_from_env": True} - ) + WEBSERVER_RABBITMQ: Annotated[ + RabbitSettings | None, + Field( + json_schema_extra={"auto_default_from_env": True}, + ), + ] + + WEBSERVER_USERS: Annotated[ + UsersSettings | None, + Field( + json_schema_extra={"auto_default_from_env": True}, + ), + ] # These plugins only require (for the moment) an entry to toggle between enabled/disabled WEBSERVER_ANNOUNCEMENTS: bool = False diff --git a/services/web/server/src/simcore_service_webserver/projects/_comments_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_comments_handlers.py index 6ad8b290ba0..c4661005704 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_comments_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_comments_handlers.py @@ -31,7 +31,7 @@ from ..security.decorators import permission_required from ..utils_aiohttp import envelope_json_response from . import _comments_api, projects_api -from ._common_models import RequestContext +from ._common.models import RequestContext from .exceptions import ProjectInvalidRightsError, ProjectNotFoundError _logger = logging.getLogger(__name__) diff --git a/services/web/server/src/simcore_service_webserver/projects/_common/__init__.py b/services/web/server/src/simcore_service_webserver/projects/_common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/web/server/src/simcore_service_webserver/projects/_common_models.py b/services/web/server/src/simcore_service_webserver/projects/_common/models.py similarity index 94% rename from services/web/server/src/simcore_service_webserver/projects/_common_models.py rename to services/web/server/src/simcore_service_webserver/projects/_common/models.py index bb98b168aea..6f358378f60 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_common_models.py +++ b/services/web/server/src/simcore_service_webserver/projects/_common/models.py @@ -7,7 +7,7 @@ from models_library.projects import ProjectID from pydantic import BaseModel, ConfigDict, Field -from ..models import RequestContext +from ...models import RequestContext assert RequestContext.__name__ # nosec diff --git a/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py index 8173a7a2db6..09be8553197 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_crud_handlers.py @@ -48,7 +48,7 @@ from ..users.api import get_user_fullname from ..workspaces.errors import WorkspaceAccessForbiddenError, WorkspaceNotFoundError from . import _crud_api_create, _crud_api_read, projects_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from ._crud_handlers_models import ( ProjectActiveQueryParams, ProjectCreateHeaders, diff --git a/services/web/server/src/simcore_service_webserver/projects/_folders_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_folders_handlers.py index 2e644a4d598..c4f1828237b 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_folders_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_folders_handlers.py @@ -5,7 +5,7 @@ from models_library.folders import FolderID from models_library.projects import ProjectID from models_library.utils.common_validators import null_or_none_str_to_none_validator -from pydantic import ConfigDict, BaseModel, field_validator +from pydantic import BaseModel, ConfigDict, field_validator from servicelib.aiohttp import status from servicelib.aiohttp.requests_validation import parse_request_path_parameters_as from servicelib.aiohttp.typing_extension import Handler @@ -14,7 +14,7 @@ from ..login.decorators import login_required from ..security.decorators import permission_required from . import _folders_api -from ._common_models import RequestContext +from ._common.models import RequestContext from .exceptions import ProjectGroupNotFoundError, ProjectNotFoundError _logger = logging.getLogger(__name__) @@ -44,9 +44,9 @@ class _ProjectsFoldersPathParams(BaseModel): model_config = ConfigDict(extra="forbid") # validators - _null_or_none_str_to_none_validator = field_validator( - "folder_id", mode="before" - )(null_or_none_str_to_none_validator) + _null_or_none_str_to_none_validator = field_validator("folder_id", mode="before")( + null_or_none_str_to_none_validator + ) @routes.put( diff --git a/services/web/server/src/simcore_service_webserver/projects/_groups_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_groups_handlers.py index bf612944d4b..d507a2b1eff 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_groups_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_groups_handlers.py @@ -21,7 +21,7 @@ from ..security.decorators import permission_required from ..utils_aiohttp import envelope_json_response from . import _groups_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from ._groups_api import ProjectGroupGet from .exceptions import ProjectGroupNotFoundError, ProjectNotFoundError diff --git a/services/web/server/src/simcore_service_webserver/projects/_metadata_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_metadata_handlers.py index 802c13f7937..df139c6fd30 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_metadata_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_metadata_handlers.py @@ -30,7 +30,7 @@ from ..security.decorators import permission_required from ..utils_aiohttp import envelope_json_response from . import _metadata_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from .exceptions import ( NodeNotFoundError, ParentNodeNotFoundError, diff --git a/services/web/server/src/simcore_service_webserver/projects/_nodes_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_nodes_handlers.py index 9ddd88c0df1..514efafa47d 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_nodes_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_nodes_handlers.py @@ -74,7 +74,7 @@ from ..utils_aiohttp import envelope_json_response from ..wallets.errors import WalletAccessForbiddenError, WalletNotEnoughCreditsError from . import nodes_utils, projects_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from ._nodes_api import NodeScreenshot, get_node_screenshots from .exceptions import ( ClustersKeeperNotAvailableError, diff --git a/services/web/server/src/simcore_service_webserver/projects/_ports_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_ports_handlers.py index eaacd9c1aa3..db8be1b9cfd 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_ports_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_ports_handlers.py @@ -33,7 +33,7 @@ from ..projects._access_rights_api import check_user_project_permission from ..security.decorators import permission_required from . import _ports_api, projects_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from .db import ProjectDBAPI from .exceptions import ( NodeNotFoundError, diff --git a/services/web/server/src/simcore_service_webserver/projects/_projects_nodes_pricing_unit_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_projects_nodes_pricing_unit_handlers.py index 05bb2f8e767..2748d81061e 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_projects_nodes_pricing_unit_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_projects_nodes_pricing_unit_handlers.py @@ -21,7 +21,7 @@ from ..security.decorators import permission_required from ..utils_aiohttp import envelope_json_response from . import projects_api -from ._common_models import RequestContext +from ._common.models import RequestContext from ._nodes_handlers import NodePathParams from .db import ProjectDBAPI from .exceptions import ProjectInvalidRightsError, ProjectNotFoundError diff --git a/services/web/server/src/simcore_service_webserver/projects/_states_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_states_handlers.py index 8ec0400238c..67a21b23ece 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_states_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_states_handlers.py @@ -36,7 +36,7 @@ from ..utils_aiohttp import envelope_json_response from ..wallets.errors import WalletNotEnoughCreditsError from . import projects_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from .exceptions import ( DefaultPricingUnitNotFoundError, ProjectInvalidRightsError, diff --git a/services/web/server/src/simcore_service_webserver/projects/_trash_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_trash_handlers.py index 963b81c4900..b2063612132 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_trash_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_trash_handlers.py @@ -16,10 +16,9 @@ ) from ..login.decorators import get_user_id, login_required from ..products.api import get_product_name -from ..projects._common_models import ProjectPathParams from ..security.decorators import permission_required from . import _trash_api -from ._common_models import RemoveQueryParams +from ._common.models import ProjectPathParams, RemoveQueryParams from .exceptions import ProjectRunningConflictError, ProjectStoppingError _logger = logging.getLogger(__name__) diff --git a/services/web/server/src/simcore_service_webserver/projects/_wallets_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_wallets_handlers.py index 56e7136d299..dd66c65cb8d 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_wallets_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_wallets_handlers.py @@ -20,7 +20,7 @@ from ..wallets.errors import WalletAccessForbiddenError from . import _wallets_api as wallets_api from . import projects_api -from ._common_models import ProjectPathParams, RequestContext +from ._common.models import ProjectPathParams, RequestContext from .exceptions import ProjectInvalidRightsError, ProjectNotFoundError _logger = logging.getLogger(__name__) diff --git a/services/web/server/src/simcore_service_webserver/projects/_workspaces_handlers.py b/services/web/server/src/simcore_service_webserver/projects/_workspaces_handlers.py index ef3d20b3c5a..b5a6082cb50 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_workspaces_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/_workspaces_handlers.py @@ -17,7 +17,7 @@ from ..security.decorators import permission_required from ..workspaces.errors import WorkspaceAccessForbiddenError, WorkspaceNotFoundError from . import _workspaces_api -from ._common_models import RequestContext +from ._common.models import RequestContext from .exceptions import ProjectInvalidRightsError, ProjectNotFoundError _logger = logging.getLogger(__name__) diff --git a/services/web/server/src/simcore_service_webserver/tree.md b/services/web/server/src/simcore_service_webserver/tree.md deleted file mode 100644 index 0117a6c851e..00000000000 --- a/services/web/server/src/simcore_service_webserver/tree.md +++ /dev/null @@ -1,562 +0,0 @@ -This is a tree view of my app. It is built in python's aiohttp. - - -├── activity -│   ├── _api.py -│   ├── _handlers.py -│   ├── plugin.py -│   └── settings.py -├── announcements -│   ├── _api.py -│   ├── _handlers.py -│   ├── _models.py -│   ├── plugin.py -│   └── _redis.py -├── api_keys -│   ├── api.py -│   ├── errors.py -│   ├── _exceptions_handlers.py -│   ├── _models.py -│   ├── plugin.py -│   ├── _repository.py -│   ├── _rest.py -│   ├── _rpc.py -│   └── _service.py -├── application.py -├── application_settings.py -├── application_settings_utils.py -├── catalog -│   ├── _api.py -│   ├── _api_units.py -│   ├── client.py -│   ├── _constants.py -│   ├── exceptions.py -│   ├── _handlers_errors.py -│   ├── _handlers.py -│   ├── licenses -│   │   ├── api.py -│   │   ├── errors.py -│   │   ├── _exceptions_handlers.py -│   │   ├── _licensed_items_api.py -│   │   ├── _licensed_items_db.py -│   │   ├── _licensed_items_handlers.py -│   │   ├── _models.py -│   │   └── plugin.py -│   ├── _models.py -│   ├── plugin.py -│   ├── settings.py -│   └── _tags_handlers.py -├── cli.py -├── _constants.py -├── db -│   ├── _aiopg.py -│   ├── _asyncpg.py -│   ├── base_repository.py -│   ├── models.py -│   ├── plugin.py -│   └── settings.py -├── db_listener -│   ├── _db_comp_tasks_listening_task.py -│   ├── plugin.py -│   └── _utils.py -├── diagnostics -│   ├── _handlers.py -│   ├── _healthcheck.py -│   ├── _monitoring.py -│   ├── plugin.py -│   └── settings.py -├── director_v2 -│   ├── _abc.py -│   ├── api.py -│   ├── _api_utils.py -│   ├── _core_base.py -│   ├── _core_computations.py -│   ├── _core_dynamic_services.py -│   ├── _core_utils.py -│   ├── exceptions.py -│   ├── _handlers.py -│   ├── plugin.py -│   └── settings.py -├── dynamic_scheduler -│   ├── api.py -│   ├── plugin.py -│   └── settings.py -├── email -│   ├── _core.py -│   ├── _handlers.py -│   ├── plugin.py -│   ├── settings.py -│   └── utils.py -├── errors.py -├── exception_handling -│   ├── _base.py -│   └── _factory.py -├── exporter -│   ├── exceptions.py -│   ├── _formatter -│   │   ├── archive.py -│   │   ├── _sds.py -│   │   ├── template_json.py -│   │   └── xlsx -│   │   ├── code_description.py -│   │   ├── core -│   │   │   ├── styling_components.py -│   │   │   └── xlsx_base.py -│   │   ├── dataset_description.py -│   │   ├── manifest.py -│   │   ├── utils.py -│   │   └── writer.py -│   ├── _handlers.py -│   ├── plugin.py -│   ├── settings.py -│   └── utils.py -├── folders -│   ├── api.py -│   ├── errors.py -│   ├── _exceptions_handlers.py -│   ├── _folders_api.py -│   ├── _folders_db.py -│   ├── _folders_handlers.py -│   ├── _models.py -│   ├── plugin.py -│   ├── _trash_api.py -│   ├── _trash_handlers.py -│   ├── _workspaces_api.py -│   └── _workspaces_handlers.py -├── garbage_collector -│   ├── _core_disconnected.py -│   ├── _core_guests.py -│   ├── _core_orphans.py -│   ├── _core.py -│   ├── _core_utils.py -│   ├── plugin.py -│   ├── settings.py -│   ├── _tasks_api_keys.py -│   ├── _tasks_core.py -│   ├── _tasks_trash.py -│   └── _tasks_users.py -├── groups -│   ├── api.py -│   ├── _classifiers_api.py -│   ├── _classifiers_handlers.py -│   ├── _common -│   │   ├── exceptions_handlers.py -│   │   └── schemas.py -│   ├── exceptions.py -│   ├── _groups_api.py -│   ├── _groups_db.py -│   ├── _groups_handlers.py -│   └── plugin.py -├── invitations -│   ├── api.py -│   ├── _client.py -│   ├── _core.py -│   ├── errors.py -│   ├── plugin.py -│   └── settings.py -├── login -│   ├── _2fa_api.py -│   ├── _2fa_handlers.py -│   ├── _auth_api.py -│   ├── _auth_handlers.py -│   ├── cli.py -│   ├── _confirmation.py -│   ├── _constants.py -│   ├── decorators.py -│   ├── errors.py -│   ├── handlers_change.py -│   ├── handlers_confirmation.py -│   ├── handlers_registration.py -│   ├── _models.py -│   ├── plugin.py -│   ├── _registration_api.py -│   ├── _registration_handlers.py -│   ├── _registration.py -│   ├── _security.py -│   ├── settings.py -│   ├── _sql.py -│   ├── storage.py -│   ├── utils_email.py -│   └── utils.py -├── log.py -├── long_running_tasks.py -├── __main__.py -├── meta_modeling -│   ├── _function_nodes.py -│   ├── _handlers.py -│   ├── _iterations.py -│   ├── plugin.py -│   ├── _projects.py -│   ├── _results.py -│   └── _version_control.py -├── _meta.py -├── models.py -├── notifications -│   ├── plugin.py -│   ├── project_logs.py -│   ├── _rabbitmq_consumers_common.py -│   ├── _rabbitmq_exclusive_queue_consumers.py -│   ├── _rabbitmq_nonexclusive_queue_consumers.py -│   └── wallet_osparc_credits.py -├── payments -│   ├── api.py -│   ├── _autorecharge_api.py -│   ├── _autorecharge_db.py -│   ├── errors.py -│   ├── _events.py -│   ├── _methods_api.py -│   ├── _methods_db.py -│   ├── _onetime_api.py -│   ├── _onetime_db.py -│   ├── plugin.py -│   ├── _rpc_invoice.py -│   ├── _rpc.py -│   ├── settings.py -│   ├── _socketio.py -│   └── _tasks.py -├── products -│   ├── _api.py -│   ├── api.py -│   ├── _db.py -│   ├── errors.py -│   ├── _events.py -│   ├── _handlers.py -│   ├── _invitations_handlers.py -│   ├── _middlewares.py -│   ├── _model.py -│   ├── plugin.py -│   └── _rpc.py -├── projects -│   ├── _access_rights_api.py -│   ├── _access_rights_db.py -│   ├── api.py -│   ├── _comments_api.py -│   ├── _comments_db.py -│   ├── _comments_handlers.py -│   ├── _common_models.py -│   ├── _crud_api_create.py -│   ├── _crud_api_delete.py -│   ├── _crud_api_read.py -│   ├── _crud_handlers_models.py -│   ├── _crud_handlers.py -│   ├── db.py -│   ├── _db_utils.py -│   ├── exceptions.py -│   ├── _folders_api.py -│   ├── _folders_db.py -│   ├── _folders_handlers.py -│   ├── _groups_api.py -│   ├── _groups_db.py -│   ├── _groups_handlers.py -│   ├── lock.py -│   ├── _metadata_api.py -│   ├── _metadata_db.py -│   ├── _metadata_handlers.py -│   ├── models.py -│   ├── _nodes_api.py -│   ├── _nodes_handlers.py -│   ├── _nodes_utils.py -│   ├── nodes_utils.py -│   ├── _observer.py -│   ├── _permalink_api.py -│   ├── plugin.py -│   ├── _ports_api.py -│   ├── _ports_handlers.py -│   ├── _projects_access.py -│   ├── projects_api.py -│   ├── _projects_db.py -│   ├── _projects_nodes_pricing_unit_handlers.py -│   ├── settings.py -│   ├── _states_handlers.py -│   ├── _tags_api.py -│   ├── _tags_handlers.py -│   ├── _trash_api.py -│   ├── _trash_handlers.py -│   ├── utils.py -│   ├── _wallets_api.py -│   ├── _wallets_handlers.py -│   ├── _workspaces_api.py -│   └── _workspaces_handlers.py -├── publications -│   ├── _handlers.py -│   └── plugin.py -├── rabbitmq.py -├── rabbitmq_settings.py -├── redis.py -├── resource_manager -│   ├── _constants.py -│   ├── plugin.py -│   ├── registry.py -│   ├── settings.py -│   └── user_sessions.py -├── _resources.py -├── resource_usage -│   ├── api.py -│   ├── _client.py -│   ├── _constants.py -│   ├── errors.py -│   ├── _observer.py -│   ├── plugin.pyf -│   ├── _pricing_plans_admin_api.py -│   ├── _pricing_plans_admin_handlers.py -│   ├── _pricing_plans_api.py -│   ├── _pricing_plans_handlers.py -│   ├── _service_runs_api.py -│   ├── _service_runs_handlers.py -│   ├── settings.py -│   └── _utils.py -├── rest -│   ├── _handlers.py -│   ├── healthcheck.py -│   ├── plugin.py -│   ├── settings.py -│   └── _utils.py -├── scicrunch -│   ├── db.py -│   ├── errors.py -│   ├── models.py -│   ├── plugin.py -│   ├── _resolver.py -│   ├── _rest.py -│   ├── service_client.py -│   └── settings.py -├── security -│   ├── api.py -│   ├── _authz_access_model.py -│   ├── _authz_access_roles.py -│   ├── _authz_db.py -│   ├── _authz_policy.py -│   ├── _constants.py -│   ├── decorators.py -│   ├── _identity_api.py -│   ├── _identity_policy.py -│   └── plugin.py -├── session -│   ├── access_policies.py -│   ├── api.py -│   ├── _cookie_storage.py -│   ├── errors.py -│   ├── plugin.py -│   └── settings.py -├── socketio -│   ├── _handlers.py -│   ├── messages.py -│   ├── models.py -│   ├── _observer.py -│   ├── plugin.py -│   ├── server.py -│   └── _utils.py -├── statics -│   ├── _constants.py -│   ├── _events.py -│   ├── _handlers.py -│   ├── plugin.py -│   └── settings.py -├── storage -│   ├── api.py -│   ├── _handlers.py -│   ├── plugin.py -│   ├── schemas.py -│   └── settings.py -├── studies_dispatcher -│   ├── _catalog.py -│   ├── _constants.py -│   ├── _core.py -│   ├── _errors.py -│   ├── _models.py -│   ├── plugin.py -│   ├── _projects_permalinks.py -│   ├── _projects.py -│   ├── _redirects_handlers.py -│   ├── _rest_handlers.py -│   ├── settings.py -│   ├── _studies_access.py -│   └── _users.py -├── tags -│   ├── _api.py -│   ├── _handlers.py -│   ├── plugin.py -│   └── schemas.py -├── tracing.py -├── users -│   ├── _api.py -│   ├── api.py -│   ├── _constants.py -│   ├── _db.py -│   ├── exceptions.py -│   ├── _handlers.py -│   ├── _models.py -│   ├── _notifications_handlers.py -│   ├── _notifications.py -│   ├── plugin.py -│   ├── _preferences_api.py -│   ├── preferences_api.py -│   ├── _preferences_db.py -│   ├── _preferences_handlers.py -│   ├── _preferences_models.py -│   ├── _schemas.py -│   ├── schemas.py -│   ├── settings.py -│   ├── _tokens_handlers.py -│   └── _tokens.py -├── utils_aiohttp.py -├── utils.py -├── utils_rate_limiting.py -├── version_control -│   ├── _core.py -│   ├── db.py -│   ├── errors.py -│   ├── _handlers_base.py -│   ├── _handlers.py -│   ├── models.py -│   ├── plugin.py -│   ├── vc_changes.py -│   └── vc_tags.py -├── wallets -│   ├── _api.py -│   ├── api.py -│   ├── _constants.py -│   ├── _db.py -│   ├── errors.py -│   ├── _events.py -│   ├── _groups_api.py -│   ├── _groups_db.py -│   ├── _groups_handlers.py -│   ├── _handlers.py -│   ├── _payments_handlers.py -│   └── plugin.py -└── workspaces - ├── api.py - ├── errors.py - ├── _exceptions_handlers.py - ├── _groups_api.py - ├── _groups_db.py - ├── _groups_handlers.py - ├── _models.py - ├── plugin.py - ├── _trash_api.py - ├── _trash_handlers.py - ├── _workspaces_api.py - ├── _workspaces_db.py - └── _workspaces_handlers.py - - - - - -The top folders represent plugins that could be interprested as different domains with small compling between each other - -Here are some conventions - -- `plugin` has a setup function to setup the app (e.g. add routes, setup events etc ). Classic `setup_xxx(app)` for aiohttp -- `settings` includes pydantic settings classes specific to the domain -- `exceptions` or `errors` include only exceptions classes - - `_exceptions_handlers` are utils to handle exceptions -- `models` correspond to domain models, i.e. not part of any of the controllers interfaces. Those are denoted `scheme`. - -Then - -- `_handlers` (or _rest) represent the rest handlers (i.e. controller layer) -- `_rpc` contains handlers (server side) to an RPC interface (i.e. controller layer) -- `_api` (or `_service`) represent the business logic of this domain (i.e. service layer) - - the shared service layer for inter-domain logic is called `api` (i.e. without `_`) -- `_db` (or `_repository`) represents the repository layer - - -Could you please apply the rules on this structure and come up with a new tree that follows: -- keeps the domain-drive modular organization -- every domain implements controller-service-repository (CSR) -- highligh the shared service layer for inter-domain logic - - -As an output just recreate the tree adding some comments on it (e.g. with #) but no further explanatio is needed - - - -```plaintext -├── activity -│   ├── controllers -│   │   ├── rest_handlers.py # Rest API handlers (controller layer) -│   │   ├── rpc_handlers.py # RPC handlers (controller layer) -│   ├── services -│   │   ├── domain_service.py # Business logic for activity domain (service layer) -│   ├── repositories -│   │   ├── activity_repository.py # Data access logic (repository layer) -│   ├── plugin.py # Setup function for activity domain -│   ├── settings.py # Domain-specific settings -│   ├── exceptions.py # Domain-specific exceptions -├── announcements -│   ├── controllers -│   │   ├── rest_handlers.py # Rest API handlers (controller layer) -│   │   ├── rpc_handlers.py # RPC handlers (controller layer) -│   ├── services -│   │   ├── domain_service.py # Business logic for announcements domain (service layer) -│   ├── repositories -│   │   ├── announcements_repository.py # Data access logic (repository layer) -│   ├── plugin.py # Setup function for announcements domain -│   ├── settings.py # Domain-specific settings -│   ├── exceptions.py # Domain-specific exceptions -├── api_keys -│   ├── controllers -│   │   ├── rest_handlers.py # Rest API handlers (controller layer) -│   │   ├── rpc_handlers.py # RPC handlers (controller layer) -│   ├── services -│   │   ├── domain_service.py # Business logic for api_keys domain (service layer) -│   ├── repositories -│   │   ├── api_keys_repository.py # Data access logic (repository layer) -│   ├── plugin.py # Setup function for api_keys domain -│   ├── settings.py # Domain-specific settings -│   ├── exceptions.py # Domain-specific exceptions -├── shared_services -│   ├── inter_domain_service.py # Shared service layer for inter-domain logic -├── db -│   ├── connection.py # Database connection logic -│   ├── base_repository.py # Base repository logic -│   ├── plugin.py # Setup function for database -│   ├── settings.py # Database-specific settings -├── catalog -│   ├── controllers -│   │   ├── rest_handlers.py # Rest API handlers (controller layer) -│   │   ├── rpc_handlers.py # RPC handlers (controller layer) -│   ├── services -│   │   ├── domain_service.py # Business logic for catalog domain (service layer) -│   ├── repositories -│   │   ├── catalog_repository.py # Data access logic (repository layer) -│   ├── plugin.py # Setup function for catalog domain -│   ├── settings.py # Domain-specific settings -│   ├── exceptions.py # Domain-specific exceptions -├── users -│   ├── controllers -│   │   ├── rest_handlers.py # Rest API handlers (controller layer) -│   │   ├── rpc_handlers.py # RPC handlers (controller layer) -│   ├── services -│   │   ├── domain_service.py # Business logic for users domain (service layer) -│   ├── repositories -│   │   ├── users_repository.py # Data access logic (repository layer) -│   ├── plugin.py # Setup function for users domain -│   ├── settings.py # Domain-specific settings -│   ├── exceptions.py # Domain-specific exceptions -├── projects -│   ├── controllers -│   │   ├── rest_handlers.py # Rest API handlers (controller layer) -│   │   ├── rpc_handlers.py # RPC handlers (controller layer) -│   ├── services -│   │   ├── domain_service.py # Business logic for projects domain (service layer) -│   ├── repositories -│   │   ├── projects_repository.py # Data access logic (repository layer) -│   ├── plugin.py # Setup function for projects domain -│   ├── settings.py # Domain-specific settings -│   ├── exceptions.py # Domain-specific exceptions -├── shared -│   ├── models -│   │   ├── user.py # Shared user model -│   │   ├── project.py # Shared project model -│   ├── schemas -│   │   ├── user_schema.py # Shared user schemas -│   │   ├── project_schema.py # Shared project schemas -│   ├── utils -│   │   ├── logger.py # Shared logging logic -│   │   ├── validators.py # Shared validation logic -├── application.py # Main application initialization -└── cli.py # Command-line interface logic -``` diff --git a/services/web/server/tests/conftest.py b/services/web/server/tests/conftest.py index 26bd1d6f5dd..c474c48ab57 100644 --- a/services/web/server/tests/conftest.py +++ b/services/web/server/tests/conftest.py @@ -292,6 +292,8 @@ async def _creator( parent_project_uuid: ProjectID | None = None, parent_node_id: NodeID | None = None, ) -> ProjectDict: + assert client.app + url, project_data, expected_data, headers = await _setup( client, project=project, diff --git a/services/web/server/tests/data/test_activity_config.yml b/services/web/server/tests/data/test_activity_config.yml deleted file mode 100644 index 7fc30d83868..00000000000 --- a/services/web/server/tests/data/test_activity_config.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: "1.0" -main: - host: 127.0.0.1 - log_level: DEBUG - port: 8080 - testing: true - studies_access_enabled: True -director: - host: director - port: 8001 - version: v0 -db: - postgres: - database: simcoredb - endpoint: postgres:5432 - host: postgres - maxsize: 10 - minsize: 10 - password: simcore - port: 5432 - user: simcore -# s3: -# access_key: 'Q3AM3UQ867SPQQA43P2F' -# bucket_name: simcore -# endpoint: play.minio.io:9000 -# secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' -login: - enabled: False - registration_invitation_required: False - registration_confirmation_required: True -smtp: - sender: "OSPARC support " - host: mail.foo.com - port: 25 - tls: False - username: Null - password: Null -storage: - host: storage - port: 11111 - version: v0 -rest: - version: v0 -projects: - enabled: True -session: - secret_key: "REPLACE_ME_with_result__Fernet_generate_key=" -activity: - enabled: True - prometheus_url: http://prometheus:9090 - prometheus_username: fake - prometheus_password: fake - prometheus_api_version: v1 diff --git a/services/web/server/tests/integration/02/notifications/test_rabbitmq_consumers.py b/services/web/server/tests/integration/02/notifications/test_rabbitmq_consumers.py index f1d4aa62187..b59ddd49a7b 100644 --- a/services/web/server/tests/integration/02/notifications/test_rabbitmq_consumers.py +++ b/services/web/server/tests/integration/02/notifications/test_rabbitmq_consumers.py @@ -220,7 +220,6 @@ async def test_log_workflow( # project random_node_id_in_user_project: NodeID, user_project_id: ProjectID, - # faker: Faker, mocker: MockerFixture, ): @@ -264,7 +263,6 @@ async def test_log_workflow_only_receives_messages_if_subscribed( # project random_node_id_in_user_project: NodeID, user_project_id: ProjectID, - # faker: Faker, mocker: MockerFixture, ): @@ -333,7 +331,6 @@ async def test_progress_non_computational_workflow( # project random_node_id_in_user_project: NodeID, user_project_id: ProjectID, - # mocker: MockerFixture, ): """ diff --git a/services/web/server/tests/integration/02/scicrunch/test_scicrunch__rest.py b/services/web/server/tests/integration/02/scicrunch/test_scicrunch__rest.py index 4ae3ca6a3e1..d42e8c42e90 100644 --- a/services/web/server/tests/integration/02/scicrunch/test_scicrunch__rest.py +++ b/services/web/server/tests/integration/02/scicrunch/test_scicrunch__rest.py @@ -14,7 +14,6 @@ """ import os -from pprint import pprint from typing import Any import pytest @@ -48,7 +47,6 @@ async def test_scicrunch_openapi_specs(settings: SciCrunchSettings): async with ClientSession() as client: resp = await client.get(f"{SCICRUNCH_DEFAULT_URL}/swagger-docs/swagger.json") openapi_specs = await resp.json() - pprint(openapi_specs["info"]) expected_api_version = 1 assert openapi_specs["info"]["version"] == expected_api_version @@ -65,7 +63,6 @@ async def test_scicrunch_get_all_versions( ): async with ClientSession() as client: versions = await get_all_versions(rrid, client, settings) - pprint(versions) assert versions @@ -85,7 +82,6 @@ async def test_scicrunch_get_all_versions_with_invalid_rrids( ): async with ClientSession() as client: versions = await get_all_versions(rrid, client, settings) - pprint(versions) # invalid keys return success but an empty list of versions! assert isinstance(versions, list) diff --git a/services/web/server/tests/integration/02/test_computation.py b/services/web/server/tests/integration/02/test_computation.py index 36cb5f972f4..75c22ef3ac7 100644 --- a/services/web/server/tests/integration/02/test_computation.py +++ b/services/web/server/tests/integration/02/test_computation.py @@ -199,7 +199,7 @@ def _assert_db_contents( for task_db in tasks_db: assert task_db.project_id == project_id - assert task_db.node_id in mock_pipeline.keys() + assert task_db.node_id in mock_pipeline assert task_db.inputs == mock_pipeline[task_db.node_id].get("inputs") diff --git a/services/web/server/tests/unit/isolated/exporter/test_exporter_formatter_archive.py b/services/web/server/tests/unit/isolated/exporter/test_exporter_formatter_archive.py index 906b465a3a6..5d468b043a4 100644 --- a/services/web/server/tests/unit/isolated/exporter/test_exporter_formatter_archive.py +++ b/services/web/server/tests/unit/isolated/exporter/test_exporter_formatter_archive.py @@ -1,8 +1,8 @@ # pylint:disable=redefined-outer-name import tempfile +from collections.abc import Iterator from pathlib import Path -from typing import Iterator import pytest from faker import Faker diff --git a/services/web/server/tests/unit/isolated/test__configs.py b/services/web/server/tests/unit/isolated/test__configs.py index ccd4247ea77..c811b9a0b92 100644 --- a/services/web/server/tests/unit/isolated/test__configs.py +++ b/services/web/server/tests/unit/isolated/test__configs.py @@ -16,7 +16,8 @@ @pytest.fixture(scope="session") def app_config_schema(): - raise RuntimeError("DEPRECATED. MUST NOT BE USED") + msg = "DEPRECATED. MUST NOT BE USED" + raise RuntimeError(msg) @pytest.fixture(scope="session") @@ -33,7 +34,7 @@ def service_webserver_environ( ), # defined if pip install --edit (but not in travis!) } - webserver_environ = eval_service_environ( + return eval_service_environ( services_docker_compose_file, "webserver", host_environ, @@ -41,8 +42,6 @@ def service_webserver_environ( use_env_devel=True, ) - return webserver_environ - @pytest.fixture(scope="session") def app_submodules_with_setup_funs(package_dir: Path) -> set[ModuleType]: diff --git a/services/web/server/tests/unit/isolated/test__redirections.py b/services/web/server/tests/unit/isolated/test__redirections.py index 3025ac8edb1..6274087326f 100644 --- a/services/web/server/tests/unit/isolated/test__redirections.py +++ b/services/web/server/tests/unit/isolated/test__redirections.py @@ -5,10 +5,12 @@ import asyncio import textwrap +from collections.abc import Awaitable, Callable from pathlib import Path import pytest from aiohttp import web +from aiohttp.test_utils import TestClient @pytest.fixture @@ -34,36 +36,42 @@ def index_static_path(tmpdir): @pytest.fixture -def client(event_loop: asyncio.AbstractEventLoop, aiohttp_client, index_static_path): +def client( + event_loop: asyncio.AbstractEventLoop, + aiohttp_client: Callable[..., Awaitable[TestClient]], + index_static_path, +): routes = web.RouteTableDef() @routes.get("/") async def get_root(_request): - raise web.HTTPOk() + raise web.HTTPOk @routes.get("/other") async def get_other(_request): - raise web.HTTPOk() + raise web.HTTPOk @routes.get("/redirect-to-other") async def get_redirect_to_other(request): - raise web.HTTPFound("/other") + msg = "/other" + raise web.HTTPFound(msg) @routes.get("/redirect-to-static") async def get_redirect_to_static(_request): - raise web.HTTPFound("/statics/index.html") + msg = "/statics/index.html" + raise web.HTTPFound(msg) @routes.get("/redirect-to-root") async def get_redirect_to_root(_request): - raise web.HTTPFound("/") + msg = "/" + raise web.HTTPFound(msg) routes.static("/statics", index_static_path) app = web.Application() app.add_routes(routes) - cli = event_loop.run_until_complete(aiohttp_client(app)) - return cli + return event_loop.run_until_complete(aiohttp_client(app)) @pytest.mark.parametrize("test_path", ["/", "/other"]) @@ -73,7 +81,7 @@ async def test_preserves_fragments(client, test_path): assert resp.real_url.fragment == "this/is/a/fragment" -@pytest.mark.xfail +@pytest.mark.xfail() @pytest.mark.parametrize( "test_path,expected_redirected_path", [ diff --git a/services/web/server/tests/unit/isolated/test_activity.py b/services/web/server/tests/unit/isolated/test_activity.py index b8d97b92c67..b08543a9996 100644 --- a/services/web/server/tests/unit/isolated/test_activity.py +++ b/services/web/server/tests/unit/isolated/test_activity.py @@ -3,21 +3,25 @@ # pylint:disable=redefined-outer-name import asyncio -from collections.abc import Callable -from pathlib import Path +import os +from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import MagicMock import pytest -import yaml from aiohttp.client_exceptions import ClientConnectionError from aiohttp.test_utils import TestClient from pytest_mock import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict from servicelib.aiohttp import status from servicelib.aiohttp.application import create_safe_application from simcore_service_webserver.activity.plugin import setup_activity -from simcore_service_webserver.application_settings import setup_settings +from simcore_service_webserver.application_settings import ( + PrometheusSettings, + setup_settings, +) from simcore_service_webserver.rest.plugin import setup_rest from simcore_service_webserver.security.plugin import setup_security from simcore_service_webserver.session.plugin import setup_session @@ -55,29 +59,61 @@ def mocked_monitoring_down(mocker: MockerFixture) -> None: @pytest.fixture -def app_config(fake_data_dir: Path, osparc_simcore_root_dir: Path) -> dict[str, Any]: - with Path.open(fake_data_dir / "test_activity_config.yml") as fh: - content = fh.read() - config = content.replace( - "${OSPARC_SIMCORE_REPO_ROOTDIR}", str(osparc_simcore_root_dir) - ) +def app_environment( + mock_env_devel_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch +) -> EnvVarsDict: + + envs = mock_env_devel_environment | setenvs_from_dict( + monkeypatch, + { + "LOGIN_REGISTRATION_CONFIRMATION_REQUIRED": "True", + "LOGIN_REGISTRATION_INVITATION_REQUIRED": "False", + "POSTGRES_DB": "simcoredb", + "POSTGRES_HOST": "postgres", + "POSTGRES_MAXSIZE": "10", + "POSTGRES_MINSIZE": "10", + "POSTGRES_PASSWORD": "simcore", + "POSTGRES_PORT": "5432", + "POSTGRES_USER": "simcore", + "PROMETHEUS_PASSWORD": "fake", + "PROMETHEUS_URL": "http://prometheus:9090", + "PROMETHEUS_USERNAME": "fake", + "PROMETHEUS_VTAG": "v1", + "SESSION_SECRET_KEY": "REPLACE_ME_with_result__Fernet_generate_key=", + "SMTP_HOST": "mail.foo.com", + "SMTP_PORT": "25", + "STORAGE_HOST": "storage", + "STORAGE_PORT": "11111", + "STORAGE_VTAG": "v0", + "WEBSERVER_LOGIN": "null", + "WEBSERVER_LOGLEVEL": "DEBUG", + "WEBSERVER_PORT": "8080", + "WEBSERVER_STUDIES_ACCESS_ENABLED": "True", + }, + ) + + monkeypatch.delenv("WEBSERVER_ACTIVITY") + envs.pop("WEBSERVER_ACTIVITY") - return yaml.safe_load(config) + return envs @pytest.fixture def client( event_loop: asyncio.AbstractEventLoop, - aiohttp_client: Callable, - app_config: dict[str, Any], + aiohttp_client: Callable[..., Awaitable[TestClient]], mock_orphaned_services: MagicMock, - monkeypatch_setenv_from_app_config: Callable, + app_environment: EnvVarsDict, ): - monkeypatch_setenv_from_app_config(app_config) + # app_environment are in place + assert {key: os.environ[key] for key in app_environment} == app_environment + expected_activity_settings = PrometheusSettings.create_from_envs() + + app = create_safe_application() - app = create_safe_application(app_config) + settings = setup_settings(app) + assert expected_activity_settings == settings.WEBSERVER_ACTIVITY - assert setup_settings(app) setup_session(app) setup_security(app) setup_rest(app) @@ -92,7 +128,7 @@ async def test_has_login_required(client: TestClient): async def test_monitoring_up( - mocked_login_required: None, mocked_monitoring: None, client + mocked_login_required: None, mocked_monitoring: None, client: TestClient ): RUNNING_NODE_ID = "894dd8d5-de3b-4767-950c-7c3ed8f51d8c" diff --git a/services/web/server/tests/unit/isolated/test_application_settings_utils.py b/services/web/server/tests/unit/isolated/test_application_settings_utils.py index 77195f3d02a..9db5ed0dd48 100644 --- a/services/web/server/tests/unit/isolated/test_application_settings_utils.py +++ b/services/web/server/tests/unit/isolated/test_application_settings_utils.py @@ -1,6 +1,5 @@ from collections.abc import Callable -import pytest from simcore_service_webserver.application_settings import ApplicationSettings from simcore_service_webserver.application_settings_utils import ( AppConfigDict, @@ -9,19 +8,14 @@ ) -@pytest.mark.skip(reason="UNDER DEV") def test_settings_infered_from_default_tests_config( default_app_cfg: AppConfigDict, monkeypatch_setenv_from_app_config: Callable ): - # TODO: use app_config_for_production_legacy envs = monkeypatch_setenv_from_app_config(default_app_cfg) - assert envs == convert_to_environ_vars(default_app_cfg) + assert envs == { + k: f"{v}" for k, v in convert_to_environ_vars(default_app_cfg).items() + } settings = ApplicationSettings.create_from_envs() - print("settings=\n", settings.model_dump_json(indent=1)) - - infered_config = convert_to_app_config(settings) - - assert default_app_cfg == infered_config - assert set(default_app_cfg.keys()) == set(infered_config.keys()) + assert convert_to_app_config(settings) diff --git a/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py b/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py index b35b2b378f4..01626715b48 100644 --- a/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py +++ b/services/web/server/tests/unit/isolated/test_diagnostics_healthcheck.py @@ -7,8 +7,8 @@ import asyncio import json import logging -from collections.abc import Callable, Coroutine import time +from collections.abc import Awaitable, Callable, Coroutine import pytest import simcore_service_webserver @@ -89,12 +89,14 @@ def mock_environment( { **mock_env_devel_environment, "AIODEBUG_SLOW_DURATION_SECS": f"{SLOW_HANDLER_DELAY_SECS / 10}", - "WEBSERVER_DIAGNOSTICS": json.dumps({ - "DIAGNOSTICS_MAX_AVG_LATENCY": "2.0", - "DIAGNOSTICS_MAX_TASK_DELAY": f"{SLOW_HANDLER_DELAY_SECS}", - "DIAGNOSTICS_START_SENSING_DELAY": f"{0}", - "DIAGNOSTICS_HEALTHCHECK_ENABLED": "1", - }), + "WEBSERVER_DIAGNOSTICS": json.dumps( + { + "DIAGNOSTICS_MAX_AVG_LATENCY": "2.0", + "DIAGNOSTICS_MAX_TASK_DELAY": f"{SLOW_HANDLER_DELAY_SECS}", + "DIAGNOSTICS_START_SENSING_DELAY": f"{0}", + "DIAGNOSTICS_HEALTHCHECK_ENABLED": "1", + } + ), "SC_HEALTHCHECK_TIMEOUT": "2m", }, ) @@ -104,7 +106,7 @@ def mock_environment( def client( event_loop: asyncio.AbstractEventLoop, unused_tcp_port_factory: Callable, - aiohttp_client: Callable, + aiohttp_client: Callable[..., Awaitable[TestClient]], api_version_prefix: str, mock_environment: EnvVarsDict, ) -> TestClient: diff --git a/services/web/server/tests/unit/isolated/test_login_settings.py b/services/web/server/tests/unit/isolated/test_login_settings.py index 0bfff446911..9a92dad7542 100644 --- a/services/web/server/tests/unit/isolated/test_login_settings.py +++ b/services/web/server/tests/unit/isolated/test_login_settings.py @@ -34,7 +34,7 @@ def twilio_config(monkeypatch: pytest.MonkeyPatch) -> dict[str, str]: "TWILIO_MESSAGING_SID": "x" * 34, } # NOTE: enforces DELETE-ENV since apparently some session-based fixtures are settings these envs - for key in TWILO_CONFIG.keys(): + for key in TWILO_CONFIG: monkeypatch.delenv(key, raising=False) return TWILO_CONFIG diff --git a/services/web/server/tests/unit/isolated/test_projects__db_utils.py b/services/web/server/tests/unit/isolated/test_projects__db_utils.py index c8c4da57eda..2a5203ae137 100644 --- a/services/web/server/tests/unit/isolated/test_projects__db_utils.py +++ b/services/web/server/tests/unit/isolated/test_projects__db_utils.py @@ -5,9 +5,10 @@ import datetime import json import re +from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable +from typing import Any import pytest from faker import Faker @@ -95,7 +96,7 @@ def test_convert_to_schema_names(fake_project: dict[str, Any]): assert col is not None # test date time conversion - date = datetime.datetime.now(datetime.timezone.utc) + date = datetime.datetime.now(datetime.UTC) db_entries["creation_date"] = date schema_entries = convert_to_schema_names(db_entries, fake_project["prjOwner"]) assert "creationDate" in schema_entries @@ -110,7 +111,7 @@ def test_convert_to_schema_names_camel_casing(fake_db_dict): assert "anEntryThatUsesSnakeCase" in db_entries assert "anotherEntryThatUsesSnakeCase" in db_entries # test date time conversion - date = datetime.datetime.now(datetime.timezone.utc) + date = datetime.datetime.now(datetime.UTC) fake_db_dict["time_entry"] = date db_entries = convert_to_schema_names(fake_db_dict, fake_email) assert "timeEntry" in db_entries diff --git a/services/web/server/tests/unit/isolated/test_rest.py b/services/web/server/tests/unit/isolated/test_rest.py index a10592b7757..31fdba39eac 100644 --- a/services/web/server/tests/unit/isolated/test_rest.py +++ b/services/web/server/tests/unit/isolated/test_rest.py @@ -3,7 +3,7 @@ # pylint:disable=no-name-in-module import asyncio -from collections.abc import Callable +from collections.abc import Awaitable, Callable from http import HTTPStatus from unittest.mock import MagicMock @@ -24,7 +24,7 @@ def client( event_loop: asyncio.AbstractEventLoop, unused_tcp_port_factory: Callable, - aiohttp_client: Callable, + aiohttp_client: Callable[..., Awaitable[TestClient]], api_version_prefix: str, mock_env_devel_environment: EnvVarsDict, mock_env_deployer_pipeline: EnvVarsDict, @@ -48,10 +48,9 @@ async def slow_handler(request: web.Request): app.router.add_get("/slow", slow_handler) - cli = event_loop.run_until_complete( + return event_loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) - return cli async def test_frontend_config( 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 5ba406db510..2fb280cf25f 100644 --- a/services/web/server/tests/unit/isolated/test_security__authz.py +++ b/services/web/server/tests/unit/isolated/test_security__authz.py @@ -97,10 +97,7 @@ def test_unique_permissions(): for permission in can: assert ( permission not in used - ), "'{}' in {} is repeated in security_roles.ROLES_PERMISSIONS".format( - permission, - role, - ) + ), f"'{permission}' in {role} is repeated in security_roles.ROLES_PERMISSIONS" used.append(permission) @@ -263,7 +260,7 @@ async def _fake_db(engine, email): raise DatabaseError # inactive user or not found - return copy.deepcopy(users_db.get(email, None)) + return copy.deepcopy(users_db.get(email)) mock_db_fun = mocker.patch( "simcore_service_webserver.security._authz_policy.get_active_user_or_none", diff --git a/services/web/server/tests/unit/isolated/test_security_api.py b/services/web/server/tests/unit/isolated/test_security_api.py index 079fa68e529..b913d95e5a7 100644 --- a/services/web/server/tests/unit/isolated/test_security_api.py +++ b/services/web/server/tests/unit/isolated/test_security_api.py @@ -6,7 +6,7 @@ import asyncio import statistics from collections import OrderedDict -from collections.abc import Callable +from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import MagicMock @@ -205,7 +205,7 @@ async def _logout(request: web.Request): @pytest.fixture def client( event_loop: asyncio.AbstractEventLoop, - aiohttp_client: Callable, + aiohttp_client: Callable[..., Awaitable[TestClient]], mocker: MockerFixture, app_products: OrderedDict[str, Product], set_products_in_app_state: Callable[ diff --git a/services/web/server/tests/unit/isolated/test_studies_dispatcher_projects_permalinks.py b/services/web/server/tests/unit/isolated/test_studies_dispatcher_projects_permalinks.py index 6858ac07ad9..f4283405108 100644 --- a/services/web/server/tests/unit/isolated/test_studies_dispatcher_projects_permalinks.py +++ b/services/web/server/tests/unit/isolated/test_studies_dispatcher_projects_permalinks.py @@ -61,7 +61,6 @@ def app_environment( "WEBSERVER_TRACING": "null", "WEBSERVER_VERSION_CONTROL": "0", "WEBSERVER_WALLETS": "0", - # "STUDIES_ACCESS_ANONYMOUS_ALLOWED": "1", }, ) @@ -112,12 +111,12 @@ def test_create_permalink(fake_get_project_request: web.Request, is_public: bool def valid_project_kwargs( request: pytest.FixtureRequest, fake_get_project_request: web.Request ): - return dict( - project_uuid=fake_get_project_request.match_info["project_uuid"], - project_type=ProjectType.TEMPLATE, - project_access_rights={"1": {"read": True, "write": False, "delete": False}}, - project_is_public=request.param, - ) + return { + "project_uuid": fake_get_project_request.match_info["project_uuid"], + "project_type": ProjectType.TEMPLATE, + "project_access_rights": {"1": {"read": True, "write": False, "delete": False}}, + "project_is_public": request.param, + } def test_permalink_only_for_template_projects( diff --git a/services/web/server/tests/unit/isolated/test_studies_dispatcher_settings.py b/services/web/server/tests/unit/isolated/test_studies_dispatcher_settings.py index 5c4377d56fd..c2d9a8b1499 100644 --- a/services/web/server/tests/unit/isolated/test_studies_dispatcher_settings.py +++ b/services/web/server/tests/unit/isolated/test_studies_dispatcher_settings.py @@ -19,11 +19,10 @@ @pytest.fixture def environment(monkeypatch: pytest.MonkeyPatch) -> EnvVarsDict: - envs = setenvs_from_dict( + return setenvs_from_dict( monkeypatch, envs=StudiesDispatcherSettings.model_config["json_schema_extra"]["example"], ) - return envs def test_studies_dispatcher_settings(environment: EnvVarsDict): diff --git a/services/web/server/tests/unit/isolated/test_utils_rate_limiting.py b/services/web/server/tests/unit/isolated/test_utils_rate_limiting.py index 6568b1b7db4..c6da8adf736 100644 --- a/services/web/server/tests/unit/isolated/test_utils_rate_limiting.py +++ b/services/web/server/tests/unit/isolated/test_utils_rate_limiting.py @@ -4,7 +4,8 @@ import asyncio import time -from typing import Callable +from collections.abc import Awaitable, Callable +from typing import Annotated import pytest from aiohttp import web @@ -12,7 +13,6 @@ from aiohttp.web_exceptions import HTTPOk, HTTPTooManyRequests from pydantic import Field, TypeAdapter, ValidationError from simcore_service_webserver.utils_rate_limiting import global_rate_limit_route -from typing_extensions import Annotated TOTAL_TEST_TIME = 1 # secs MAX_NUM_REQUESTS = 3 @@ -28,7 +28,10 @@ async def get_ok_handler(_request: web.Request): @pytest.fixture -def client(event_loop, aiohttp_client: Callable) -> TestClient: +def client( + event_loop, + aiohttp_client: Callable[..., Awaitable[TestClient]], +) -> TestClient: app = web.Application() app.router.add_get("/", get_ok_handler) diff --git a/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_users.py b/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_users.py index 0575ae5a4ff..7661d74443e 100644 --- a/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_users.py +++ b/services/web/server/tests/unit/with_dbs/01/groups/test_groups_handlers_users.py @@ -4,9 +4,8 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -from collections.abc import AsyncIterator +from collections.abc import AsyncIterable, AsyncIterator from contextlib import AsyncExitStack -from typing import AsyncIterable import pytest from aiohttp.test_utils import TestClient diff --git a/services/web/server/tests/unit/with_dbs/01/test_groups_handlers_classifers.py b/services/web/server/tests/unit/with_dbs/01/test_groups_handlers_classifers.py index c7367b03b94..02a0ddf581b 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_groups_handlers_classifers.py +++ b/services/web/server/tests/unit/with_dbs/01/test_groups_handlers_classifers.py @@ -1,61 +1,50 @@ -# pylint:disable=unused-variable -# pylint:disable=unused-argument -# pylint:disable=redefined-outer-name +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments import re -from copy import deepcopy import pytest from aiohttp import web_exceptions from aioresponses.core import aioresponses -from simcore_service_webserver.application_settings_utils import AppConfigDict +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict @pytest.fixture -def app_cfg(default_app_cfg: AppConfigDict, unused_tcp_port_factory): - """App's configuration used for every test in this module - - NOTE: Overrides services/web/server/tests/unit/with_dbs/conftest.py::app_cfg to influence app setup - """ - cfg = deepcopy(default_app_cfg) - - cfg["main"]["port"] = unused_tcp_port_factory() - cfg["main"]["studies_access_enabled"] = True - - exclude = { - "tracing", - "smtp", - "storage", - "activity", - "diagnostics", - "tags", - "publications", - "catalog", - "computation", - "products", - "socketio", - "resource_manager", - "projects", - "login", - "users", - } - include = { - "db", - "rest", - "groups", - } - - assert include.intersection(exclude) == set() - - for section in include: - cfg[section]["enabled"] = True - for section in exclude: - cfg[section]["enabled"] = False - - # NOTE: To see logs, use pytest -s --log-cli-level=DEBUG - ## setup_logging(level=logging.DEBUG) - - return cfg +def app_environment( + monkeypatch: pytest.MonkeyPatch, + app_environment: EnvVarsDict, +) -> EnvVarsDict: + + monkeypatch.delenv("WEBSERVER_STUDIES_DISPATCHER", raising=False) + app_environment.pop("WEBSERVER_STUDIES_DISPATCHER", None) + + return app_environment | setenvs_from_dict( + monkeypatch, + { + # exclude + "WEBSERVER_ACTIVITY": "null", + "WEBSERVER_CATALOG": "null", + "WEBSERVER_CLUSTERS": "null", + "WEBSERVER_COMPUTATION": "null", + "WEBSERVER_DIAGNOSTICS": "null", + "WEBSERVER_EMAIL": "null", + "WEBSERVER_GARBAGE_COLLECTOR": "null", + "WEBSERVER_GROUPS": "0", + "WEBSERVER_LOGIN": "null", + "WEBSERVER_PRODUCTS": "0", + "WEBSERVER_PROJECTS": "null", + "WEBSERVER_PUBLICATIONS": "0", + "WEBSERVER_SOCKETIO": "0", + "WEBSERVER_STORAGE": "null", + "WEBSERVER_RESOURCE_MANAGER": "null", + "WEBSERVER_TAGS": "0", + "WEBSERVER_TRACING": "null", + "WEBSERVER_USERS": "null", + }, + ) @pytest.mark.skip(reason="UNDER DEV: test_group_handlers") diff --git a/services/web/server/tests/unit/with_dbs/01/test_guests_management.py b/services/web/server/tests/unit/with_dbs/01/test_guests_management.py deleted file mode 100644 index dae525ca89a..00000000000 --- a/services/web/server/tests/unit/with_dbs/01/test_guests_management.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint:disable=unused-variable -# pylint:disable=unused-argument -# pylint:disable=redefined-outer-name - -from copy import deepcopy - -import pytest -from simcore_service_webserver import application - - -@pytest.fixture -def client(event_loop, aiohttp_client, app_cfg, postgres_db): - - # config app - cfg = deepcopy(app_cfg) - port = cfg["main"]["port"] - cfg["projects"]["enabled"] = True - - app = application.create_application() - - # server and client - return event_loop.run_until_complete( - aiohttp_client(app, server_kwargs={"port": port, "host": "localhost"}) - ) - - -@pytest.mark.skip(reason="Under dev") -def test_users_projects_db(client): - # given schema, an easy way to produce projects? - # a language to emulate UI?? - # See https://github.com/cwacek/python-jsonschema-objects - # - # api/specs/webserver/v0/components/schemas/project-v0.0.1.json - # create_project(client.app, ) - pass - - -@pytest.mark.skip(reason="Under dev") -def test_cleanup_expired_guest_users(client): - pass - # Guests users expire - - # Shall delete all guest users and non-shared owned projects - # that expired. - - # Shall remove all resources (data and running services) of expired users - - -@pytest.mark.skip(reason="Under dev -> resource management ") -def test_limit_guest_users(client): - # a guest user has limited access to resources - # also limited time and amount - # - pass diff --git a/services/web/server/tests/unit/with_dbs/01/test_statics.py b/services/web/server/tests/unit/with_dbs/01/test_statics.py index 1eb8212d986..d3ba4448061 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_statics.py +++ b/services/web/server/tests/unit/with_dbs/01/test_statics.py @@ -5,7 +5,6 @@ import json import re from collections.abc import Callable -from copy import deepcopy import pytest import sqlalchemy as sa @@ -16,9 +15,7 @@ from servicelib.aiohttp import status from servicelib.aiohttp.application import create_safe_application from simcore_postgres_database.models.products import products -from simcore_service_webserver._meta import API_VTAG from simcore_service_webserver.application_settings import setup_settings -from simcore_service_webserver.application_settings_utils import AppConfigDict from simcore_service_webserver.db.plugin import setup_db from simcore_service_webserver.products.plugin import setup_products from simcore_service_webserver.rest.plugin import setup_rest @@ -53,30 +50,19 @@ def client( app_environment: EnvVarsDict, event_loop: asyncio.AbstractEventLoop, aiohttp_client: Callable, - app_cfg: AppConfigDict, postgres_db: sa.engine.Engine, - monkeypatch_setenv_from_app_config: Callable, ) -> TestClient: - cfg = deepcopy(app_cfg) - - port = cfg["main"]["port"] - - assert cfg["rest"]["version"] == API_VTAG - monkeypatch_setenv_from_app_config(cfg) - - # fake config - app = create_safe_application(cfg) + app = create_safe_application() settings = setup_settings(app) - print(settings.model_dump_json(indent=1)) - + assert settings.WEBSERVER_STATICWEB setup_rest(app) setup_db(app) setup_products(app) - setup_statics(app) + assert setup_statics(app) return event_loop.run_until_complete( - aiohttp_client(app, server_kwargs={"port": port, "host": "localhost"}) + aiohttp_client(app, server_kwargs={"host": "localhost"}) ) diff --git a/services/web/server/tests/unit/with_dbs/01/test_storage.py b/services/web/server/tests/unit/with_dbs/01/test_storage.py index e6977b67c6d..e03c838cd0a 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_storage.py +++ b/services/web/server/tests/unit/with_dbs/01/test_storage.py @@ -1,13 +1,18 @@ -# pylint:disable=unused-import -# pylint:disable=unused-argument -# pylint:disable=redefined-outer-name +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments +import asyncio +from collections.abc import Awaitable, Callable from urllib.parse import quote import pytest from aiohttp import web +from aiohttp.test_utils import TestClient, TestServer from faker import Faker from pytest_simcore.helpers.assert_checks import assert_status +from pytest_simcore.helpers.typing_env import EnvVarsDict from servicelib.aiohttp import status from servicelib.aiohttp.application import create_safe_application from simcore_postgres_database.models.users import UserRole @@ -17,10 +22,12 @@ # TODO: create a fake storage service here @pytest.fixture() -def storage_server(event_loop, aiohttp_server, app_cfg): - cfg = app_cfg["storage"] - app = create_safe_application(cfg) - +def storage_server( + event_loop: asyncio.AbstractEventLoop, + aiohttp_server: Callable[..., Awaitable[TestServer]], + app_environment: EnvVarsDict, + storage_test_server_port: int, +) -> TestServer: async def _get_locs(request: web.Request): assert not request.can_read_body @@ -119,11 +126,15 @@ async def _get_datasets_meta(request: web.Request): } ) - storage_api_version = cfg["version"] + storage_api_version = app_environment["STORAGE_VTAG"] + storage_port = int(app_environment["STORAGE_PORT"]) + assert storage_port == storage_test_server_port + assert ( storage_api_version != API_VERSION ), "backend service w/ different version as webserver entrypoint" + app = create_safe_application() app.router.add_get(f"/{storage_api_version}/locations", _get_locs) app.router.add_post( f"/{storage_api_version}/locations/0:sync", _post_sync_meta_data @@ -140,10 +151,7 @@ async def _get_datasets_meta(request: web.Request): _get_datasets_meta, ) - assert cfg["host"] == "localhost" - - server = event_loop.run_until_complete(aiohttp_server(app, port=cfg["port"])) - return server + return event_loop.run_until_complete(aiohttp_server(app, port=storage_port)) # -------------------------------------------------------------------------- @@ -159,7 +167,9 @@ async def _get_datasets_meta(request: web.Request): (UserRole.TESTER, status.HTTP_200_OK), ], ) -async def test_get_storage_locations(client, storage_server, logged_user, expected): +async def test_get_storage_locations( + client: TestClient, storage_server: TestServer, logged_user, expected +): url = "/v0/storage/locations" assert url.startswith(PREFIX) @@ -181,7 +191,9 @@ async def test_get_storage_locations(client, storage_server, logged_user, expect (UserRole.ADMIN, status.HTTP_200_OK), ], ) -async def test_sync_file_meta_table(client, storage_server, logged_user, expected): +async def test_sync_file_meta_table( + client: TestClient, storage_server: TestServer, logged_user, expected +): url = "/v0/storage/locations/0:sync" assert url.startswith(PREFIX) @@ -203,7 +215,9 @@ async def test_sync_file_meta_table(client, storage_server, logged_user, expecte (UserRole.TESTER, status.HTTP_200_OK), ], ) -async def test_get_datasets_metadata(client, storage_server, logged_user, expected): +async def test_get_datasets_metadata( + client: TestClient, storage_server: TestServer, logged_user, expected +): url = "/v0/storage/locations/0/datasets" assert url.startswith(PREFIX) @@ -229,7 +243,7 @@ async def test_get_datasets_metadata(client, storage_server, logged_user, expect ], ) async def test_get_files_metadata_dataset( - client, storage_server, logged_user, expected + client: TestClient, storage_server: TestServer, logged_user, expected ): url = "/v0/storage/locations/0/datasets/N:asdfsdf/metadata" assert url.startswith(PREFIX) @@ -258,7 +272,7 @@ async def test_get_files_metadata_dataset( ], ) async def test_storage_file_meta( - client, storage_server, logged_user, expected, faker: Faker + client: TestClient, storage_server: TestServer, logged_user, expected, faker: Faker ): # tests redirect of path with quotes in path file_id = f"{faker.uuid4()}/{faker.uuid4()}/a/b/c/d/e/dat" @@ -284,7 +298,9 @@ async def test_storage_file_meta( (UserRole.TESTER, status.HTTP_200_OK), ], ) -async def test_storage_list_filter(client, storage_server, logged_user, expected): +async def test_storage_list_filter( + client: TestClient, storage_server: TestServer, logged_user, expected +): # tests composition of 2 queries file_id = "a/b/c/d/e/dat" url = "/v0/storage/locations/0/files/metadata?uuid_filter={}".format( diff --git a/services/web/server/tests/unit/with_dbs/02/test_project_lock.py b/services/web/server/tests/unit/with_dbs/02/test_project_lock.py index 5c33586e151..71d4602a1f8 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_project_lock.py +++ b/services/web/server/tests/unit/with_dbs/02/test_project_lock.py @@ -122,7 +122,8 @@ async def test_raise_exception_while_locked_release_lock( ) assert redis_value # now raising an exception - raise ValueError("pytest exception") + msg = "pytest exception" + raise ValueError(msg) # now the lock shall be released redis_value = await redis_locks_client.get( PROJECT_REDIS_LOCK_KEY.format(project_uuid) diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__list.py b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__list.py index f0147c7eb02..d16058179be 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__list.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_crud_handlers__list.py @@ -32,7 +32,7 @@ def _extract(dikt, keys): modified = [ "lastChangeDate", ] - keep = [k for k in update_data.keys() if k not in modified] + keep = [k for k in update_data if k not in modified] assert _extract(current_project, keep) == _extract(update_data, keep) @@ -86,7 +86,7 @@ async def _list_projects( ) ) if exp_offset <= 0: - assert links["prev"] == None + assert links["prev"] is None else: assert links["prev"] == str( URL(complete_url).update_query( @@ -94,7 +94,7 @@ async def _list_projects( ) ) if exp_offset >= (exp_last_page * exp_limit): - assert links["next"] == None + assert links["next"] is None else: assert links["next"] == str( URL(complete_url).update_query( diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_groups_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_groups_handlers.py index fe3ecfa8c3a..76569151068 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_groups_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_groups_handlers.py @@ -111,9 +111,9 @@ async def test_projects_groups_full_workflow( data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 2 assert data[1]["gid"] == new_user["primary_gid"] - assert data[1]["read"] == True - assert data[1]["write"] == False - assert data[1]["delete"] == False + assert data[1]["read"] is True + assert data[1]["write"] is False + assert data[1]["delete"] is False # Get the project endpoint and check the permissions url = client.app.router["get_project"].url_for( @@ -163,9 +163,9 @@ async def test_projects_groups_full_workflow( ) data, _ = await assert_status(resp, status.HTTP_200_OK) assert data["gid"] == new_user["primary_gid"] - assert data["read"] == True - assert data["write"] == True - assert data["delete"] == False + assert data["read"] is True + assert data["write"] is True + assert data["delete"] is False # List the project groups url = client.app.router["list_project_groups"].url_for( @@ -175,9 +175,9 @@ async def test_projects_groups_full_workflow( data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 2 assert data[1]["gid"] == new_user["primary_gid"] - assert data[1]["read"] == True - assert data[1]["write"] == True - assert data[1]["delete"] == False + assert data[1]["read"] is True + assert data[1]["write"] is True + assert data[1]["delete"] is False # Get the project endpoint and check the permissions url = client.app.router["get_project"].url_for( diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py index ca193544302..ce87970f75c 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_metadata_handlers.py @@ -43,7 +43,6 @@ async def test_custom_metadata_handlers( # for deletion mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, - # client: TestClient, faker: Faker, logged_user: UserInfoDict, @@ -118,7 +117,6 @@ async def test_new_project_with_parent_project_node( # for deletion mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, - # client: TestClient, logged_user: UserInfoDict, primary_group: dict[str, str], @@ -196,7 +194,6 @@ async def test_new_project_with_invalid_parent_project_node( # for deletion mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, - # client: TestClient, logged_user: UserInfoDict, primary_group: dict[str, str], @@ -280,7 +277,6 @@ async def test_set_project_parent_backward_compatibility( # for deletion mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, - # client: TestClient, logged_user: UserInfoDict, primary_group: dict[str, str], @@ -344,7 +340,6 @@ async def test_update_project_metadata_backward_compatibility_with_same_project_ # for deletion mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, - # client: TestClient, faker: Faker, logged_user: UserInfoDict, @@ -400,7 +395,6 @@ async def test_update_project_metadata_s4lacad_backward_compatibility_passing_ni # for deletion mocked_dynamic_services_interface: dict[str, MagicMock], storage_subsystem_mock: MockedStorageSubsystem, - # client: TestClient, logged_user: UserInfoDict, primary_group: dict[str, str], diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_pricing_unit_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_pricing_unit_handlers.py index 5812190c354..1ebd06d7392 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_pricing_unit_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_pricing_unit_handlers.py @@ -73,10 +73,10 @@ async def test_project_node_pricing_unit_user_project_access( ) resp = await client.get(f"{base_url}") data, _ = await assert_status(resp, expected) - assert data == None + assert data is None # Now we will log as a different user who doesnt have access to the project - async with LoggedUser(client) as new_logged_user: + async with LoggedUser(client): base_url = client.app.router["get_project_node_pricing_unit"].url_for( project_id=user_project["uuid"], node_id=node_id ) diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py index db4596f06ec..95c18aca7c8 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py @@ -236,7 +236,7 @@ async def test_io_workflow( }, "76f607b4-8761-4f96-824d-cab670bc45f5": { "key": "76f607b4-8761-4f96-824d-cab670bc45f5", - "value": 6, # + "value": 6, "label": "Random sleep interval", }, } diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py index f3b91131b1a..6fcd177f37b 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py @@ -163,7 +163,7 @@ async def _open_project( try: data, error = await assert_status(resp, e) return data, error - except AssertionError: # noqa: PERF203 + except AssertionError: # re-raise if last item if e == expected[-1]: raise @@ -1235,8 +1235,8 @@ async def test_open_shared_project_2_users_locked( # now the expected result is that the project is locked and opened by client 1 owner1 = Owner( user_id=logged_user["id"], - first_name=logged_user.get("first_name", None), - last_name=logged_user.get("last_name", None), + first_name=logged_user.get("first_name"), + last_name=logged_user.get("last_name"), ) expected_project_state_client_1.locked.value = True expected_project_state_client_1.locked.status = ProjectStatus.OPENED diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_wallet_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_wallet_handlers.py index 30aaa89abbc..132c6f21252 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_wallet_handlers.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_wallet_handlers.py @@ -63,10 +63,10 @@ async def test_project_wallets_user_project_access( ) resp = await client.get(f"{base_url}") data, _ = await assert_status(resp, expected) - assert data == None + assert data is None # Now we will log as a different user who doesnt have access to the project - async with LoggedUser(client) as new_logged_user: + async with LoggedUser(client): base_url = client.app.router["get_project_wallet"].url_for( project_id=user_project["uuid"] ) @@ -112,7 +112,7 @@ async def test_project_wallets_full_workflow( ) resp = await client.get(f"{base_url}") data, _ = await assert_status(resp, expected) - assert data == None + assert data is None # Now we will connect the wallet base_url = client.app.router["connect_wallet_to_project"].url_for( diff --git a/services/web/server/tests/unit/with_dbs/03/invitations/conftest.py b/services/web/server/tests/unit/with_dbs/03/invitations/conftest.py index 247289ca322..48d8f1ac41f 100644 --- a/services/web/server/tests/unit/with_dbs/03/invitations/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/invitations/conftest.py @@ -8,7 +8,7 @@ import json from contextlib import suppress from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -155,7 +155,7 @@ def _generate(url, **kwargs): **example, **body, "invitation_url": f"https://osparc-simcore.test/#/registration?invitation={fake_code}", - "created": datetime.now(tz=timezone.utc), + "created": datetime.now(tz=UTC), } ) ), diff --git a/services/web/server/tests/unit/with_dbs/03/invitations/test_products__invitations_handlers.py b/services/web/server/tests/unit/with_dbs/03/invitations/test_products__invitations_handlers.py index 64aec0a93d9..9f347239acd 100644 --- a/services/web/server/tests/unit/with_dbs/03/invitations/test_products__invitations_handlers.py +++ b/services/web/server/tests/unit/with_dbs/03/invitations/test_products__invitations_handlers.py @@ -4,7 +4,7 @@ # pylint: disable=too-many-arguments -from datetime import datetime, timezone +from datetime import UTC, datetime from http import HTTPStatus from typing import Final @@ -81,7 +81,7 @@ async def test_product_owner_generates_invitation( trial_account_days: PositiveInt | None, extra_credits_in_usd: PositiveInt | None, ): - before_dt = datetime.now(tz=timezone.utc) + before_dt = datetime.now(tz=UTC) request_model = GenerateInvitation( guest=guest_email, @@ -109,7 +109,7 @@ async def test_product_owner_generates_invitation( product_base_url = f"{client.make_url('/')}" assert f"{got.invitation_link}".startswith(product_base_url) assert before_dt < got.created - assert got.created < datetime.now(tz=timezone.utc) + assert got.created < datetime.now(tz=UTC) MANY_TIMES: Final = 2 diff --git a/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa_resend.py b/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa_resend.py index 2cf5b63eb24..1f3c76c2ea8 100644 --- a/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa_resend.py +++ b/services/web/server/tests/unit/with_dbs/03/login/test_login_2fa_resend.py @@ -78,7 +78,7 @@ async def test_resend_2fa_workflow( assert client.app # spy send functions - mock_send_sms_code1 = mocker.patch( + mocker.patch( "simcore_service_webserver.login._2fa_handlers.send_sms_code", autospec=True ) mock_send_sms_code2 = mocker.patch( diff --git a/services/web/server/tests/unit/with_dbs/03/login/test_login_reset_password.py b/services/web/server/tests/unit/with_dbs/03/login/test_login_reset_password.py index dd4a49a698a..a5c95ba7c3b 100644 --- a/services/web/server/tests/unit/with_dbs/03/login/test_login_reset_password.py +++ b/services/web/server/tests/unit/with_dbs/03/login/test_login_reset_password.py @@ -176,7 +176,7 @@ async def test_reset_and_confirm( assert ( response.url.path_qs == URL(login_options.LOGIN_REDIRECT) - .with_fragment("reset-password?code=%s" % code) + .with_fragment(f"reset-password?code={code}") .path_qs ) diff --git a/services/web/server/tests/unit/with_dbs/03/login/test_login_utils_emails.py b/services/web/server/tests/unit/with_dbs/03/login/test_login_utils_emails.py index 42d803d6710..e5e417bb8fc 100644 --- a/services/web/server/tests/unit/with_dbs/03/login/test_login_utils_emails.py +++ b/services/web/server/tests/unit/with_dbs/03/login/test_login_utils_emails.py @@ -56,18 +56,16 @@ async def print_mail(*, message, settings): print(message) print("---------------") - mock = mocker.patch( + return mocker.patch( "simcore_service_webserver.email._core._do_send_mail", spec=True, side_effect=print_mail, ) - return mock @pytest.fixture def destination_email(faker: Faker) -> str: - email = faker.email() - return email + return faker.email() @pytest.mark.parametrize("product_name", FRONTEND_APPS_AVAILABLE) diff --git a/services/web/server/tests/unit/with_dbs/03/meta_modeling/conftest.py b/services/web/server/tests/unit/with_dbs/03/meta_modeling/conftest.py index 3b965a09e46..d7e3dc7529e 100644 --- a/services/web/server/tests/unit/with_dbs/03/meta_modeling/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/meta_modeling/conftest.py @@ -1,80 +1,43 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument # pylint: disable=unused-variable - -import logging -from copy import deepcopy -from typing import Any - +# pylint: disable=too-many-arguments import pytest +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict from simcore_postgres_database.models.users import UserRole -from simcore_service_webserver.log import setup_logging @pytest.fixture def user_role() -> UserRole: - # TODO: user rights still not in place return UserRole.TESTER @pytest.fixture -def app_cfg(default_app_cfg, unused_tcp_port_factory, monkeypatch) -> dict[str, Any]: - """App's configuration used for every test in this module - - NOTE: Overrides services/web/server/tests/unit/with_dbs/conftest.py::app_cfg to influence app setup - """ - cfg = deepcopy(default_app_cfg) - - monkeypatch.setenv("WEBSERVER_DEV_FEATURES_ENABLED", "1") - - cfg["main"]["port"] = unused_tcp_port_factory() - cfg["main"]["studies_access_enabled"] = True - - exclude = { - "activity", - "clusters", - "computation", - "diagnostics", - "garbage_collector", - "groups", - "publications", - "smtp", - "socketio", - "storage", - "studies_dispatcher", - "tags", - "tracing", - } - include = { - "catalog", - "db", - "login", - "meta_modeling", # MODULE UNDER TEST - "products", - "projects", - "redis", - "resource_manager", - "rest", - "users", - "version_control", - } - - assert include.intersection(exclude) == set() - - for section in include: - cfg[section]["enabled"] = True - for section in exclude: - cfg[section]["enabled"] = False - - # NOTE: To see logs, use pytest -s --log-cli-level=DEBUG - setup_logging( - level=logging.DEBUG, - log_format_local_dev_enabled=True, - logger_filter_mapping={}, - tracing_settings=None, +def app_environment( + monkeypatch: pytest.MonkeyPatch, + app_environment: EnvVarsDict, +) -> EnvVarsDict: + return app_environment | setenvs_from_dict( + monkeypatch, + { + # exclude + "WEBSERVER_ACTIVITY": "null", + "WEBSERVER_CLUSTERS": "null", + "WEBSERVER_COMPUTATION": "null", + "WEBSERVER_DIAGNOSTICS": "null", + "WEBSERVER_GROUPS": "0", + "WEBSERVER_PUBLICATIONS": "0", + "WEBSERVER_GARBAGE_COLLECTOR": "null", + "WEBSERVER_EMAIL": "null", + "WEBSERVER_SOCKETIO": "0", + "WEBSERVER_STORAGE": "null", + "WEBSERVER_STUDIES_DISPATCHER": "null", + "WEBSERVER_TAGS": "0", + "WEBSERVER_TRACING": "null", + # Module under test + "WEBSERVER_DEV_FEATURES_ENABLED": "1", + "WEBSERVER_VERSION_CONTROL": "1", + "WEBSERVER_META_MODELING": "1", + }, ) - - # Enforces smallest GC in the background task - cfg["resource_manager"]["garbage_collection_interval_seconds"] = 1 - - return cfg diff --git a/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_results.py b/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_results.py index 8c86119bfc1..fdec06806ed 100644 --- a/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_results.py +++ b/services/web/server/tests/unit/with_dbs/03/meta_modeling/test_meta_modeling_results.py @@ -127,7 +127,7 @@ def test_extract_project_results(fake_workbench: dict[str, Any]): @pytest.mark.parametrize( "model_cls", - (ExtractedResults,), + [ExtractedResults], ) def test_models_examples( model_cls: type[BaseModel], model_cls_examples: dict[str, Any] diff --git a/services/web/server/tests/unit/with_dbs/03/resource_usage/test_list_osparc_credits_aggregated_usages.py b/services/web/server/tests/unit/with_dbs/03/resource_usage/test_list_osparc_credits_aggregated_usages.py index 5f7f7c41b18..bb6f2123276 100644 --- a/services/web/server/tests/unit/with_dbs/03/resource_usage/test_list_osparc_credits_aggregated_usages.py +++ b/services/web/server/tests/unit/with_dbs/03/resource_usage/test_list_osparc_credits_aggregated_usages.py @@ -27,11 +27,9 @@ _SERVICE_RUN_GET = OsparcCreditsAggregatedUsagesPage( items=[ OsparcCreditsAggregatedByServiceGet( - **{ - "osparc_credits": Decimal(-50), - "service_key": "simcore/services/comp/itis/sleeper", - "running_time_in_hours": Decimal(0.5), - } + osparc_credits=Decimal(-50), + service_key="simcore/services/comp/itis/sleeper", + running_time_in_hours=Decimal(0.5), ) ], total=1, diff --git a/services/web/server/tests/unit/with_dbs/03/tags/conftest.py b/services/web/server/tests/unit/with_dbs/03/tags/conftest.py index 45682405bfe..ea8907aeab3 100644 --- a/services/web/server/tests/unit/with_dbs/03/tags/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/tags/conftest.py @@ -2,12 +2,15 @@ # pylint:disable=unused-argument # pylint:disable=redefined-outer-name +import asyncio from collections.abc import AsyncIterator, Callable from copy import deepcopy from pathlib import Path import pytest from aioresponses import aioresponses +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict from pytest_simcore.helpers.webserver_login import UserInfoDict from pytest_simcore.helpers.webserver_projects import NewProject, delete_all_projects from servicelib.aiohttp.application import create_safe_application @@ -34,33 +37,44 @@ DEFAULT_GARBAGE_COLLECTOR_DELETION_TIMEOUT_SECONDS: int = 3 +@pytest.fixture +def app_environment( + monkeypatch: pytest.MonkeyPatch, + app_environment: EnvVarsDict, +) -> EnvVarsDict: + # NOTE: undos some app_environment settings + monkeypatch.delenv("WEBSERVER_GARBAGE_COLLECTOR", raising=False) + app_environment.pop("WEBSERVER_GARBAGE_COLLECTOR", None) + + return app_environment | setenvs_from_dict( + monkeypatch, + { + # reduce deletion delay + "RESOURCE_MANAGER_RESOURCE_TTL_S": f"{DEFAULT_GARBAGE_COLLECTOR_INTERVAL_SECONDS}", + # increase speed of garbage collection + "GARBAGE_COLLECTOR_INTERVAL_S": f"{DEFAULT_GARBAGE_COLLECTOR_DELETION_TIMEOUT_SECONDS}", + }, + ) + + @pytest.fixture def client( - event_loop, - aiohttp_client, - app_cfg, + event_loop: asyncio.AbstractEventLoop, + aiohttp_client: Callable, + app_environment: EnvVarsDict, postgres_db, mocked_dynamic_services_interface, mock_orphaned_services, redis_client, # this ensure redis is properly cleaned - monkeypatch_setenv_from_app_config: Callable, ): - # config app - cfg = deepcopy(app_cfg) - port = cfg["main"]["port"] - cfg["projects"]["enabled"] = True - cfg["resource_manager"][ - "garbage_collection_interval_seconds" - ] = DEFAULT_GARBAGE_COLLECTOR_INTERVAL_SECONDS # increase speed of garbage collection - cfg["resource_manager"][ - "resource_deletion_timeout_seconds" - ] = DEFAULT_GARBAGE_COLLECTOR_DELETION_TIMEOUT_SECONDS # reduce deletion delay - - monkeypatch_setenv_from_app_config(cfg) + app = create_safe_application() - app = create_safe_application(cfg) + assert "WEBSERVER_GARBAGE_COLLECTOR" not in app_environment - assert setup_settings(app) + settings = setup_settings(app) + assert settings.WEBSERVER_GARBAGE_COLLECTOR is not None + assert settings.WEBSERVER_PROJECTS is not None + assert settings.WEBSERVER_TAGS is not None # setup app setup_db(app) @@ -71,14 +85,14 @@ def client( setup_resource_manager(app) setup_socketio(app) setup_director_v2(app) - setup_tags(app) - assert setup_projects(app) + assert setup_tags(app) + setup_projects(app) setup_products(app) setup_wallets(app) # server and client return event_loop.run_until_complete( - aiohttp_client(app, server_kwargs={"port": port, "host": "localhost"}) + aiohttp_client(app, server_kwargs={"host": "localhost"}) ) # teardown here ... diff --git a/services/web/server/tests/unit/with_dbs/03/test_project_db.py b/services/web/server/tests/unit/with_dbs/03/test_project_db.py index 1ab6ca802f3..098a4a78388 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_project_db.py +++ b/services/web/server/tests/unit/with_dbs/03/test_project_db.py @@ -504,7 +504,7 @@ async def test_patch_user_project_workbench_concurrently( # patch all the nodes concurrently randomly_created_outputs = [ { - "outputs": {f"out_{k}": f"{k}"} # noqa: RUF011 + "outputs": {f"out_{k}": f"{k}"} # noqa: B035 for k in range(randint(1, 10)) # noqa: S311 } for n in range(_NUMBER_OF_NODES) diff --git a/services/web/server/tests/unit/with_dbs/03/test_session.py b/services/web/server/tests/unit/with_dbs/03/test_session.py index c3684acb326..060959ee44d 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_session.py +++ b/services/web/server/tests/unit/with_dbs/03/test_session.py @@ -12,10 +12,10 @@ from aiohttp import web from aiohttp.test_utils import TestClient from cryptography.fernet import Fernet +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict from pytest_simcore.helpers.typing_env import EnvVarsDict from pytest_simcore.helpers.webserver_login import NewUser from simcore_service_webserver.application import create_application -from simcore_service_webserver.application_settings_utils import AppConfigDict from simcore_service_webserver.session._cookie_storage import ( SharedCookieEncryptedCookieStorage, ) @@ -28,13 +28,26 @@ def session_url_path() -> str: return "/v0/test-session" +@pytest.fixture +def app_environment( + monkeypatch: pytest.MonkeyPatch, + app_environment: EnvVarsDict, +) -> EnvVarsDict: + return app_environment | setenvs_from_dict( + monkeypatch, + { + # do not include static entrypoint + "WEBSERVER_STATICWEB": "null", + }, + ) + + @pytest.fixture def client( session_url_path: str, event_loop: asyncio.AbstractEventLoop, aiohttp_client: Callable, disable_static_webserver: Callable, - app_cfg: AppConfigDict, app_environment: EnvVarsDict, postgres_db, mock_orphaned_services, # disables gc @@ -52,15 +65,7 @@ async def _get_user_session(request: web.Request): app.add_routes(extra_routes) - return event_loop.run_until_complete( - aiohttp_client( - app, - server_kwargs={ - "port": app_cfg["main"]["port"], - "host": app_cfg["main"]["host"], - }, - ) - ) + return event_loop.run_until_complete(aiohttp_client(app)) async def test_security_identity_is_email_and_product( diff --git a/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py b/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py index d19d2dca6ae..dd79e1a9b6c 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py +++ b/services/web/server/tests/unit/with_dbs/03/test_session_access_policies.py @@ -5,7 +5,8 @@ from asyncio import AbstractEventLoop -from typing import Callable, Protocol +from collections.abc import Callable +from typing import Protocol import pytest from aiohttp import ClientResponse, web diff --git a/services/web/server/tests/unit/with_dbs/03/test_socketio.py b/services/web/server/tests/unit/with_dbs/03/test_socketio.py index 699ff0ccef9..5b63d8f3c84 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_socketio.py +++ b/services/web/server/tests/unit/with_dbs/03/test_socketio.py @@ -54,7 +54,7 @@ def app_environment(app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatc @pytest.mark.skip( reason="Pending https://github.com/ITISFoundation/osparc-simcore/issues/5332" ) -@pytest.mark.parametrize("user_role", (UserRole.USER,)) +@pytest.mark.parametrize("user_role", [UserRole.USER]) async def test_socketio_session_client_to_server( logged_user: UserInfoDict, client: TestClient, diff --git a/services/web/server/tests/unit/with_dbs/03/test_users__notifications.py b/services/web/server/tests/unit/with_dbs/03/test_users__notifications.py index ccf246540bd..a1d99e48268 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_users__notifications.py +++ b/services/web/server/tests/unit/with_dbs/03/test_users__notifications.py @@ -10,7 +10,7 @@ from collections.abc import AsyncIterable, AsyncIterator from contextlib import asynccontextmanager from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from http import HTTPStatus from typing import Any @@ -76,7 +76,7 @@ def _create_notification( "actionable_path": "a/path", "title": "test_title", "text": "text_text", - "date": datetime.now(timezone.utc).isoformat(), + "date": datetime.now(UTC).isoformat(), "product": product_name, } ) @@ -154,7 +154,7 @@ async def test_list_user_notifications( result = TypeAdapter(list[UserNotification]).validate_python( json_response["data"] - ) # noqa: F821 + ) assert len(result) <= MAX_NOTIFICATIONS_FOR_USER_TO_SHOW assert result == list( reversed(created_notifications[:MAX_NOTIFICATIONS_FOR_USER_TO_SHOW]) @@ -229,7 +229,7 @@ async def test_create_user_notification( # these are always generated and overwritten, even if provided by the user, since # we do not want to overwrite existing ones assert user_notifications[0].read is False - assert user_notifications[0].id != notification_dict.get("id", None) + assert user_notifications[0].id != notification_dict.get("id") else: assert error is not None diff --git a/services/web/server/tests/unit/with_dbs/03/test_users__tokens.py b/services/web/server/tests/unit/with_dbs/03/test_users__tokens.py index fd040e1d88a..76481526d96 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_users__tokens.py +++ b/services/web/server/tests/unit/with_dbs/03/test_users__tokens.py @@ -183,7 +183,7 @@ async def test_delete_token( sid = fake_tokens[0]["service"] url = client.app.router["delete_token"].url_for(service=sid) - assert "/v0/me/tokens/%s" % sid == str(url) + assert f"/v0/me/tokens/{sid}" == str(url) resp = await client.delete(url.path) diff --git a/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py b/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py index e9a6244886c..53bdeb18c5b 100644 --- a/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py +++ b/services/web/server/tests/unit/with_dbs/03/version_control/conftest.py @@ -2,11 +2,8 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -import logging from collections.abc import AsyncIterator, Awaitable, Callable -from copy import deepcopy from pathlib import Path -from typing import Any from unittest import mock from uuid import UUID @@ -21,6 +18,8 @@ from models_library.utils.fastapi_encoders import jsonable_encoder from pytest_mock import MockerFixture from pytest_simcore.helpers.faker_factories import random_project +from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict from pytest_simcore.helpers.webserver_login import UserInfoDict from pytest_simcore.helpers.webserver_projects import NewProject from servicelib.aiohttp import status @@ -31,7 +30,6 @@ from simcore_service_webserver._meta import API_VTAG as VX from simcore_service_webserver.db.models import UserRole from simcore_service_webserver.db.plugin import APP_AIOPG_ENGINE_KEY -from simcore_service_webserver.log import setup_logging from simcore_service_webserver.projects.db import ProjectDBAPI from simcore_service_webserver.projects.models import ProjectDict from tenacity.asyncio import AsyncRetrying @@ -69,70 +67,33 @@ def catalog_subsystem_mock_override( @pytest.fixture -def app_cfg( - default_app_cfg, - unused_tcp_port_factory, +def app_environment( catalog_subsystem_mock_override: None, - monkeypatch, -) -> dict[str, Any]: - """App's configuration used for every test in this module - - NOTE: Overrides services/web/server/tests/unit/with_dbs/conftest.py::app_cfg to influence app setup - """ - cfg = deepcopy(default_app_cfg) - - monkeypatch.setenv("WEBSERVER_DEV_FEATURES_ENABLED", "1") - - cfg["main"]["port"] = unused_tcp_port_factory() - cfg["main"]["studies_access_enabled"] = True - - exclude = { - "activity", - "clusters", - "computation", - "diagnostics", - "groups", - "publications", - "garbage_collector", - "smtp", - "socketio", - "storage", - "studies_dispatcher", - "tags", - "tracing", - } - include = { - "catalog", - "db", - "login", - "products", - "projects", - "resource_manager", - "rest", - "users", - "version_control", # MODULE UNDER TEST - } - - assert include.intersection(exclude) == set() - - for section in include: - cfg[section]["enabled"] = True - for section in exclude: - cfg[section]["enabled"] = False - - # NOTE: To see logs, use pytest -s --log-cli-level=DEBUG - setup_logging( - level=logging.DEBUG, - log_format_local_dev_enabled=True, - logger_filter_mapping={}, - tracing_settings=None, + monkeypatch: pytest.MonkeyPatch, + app_environment: EnvVarsDict, +) -> EnvVarsDict: + + return app_environment | setenvs_from_dict( + monkeypatch, + { + # exclude + "WEBSERVER_ACTIVITY": "null", + "WEBSERVER_CLUSTERS": "null", + "WEBSERVER_COMPUTATION": "null", + "WEBSERVER_DIAGNOSTICS": "null", + "WEBSERVER_GARBAGE_COLLECTOR": "null", + "WEBSERVER_GROUPS": "0", + "WEBSERVER_PUBLICATIONS": "0", + "WEBSERVER_SOCKETIO": "0", + "WEBSERVER_STUDIES_DISPATCHER": "null", + "WEBSERVER_TAGS": "0", + "WEBSERVER_TRACING": "null", + # Module under test + "WEBSERVER_DEV_FEATURES_ENABLED": "1", + "WEBSERVER_VERSION_CONTROL": "1", + }, ) - # Enforces smallest GC in the background task - cfg["resource_manager"]["garbage_collection_interval_seconds"] = 1 - - return cfg - @pytest.fixture async def user_id(logged_user: UserInfoDict) -> UserID: diff --git a/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_core.py b/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_core.py index fa05653b87c..c5ca46bf52c 100644 --- a/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_core.py +++ b/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_core.py @@ -2,13 +2,12 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable -from typing import Awaitable, Callable +from collections.abc import Awaitable, Callable from uuid import UUID import pytest from aiohttp import web from aiohttp.test_utils import TestClient, make_mocked_request -from faker import Faker from simcore_service_webserver._constants import RQT_USERID_KEY from simcore_service_webserver.projects import projects_api from simcore_service_webserver.projects.models import ProjectDict @@ -28,11 +27,10 @@ def aiohttp_mocked_request(client: TestClient, user_id: int) -> web.Request: return req -@pytest.mark.acceptance_test +@pytest.mark.acceptance_test() async def test_workflow( client: TestClient, project_uuid: UUID, - faker: Faker, user_id: int, user_project: ProjectDict, aiohttp_mocked_request: web.Request, diff --git a/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_handlers.py b/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_handlers.py index 325eb63e353..ac229a3b410 100644 --- a/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_handlers.py +++ b/services/web/server/tests/unit/with_dbs/03/version_control/test_version_control_handlers.py @@ -1,6 +1,8 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument # pylint: disable=unused-variable +# pylint: disable=too-many-arguments + from collections.abc import Awaitable, Callable from http import HTTPStatus @@ -23,7 +25,7 @@ ) -async def assert_resp_page( +async def _assert_resp_page( resp: aiohttp.ClientResponse, expected_page_cls: type[Page], expected_total: int, @@ -38,7 +40,7 @@ async def assert_resp_page( return page -async def assert_status_and_body( +async def _assert_status_and_body( resp, expected_cls: HTTPStatus, expected_model: type[BaseModel] ) -> BaseModel: data, _ = await assert_status(resp, expected_cls) @@ -84,7 +86,7 @@ async def test_workflow( # # this project now has a repo resp = await client.get(f"/{VX}/repos/projects") - page = await assert_resp_page( + page = await _assert_resp_page( resp, expected_page_cls=Page[ProjectDict], expected_total=1, expected_count=1 ) @@ -97,8 +99,8 @@ async def test_workflow( assert CheckpointApiModel.model_validate(data) == checkpoint1 # TODO: GET checkpoint with tag + resp = await client.get(f"/{VX}/repos/projects/{project_uuid}/checkpoints/v1") with pytest.raises(aiohttp.ClientResponseError) as excinfo: - resp = await client.get(f"/{VX}/repos/projects/{project_uuid}/checkpoints/v1") resp.raise_for_status() assert CheckpointApiModel.model_validate(data) == checkpoint1 @@ -114,7 +116,7 @@ async def test_workflow( # LIST checkpoints resp = await client.get(f"/{VX}/repos/projects/{project_uuid}/checkpoints") - page = await assert_resp_page( + page = await _assert_resp_page( resp, expected_page_cls=Page[CheckpointApiModel], expected_total=1, @@ -226,7 +228,7 @@ async def test_delete_project_and_repo( # LIST resp = await client.get(f"/{VX}/repos/projects/{project_uuid}/checkpoints") - await assert_resp_page( + await _assert_resp_page( resp, expected_page_cls=Page[CheckpointApiModel], expected_total=1, @@ -247,7 +249,7 @@ async def test_delete_project_and_repo( # LIST empty resp = await client.get(f"/{VX}/repos/projects/{project_uuid}/checkpoints") - await assert_resp_page( + await _assert_resp_page( resp, expected_page_cls=Page[CheckpointApiModel], expected_total=0, diff --git a/services/web/server/tests/unit/with_dbs/04/folders/test_folders__full_search.py b/services/web/server/tests/unit/with_dbs/04/folders/test_folders__full_search.py index e9bde5d9ec5..6fdfe372c6b 100644 --- a/services/web/server/tests/unit/with_dbs/04/folders/test_folders__full_search.py +++ b/services/web/server/tests/unit/with_dbs/04/folders/test_folders__full_search.py @@ -112,7 +112,7 @@ async def test_folders_full_search( assert len(data) == 1 # Create new user - async with LoggedUser(client) as new_logged_user: + async with LoggedUser(client): # list full folder search url = client.app.router["list_folders_full_search"].url_for() resp = await client.get(f"{url}") diff --git a/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py b/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py index 18cba797177..d4aee7eb58f 100644 --- a/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py +++ b/services/web/server/tests/unit/with_dbs/04/garbage_collector/test_resource_manager.py @@ -8,7 +8,6 @@ import asyncio from asyncio import Future from collections.abc import AsyncIterator, Awaitable, Callable -from copy import deepcopy from pathlib import Path from typing import Any from unittest import mock @@ -28,6 +27,7 @@ from pytest_mock import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict +from pytest_simcore.helpers.typing_env import EnvVarsDict from pytest_simcore.helpers.webserver_login import UserInfoDict from pytest_simcore.helpers.webserver_parametrizations import MockedStorageSubsystem from pytest_simcore.helpers.webserver_projects import NewProject @@ -37,7 +37,6 @@ from servicelib.aiohttp.application_setup import is_setup_completed from servicelib.common_headers import UNDEFINED_DEFAULT_SIMCORE_USER_AGENT_VALUE from simcore_postgres_database.models.users import UserRole -from simcore_service_webserver._meta import API_VTAG from simcore_service_webserver.application_settings import setup_settings from simcore_service_webserver.db.plugin import setup_db from simcore_service_webserver.director_v2.plugin import setup_director_v2 @@ -104,45 +103,43 @@ async def _open_project(client, project_uuid: str, client_session_id: str) -> No @pytest.fixture def app_environment( - app_environment: dict[str, str], monkeypatch: pytest.MonkeyPatch -) -> dict[str, str]: - overrides = setenvs_from_dict( + monkeypatch: pytest.MonkeyPatch, + app_environment: EnvVarsDict, +) -> EnvVarsDict: + + # NOTE: undos some app_environment settings + monkeypatch.delenv("WEBSERVER_GARBAGE_COLLECTOR", raising=False) + app_environment.pop("WEBSERVER_GARBAGE_COLLECTOR", None) + + return app_environment | setenvs_from_dict( monkeypatch, { "WEBSERVER_COMPUTATION": "1", "WEBSERVER_NOTIFICATIONS": "1", + # sets TTL of a resource after logout + "RESOURCE_MANAGER_RESOURCE_TTL_S": f"{SERVICE_DELETION_DELAY}", + "GARBAGE_COLLECTOR_INTERVAL_S": "30", }, ) - return app_environment | overrides @pytest.fixture def client( event_loop: asyncio.AbstractEventLoop, aiohttp_client: Callable, - app_cfg: dict[str, Any], + app_environment: EnvVarsDict, postgres_db: sa.engine.Engine, mock_orphaned_services, redis_client: Redis, - monkeypatch_setenv_from_app_config: Callable, mock_dynamic_scheduler_rabbitmq: None, ) -> TestClient: - cfg = deepcopy(app_cfg) - assert cfg["rest"]["version"] == API_VTAG - assert cfg["rest"]["enabled"] - cfg["projects"]["enabled"] = True - - # sets TTL of a resource after logout - cfg["resource_manager"][ - "resource_deletion_timeout_seconds" - ] = SERVICE_DELETION_DELAY - - monkeypatch_setenv_from_app_config(cfg) - app = create_safe_application(cfg) + app = create_safe_application() - # activates only security+restAPI sub-modules + assert "WEBSERVER_GARBAGE_COLLECTOR" not in app_environment - assert setup_settings(app) + settings = setup_settings(app) + assert settings.WEBSERVER_GARBAGE_COLLECTOR is not None + assert settings.WEBSERVER_PROJECTS is not None setup_db(app) setup_session(app) @@ -151,7 +148,7 @@ def client( setup_login(app) setup_users(app) setup_socketio(app) - setup_projects(app) + assert setup_projects(app) setup_director_v2(app) assert setup_resource_manager(app) setup_rabbitmq(app) @@ -167,7 +164,6 @@ def client( return event_loop.run_until_complete( aiohttp_client( app, - server_kwargs={"port": cfg["main"]["port"], "host": cfg["main"]["host"]}, ) ) @@ -351,7 +347,7 @@ async def test_websocket_multiple_connections( clients.append(sio) resource_keys.append(resource_key) - for sio, resource_key in zip(clients, resource_keys): + for sio, resource_key in zip(clients, resource_keys, strict=True): sid = sio.get_sid() await sio.disconnect() await sio.wait() diff --git a/services/web/server/tests/unit/with_dbs/04/notifications/test_notifications__db_comp_tasks_listening_task.py b/services/web/server/tests/unit/with_dbs/04/notifications/test_notifications__db_comp_tasks_listening_task.py index 49989b3fa31..c17c5aa1aa6 100644 --- a/services/web/server/tests/unit/with_dbs/04/notifications/test_notifications__db_comp_tasks_listening_task.py +++ b/services/web/server/tests/unit/with_dbs/04/notifications/test_notifications__db_comp_tasks_listening_task.py @@ -6,7 +6,8 @@ import json import logging -from typing import Any, AsyncIterator, Awaitable, Callable, Iterator +from collections.abc import AsyncIterator, Awaitable, Callable, Iterator +from typing import Any from unittest import mock import aiopg.sa @@ -53,7 +54,7 @@ async def mock_project_subsystem( return_value="", ) - yield mocked_project_calls + return mocked_project_calls @pytest.fixture diff --git a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_handlers.py b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_handlers.py index 86ed849075f..29c3251bbad 100644 --- a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_handlers.py +++ b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_handlers.py @@ -317,7 +317,7 @@ async def assert_redirected_to_study( assert resp.url.path == "/" assert ( "OSPARC-SIMCORE" in content - ), "Expected front-end rendering workbench's study, got %s" % str(content) + ), f"Expected front-end rendering workbench's study, got {content!s}" # Expects auth cookie for current user assert DEFAULT_SESSION_COOKIE_NAME in [c.key for c in session.cookie_jar] diff --git a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_projects.py b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_projects.py index cd9bc502089..1488ea3bbc0 100644 --- a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_projects.py +++ b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_projects.py @@ -73,8 +73,7 @@ def viewer_id(faker: Faker) -> NodeID: @pytest.fixture def viewer_info(view: dict[str, Any]) -> ViewerInfo: view.setdefault("label", view.pop("display_name", "Undefined")) - viewer_ = ViewerInfo(**view) - return viewer_ + return ViewerInfo(**view) @pytest.mark.parametrize("only_service", [True, False]) diff --git a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_studies_access.py b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_studies_access.py index 366f10dba16..582376778fa 100644 --- a/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_studies_access.py +++ b/services/web/server/tests/unit/with_dbs/04/studies_dispatcher/test_studies_dispatcher_studies_access.py @@ -207,7 +207,7 @@ async def _assert_redirected_to_study( assert response.url.path == "/" assert ( "OSPARC-SIMCORE" in content - ), "Expected front-end rendering workbench's study, got %s" % str(content) + ), f"Expected front-end rendering workbench's study, got {content!s}" # Expects fragment to indicate client where to find newly created project m = re.match(r"/study/([\d\w-]+)", response.real_url.fragment) @@ -217,8 +217,7 @@ async def _assert_redirected_to_study( assert _is_user_authenticated(session) # returns newly created project - redirected_project_id = m.group(1) - return redirected_project_id + return m.group(1) # ----------------------------------------------------------- @@ -333,7 +332,7 @@ async def test_access_study_by_logged_user( user_project = projects[0] # heck redirects to /#/study/{uuid} - assert resp.real_url.fragment.endswith("/study/%s" % user_project["uuid"]) + assert resp.real_url.fragment.endswith("/study/{}".format(user_project["uuid"])) _assert_same_projects(user_project, published_project) assert user_project["prjOwner"] == logged_user["email"] diff --git a/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces.py b/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces.py index 7e45b93400a..56753822a6d 100644 --- a/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces.py +++ b/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces.py @@ -169,7 +169,7 @@ async def test_list_workspaces_with_text_search( }, ) data, _ = await assert_status(resp, status.HTTP_201_CREATED) - added_workspace = WorkspaceGet.model_validate(data) + WorkspaceGet.model_validate(data) # CREATE a new workspace url = client.app.router["create_workspace"].url_for() @@ -182,7 +182,7 @@ async def test_list_workspaces_with_text_search( }, ) data, _ = await assert_status(resp, status.HTTP_201_CREATED) - added_workspace = WorkspaceGet.model_validate(data) + WorkspaceGet.model_validate(data) # LIST user workspaces url = client.app.router["list_workspaces"].url_for() diff --git a/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces__folders_and_projects_crud.py b/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces__folders_and_projects_crud.py index 3d5c2d7991a..d8053ab8264 100644 --- a/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces__folders_and_projects_crud.py +++ b/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces__folders_and_projects_crud.py @@ -418,7 +418,7 @@ async def test_listing_folders_and_projects_in_workspace__multiple_workspaces_cr # Create project in workspace project_data = deepcopy(fake_project) project_data["workspace_id"] = f"{added_workspace_1['workspaceId']}" - project = await create_project( + await create_project( client.app, project_data, user_id=logged_user["id"], @@ -451,7 +451,7 @@ async def test_listing_folders_and_projects_in_workspace__multiple_workspaces_cr # Create project in workspace project_data = deepcopy(fake_project) project_data["workspace_id"] = f"{added_workspace_2['workspaceId']}" - project = await create_project( + await create_project( client.app, project_data, user_id=logged_user["id"], diff --git a/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces_groups.py b/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces_groups.py index 3bb41d3bd37..af19129d88c 100644 --- a/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces_groups.py +++ b/services/web/server/tests/unit/with_dbs/04/workspaces/test_workspaces_groups.py @@ -40,9 +40,9 @@ async def test_workspaces_groups_full_workflow( data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 1 assert data[0]["gid"] == logged_user["primary_gid"] - assert data[0]["read"] == True - assert data[0]["write"] == True - assert data[0]["delete"] == True + assert data[0]["read"] is True + assert data[0]["write"] is True + assert data[0]["delete"] is True async with NewUser( app=client.app, @@ -65,9 +65,9 @@ async def test_workspaces_groups_full_workflow( data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 2 assert data[1]["gid"] == new_user["primary_gid"] - assert data[1]["read"] == True - assert data[1]["write"] == False - assert data[1]["delete"] == False + assert data[1]["read"] is True + assert data[1]["write"] is False + assert data[1]["delete"] is False # Update the workspace permissions of the added user url = client.app.router["replace_workspace_group"].url_for( @@ -79,9 +79,9 @@ async def test_workspaces_groups_full_workflow( ) data, _ = await assert_status(resp, status.HTTP_200_OK) assert data["gid"] == new_user["primary_gid"] - assert data["read"] == True - assert data["write"] == True - assert data["delete"] == False + assert data["read"] is True + assert data["write"] is True + assert data["delete"] is False # List the workspace groups url = client.app.router["list_workspace_groups"].url_for( @@ -91,9 +91,9 @@ async def test_workspaces_groups_full_workflow( data, _ = await assert_status(resp, status.HTTP_200_OK) assert len(data) == 2 assert data[1]["gid"] == new_user["primary_gid"] - assert data[1]["read"] == True - assert data[1]["write"] == True - assert data[1]["delete"] == False + assert data[1]["read"] is True + assert data[1]["write"] is True + assert data[1]["delete"] is False # Delete the workspace group url = client.app.router["delete_workspace_group"].url_for( diff --git a/services/web/server/tests/unit/with_dbs/conftest.py b/services/web/server/tests/unit/with_dbs/conftest.py index 6661af40d5e..12d34d5be78 100644 --- a/services/web/server/tests/unit/with_dbs/conftest.py +++ b/services/web/server/tests/unit/with_dbs/conftest.py @@ -97,6 +97,7 @@ def disable_swagger_doc_generation( def docker_compose_env(default_app_cfg: AppConfigDict) -> Iterator[pytest.MonkeyPatch]: postgres_cfg = default_app_cfg["db"]["postgres"] redis_cfg = default_app_cfg["resource_manager"]["redis"] + # docker-compose reads these environs with pytest.MonkeyPatch().context() as patcher: patcher.setenv("TEST_POSTGRES_DB", postgres_cfg["database"]) @@ -119,24 +120,28 @@ def docker_compose_file(docker_compose_env: pytest.MonkeyPatch) -> str: @pytest.fixture -def app_cfg(default_app_cfg: AppConfigDict, unused_tcp_port_factory) -> AppConfigDict: - """ - NOTE: SHOULD be overriden in any test module to configure the app accordingly - """ - cfg = deepcopy(default_app_cfg) - # fills ports on the fly - cfg["main"]["port"] = unused_tcp_port_factory() - cfg["storage"]["port"] = unused_tcp_port_factory() +def webserver_test_server_port(unused_tcp_port_factory: Callable): + # used to create a TestServer that emulates web-server service + return unused_tcp_port_factory() - # this fixture can be safely modified during test since it is renovated on every call - return cfg + +@pytest.fixture +def storage_test_server_port( + unused_tcp_port_factory: Callable, webserver_test_server_port: int +): + # used to create a TestServer that emulates storage service + port = unused_tcp_port_factory() + assert port != webserver_test_server_port + return port @pytest.fixture def app_environment( monkeypatch: pytest.MonkeyPatch, - app_cfg: AppConfigDict, - monkeypatch_setenv_from_app_config: Callable[[AppConfigDict], dict[str, str]], + default_app_cfg: AppConfigDict, + mock_env_devel_environment: EnvVarsDict, + storage_test_server_port: int, + monkeypatch_setenv_from_app_config: Callable[[AppConfigDict], EnvVarsDict], ) -> EnvVarsDict: # WARNING: this fixture is commonly overriden. Check before renaming. """overridable fixture that defines the ENV for the webserver application @@ -144,20 +149,27 @@ def app_environment( override like so: @pytest.fixture - def app_environment(app_environment: dict[str, str], monkeypatch: pytest.MonkeyPatch) -> dict[str, str]: + def app_environment(app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch) -> EnvVarsDict: monkeypatch.setenv("MODIFIED_ENV", "VALUE") return app_environment | {"MODIFIED_ENV":"VALUE"} """ - print("+ web_server:") - cfg = deepcopy(app_cfg) - envs = monkeypatch_setenv_from_app_config(cfg) - - # - # NOTE: this emulates hostname: "wb-{{.Node.Hostname}}-{{.Task.Slot}}" in docker-compose that - # affects PostgresSettings.POSTGRES_CLIENT_NAME - # - extra = setenvs_from_dict(monkeypatch, {"HOSTNAME": "wb-test_host.0"}) - return envs | extra + # NOTE: remains from from old cfg + cfg = deepcopy(default_app_cfg) + cfg["storage"]["port"] = storage_test_server_port + envs_app_cfg = monkeypatch_setenv_from_app_config(cfg) + + return ( + mock_env_devel_environment + | envs_app_cfg + | setenvs_from_dict( + monkeypatch, + { + # this emulates hostname: "wb-{{.Node.Hostname}}-{{.Task.Slot}}" in docker-compose that + # affects PostgresSettings.POSTGRES_CLIENT_NAME + "HOSTNAME": "wb-test_host.0" + }, + ) + ) @pytest.fixture @@ -191,21 +203,23 @@ async def _print_mail_to_stdout( @pytest.fixture def web_server( event_loop: asyncio.AbstractEventLoop, - app_cfg: AppConfigDict, app_environment: EnvVarsDict, postgres_db: sa.engine.Engine, + webserver_test_server_port: int, # tools aiohttp_server: Callable, mocked_send_email: None, disable_static_webserver: Callable, ) -> TestServer: + assert app_environment + # original APP app = create_application() disable_static_webserver(app) server = event_loop.run_until_complete( - aiohttp_server(app, port=app_cfg["main"]["port"]) + aiohttp_server(app, port=webserver_test_server_port) ) assert isinstance(postgres_db, sa.engine.Engine)