diff --git a/api/specs/web-server/_conversations.py b/api/specs/web-server/_conversations.py index 0fd8983331df..96c04b9e3227 100644 --- a/api/specs/web-server/_conversations.py +++ b/api/specs/web-server/_conversations.py @@ -32,7 +32,6 @@ ) from simcore_service_webserver.conversations._controller._conversations_rest import ( _ConversationsCreateBodyParams, - _GetConversationsQueryParams, _ListConversationsQueryParams, ) @@ -56,7 +55,6 @@ ) async def create_conversation( _body: _ConversationsCreateBodyParams, - _query: Annotated[_GetConversationsQueryParams, Depends()], ): ... @@ -76,7 +74,6 @@ async def list_conversations( async def update_conversation( _params: Annotated[ConversationPathParams, Depends()], _body: ConversationPatch, - _query: Annotated[as_query(_GetConversationsQueryParams), Depends()], ): ... @@ -86,7 +83,6 @@ async def update_conversation( ) async def delete_conversation( _params: Annotated[ConversationPathParams, Depends()], - _query: Annotated[as_query(_GetConversationsQueryParams), Depends()], ): ... @@ -96,7 +92,6 @@ async def delete_conversation( ) async def get_conversation( _params: Annotated[ConversationPathParams, Depends()], - _query: Annotated[as_query(_GetConversationsQueryParams), Depends()], ): ... diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 9f28be3bd152..32227dec8109 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1487,11 +1487,11 @@ qx.Class.define("osparc.data.Resources", { }, getConversation: { method: "GET", - url: statics.API + "/conversations/{conversationId}?type=SUPPORT" + url: statics.API + "/conversations/{conversationId}" }, renameConversation: { method: "PATCH", - url: statics.API + "/conversations/{conversationId}?type=SUPPORT" + url: statics.API + "/conversations/{conversationId}" }, deleteConversation: { method: "DELETE", diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index c8660f8d319c..bdecdf8d20bd 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -516,12 +516,6 @@ paths: - conversations summary: Create Conversation operationId: create_conversation - parameters: - - name: type - in: query - required: true - schema: - $ref: '#/components/schemas/ConversationType' requestBody: required: true content: @@ -541,11 +535,6 @@ paths: summary: List Conversations operationId: list_conversations parameters: - - name: type - in: query - required: true - schema: - $ref: '#/components/schemas/ConversationType' - name: limit in: query required: false @@ -563,6 +552,11 @@ paths: minimum: 0 default: 0 title: Offset + - name: type + in: query + required: true + schema: + $ref: '#/components/schemas/ConversationType' responses: '200': description: Successful Response @@ -584,11 +578,6 @@ paths: type: string format: uuid title: Conversation Id - - name: type - in: query - required: true - schema: - $ref: '#/components/schemas/ConversationType' requestBody: required: true content: @@ -615,11 +604,6 @@ paths: type: string format: uuid title: Conversation Id - - name: type - in: query - required: true - schema: - $ref: '#/components/schemas/ConversationType' responses: '204': description: Successful Response @@ -636,11 +620,6 @@ paths: type: string format: uuid title: Conversation Id - - name: type - in: query - required: true - schema: - $ref: '#/components/schemas/ConversationType' responses: '200': description: Successful Response diff --git a/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py b/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py index 13b92ed4993f..56440e5c24e0 100644 --- a/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py +++ b/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_messages_rest.py @@ -97,6 +97,7 @@ async def create_conversation_message(request: web.Request): message, is_first_message = ( await _conversation_message_service.create_support_message_with_first_check( app=request.app, + product_name=req_ctx.product_name, user_id=req_ctx.user_id, project_id=None, # Support conversations don't use project_id conversation_id=path_params.conversation_id, @@ -128,11 +129,6 @@ async def create_conversation_message(request: web.Request): template=email_template_path, context={ "host": request.host, - "product": product.model_dump( - include={ - "display_name", - } - ), "first_name": user["first_name"], "last_name": user["last_name"], "user_email": user["email"], @@ -275,6 +271,7 @@ async def update_conversation_message(request: web.Request): message = await _conversation_message_service.update_message( app=request.app, + product_name=req_ctx.product_name, project_id=None, # Support conversations don't use project_id conversation_id=path_params.conversation_id, message_id=path_params.message_id, @@ -314,6 +311,7 @@ async def delete_conversation_message(request: web.Request): await _conversation_message_service.delete_message( app=request.app, + product_name=req_ctx.product_name, user_id=req_ctx.user_id, project_id=None, # Support conversations don't use project_id conversation_id=path_params.conversation_id, diff --git a/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_rest.py b/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_rest.py index 3abe811912e1..70884efb2537 100644 --- a/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_rest.py +++ b/services/web/server/src/simcore_service_webserver/conversations/_controller/_conversations_rest.py @@ -16,7 +16,7 @@ PageQueryParameters, ) from models_library.rest_pagination_utils import paginate_data -from pydantic import BaseModel, ConfigDict, field_validator +from pydantic import ConfigDict, field_validator from servicelib.aiohttp import status from servicelib.aiohttp.requests_validation import ( parse_request_body_as, @@ -29,12 +29,9 @@ from ..._meta import API_VTAG as VTAG from ...login.decorators import login_required from ...models import AuthenticatedRequestContext +from ...users import users_service from ...utils_aiohttp import envelope_json_response -from .. import conversations_service -from .._conversation_service import ( - get_support_conversation_for_user, - list_support_conversations_for_user, -) +from .. import _conversation_service, conversations_service from ._common import ConversationPathParams, raise_unsupported_type from ._rest_exceptions import _handle_exceptions @@ -43,7 +40,7 @@ routes = web.RouteTableDef() -class _GetConversationsQueryParams(BaseModel): +class _ListConversationsQueryParams(PageQueryParameters): type: ConversationType model_config = ConfigDict(extra="forbid") @@ -56,11 +53,6 @@ def validate_type(cls, value): return value -class _ListConversationsQueryParams(PageQueryParameters, _GetConversationsQueryParams): - - model_config = ConfigDict(extra="forbid") - - class _ConversationsCreateBodyParams(InputSchema): name: str type: ConversationType @@ -109,14 +101,17 @@ async def list_conversations(request: web.Request): query_params = parse_request_query_parameters_as( _ListConversationsQueryParams, request ) - assert query_params.type == ConversationType.SUPPORT # nosec - - total, conversations = await list_support_conversations_for_user( - app=request.app, - user_id=req_ctx.user_id, - product_name=req_ctx.product_name, - offset=query_params.offset, - limit=query_params.limit, + if query_params.type != ConversationType.SUPPORT: + raise_unsupported_type(query_params.type) + + total, conversations = ( + await _conversation_service.list_support_conversations_for_user( + app=request.app, + user_id=req_ctx.user_id, + product_name=req_ctx.product_name, + offset=query_params.offset, + limit=query_params.limit, + ) ) page = Page[ConversationRestGet].model_validate( @@ -147,12 +142,14 @@ async def get_conversation(request: web.Request): """Get a specific conversation""" req_ctx = AuthenticatedRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(ConversationPathParams, request) - query_params = parse_request_query_parameters_as( - _GetConversationsQueryParams, request + + conversation = await _conversation_service.get_conversation( + request.app, conversation_id=path_params.conversation_id ) - assert query_params.type == ConversationType.SUPPORT # nosec + if conversation.type != ConversationType.SUPPORT: + raise_unsupported_type(conversation.type) - conversation = await get_support_conversation_for_user( + conversation = await _conversation_service.get_support_conversation_for_user( app=request.app, user_id=req_ctx.user_id, product_name=req_ctx.product_name, @@ -174,16 +171,21 @@ async def update_conversation(request: web.Request): req_ctx = AuthenticatedRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(ConversationPathParams, request) body_params = await parse_request_body_as(ConversationPatch, request) - query_params = parse_request_query_parameters_as( - _GetConversationsQueryParams, request + + conversation = await _conversation_service.get_conversation( + request.app, conversation_id=path_params.conversation_id ) - assert query_params.type == ConversationType.SUPPORT # nosec + if conversation.type != ConversationType.SUPPORT: + raise_unsupported_type(conversation.type) - await get_support_conversation_for_user( + # Only support conversation creator can update conversation + _user_group_id = await users_service.get_user_primary_group_id( + request.app, user_id=req_ctx.user_id + ) + await _conversation_service.get_conversation_for_user( app=request.app, - user_id=req_ctx.user_id, - product_name=req_ctx.product_name, conversation_id=path_params.conversation_id, + user_group_id=_user_group_id, ) conversation = await conversations_service.update_conversation( @@ -207,16 +209,21 @@ async def delete_conversation(request: web.Request): """Delete a conversation""" req_ctx = AuthenticatedRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(ConversationPathParams, request) - query_params = parse_request_query_parameters_as( - _GetConversationsQueryParams, request + + conversation = await _conversation_service.get_conversation( + request.app, conversation_id=path_params.conversation_id ) - assert query_params.type == ConversationType.SUPPORT # nosec + if conversation.type != ConversationType.SUPPORT: + raise_unsupported_type(conversation.type) - await get_support_conversation_for_user( + # Only support conversation creator can delete conversation + _user_group_id = await users_service.get_user_primary_group_id( + request.app, user_id=req_ctx.user_id + ) + await _conversation_service.get_conversation_for_user( app=request.app, - user_id=req_ctx.user_id, - product_name=req_ctx.product_name, conversation_id=path_params.conversation_id, + user_group_id=_user_group_id, ) await conversations_service.delete_conversation( diff --git a/services/web/server/src/simcore_service_webserver/conversations/_conversation_message_service.py b/services/web/server/src/simcore_service_webserver/conversations/_conversation_message_service.py index 1311654d60c6..e920306c0428 100644 --- a/services/web/server/src/simcore_service_webserver/conversations/_conversation_message_service.py +++ b/services/web/server/src/simcore_service_webserver/conversations/_conversation_message_service.py @@ -11,16 +11,18 @@ ConversationMessagePatchDB, ConversationMessageType, ) +from models_library.products import ProductName from models_library.projects import ProjectID from models_library.rest_ordering import OrderBy, OrderDirection from models_library.rest_pagination import PageTotalCount from models_library.users import UserID from servicelib.redis import exclusive +from simcore_service_webserver.groups import api as group_service +from ..products import products_service from ..redis import get_redis_lock_manager_client_sdk from ..users import users_service -from . import _conversation_message_repository -from ._conversation_service import _get_recipients +from . import _conversation_message_repository, _conversation_service from ._socketio import ( notify_conversation_message_created, notify_conversation_message_deleted, @@ -33,9 +35,23 @@ CONVERSATION_MESSAGE_REDIS_LOCK_KEY = "conversation_message_update:{}" +async def _get_recipients_from_product_support_group( + app: web.Application, product_name: ProductName +) -> set[UserID]: + product = products_service.get_product(app, product_name=product_name) + _support_standard_group_id = product.support_standard_group_id + if _support_standard_group_id: + users = await group_service.list_group_members( + app, group_id=_support_standard_group_id + ) + return {user.id for user in users} + return set() + + async def create_message( app: web.Application, *, + product_name: ProductName, user_id: UserID, project_id: ProjectID | None, conversation_id: ConversationID, @@ -56,10 +72,28 @@ async def create_message( if project_id: await notify_conversation_message_created( app, - recipients=await _get_recipients(app, project_id), + recipients=await _conversation_service.get_recipients_from_project( + app, project_id + ), project_id=project_id, conversation_message=created_message, ) + else: + _conversation = await _conversation_service.get_conversation( + app, conversation_id=conversation_id + ) + _conversation_creator_user = await users_service.get_user_id_from_gid( + app, primary_gid=_conversation.user_group_id + ) + _product_group_users = await _get_recipients_from_product_support_group( + app, product_name=product_name + ) + await notify_conversation_message_created( + app, + recipients=_product_group_users | {_conversation_creator_user}, + project_id=None, + conversation_message=created_message, + ) return created_message @@ -67,6 +101,7 @@ async def create_message( async def create_support_message_with_first_check( app: web.Application, *, + product_name: ProductName, user_id: UserID, project_id: ProjectID | None, conversation_id: ConversationID, @@ -105,6 +140,7 @@ async def _create_support_message_and_check_if_it_is_first_message() -> ( """ created_message = await create_message( app, + product_name=product_name, user_id=user_id, project_id=project_id, conversation_id=conversation_id, @@ -145,6 +181,7 @@ async def get_message( async def update_message( app: web.Application, *, + product_name: ProductName, project_id: ProjectID | None, conversation_id: ConversationID, message_id: ConversationMessageID, @@ -161,10 +198,28 @@ async def update_message( if project_id: await notify_conversation_message_updated( app, - recipients=await _get_recipients(app, project_id), + recipients=await _conversation_service.get_recipients_from_project( + app, project_id + ), project_id=project_id, conversation_message=updated_message, ) + else: + _conversation = await _conversation_service.get_conversation( + app, conversation_id=conversation_id + ) + _conversation_creator_user = await users_service.get_user_id_from_gid( + app, primary_gid=_conversation.user_group_id + ) + _product_group_users = await _get_recipients_from_product_support_group( + app, product_name=product_name + ) + await notify_conversation_message_updated( + app, + recipients=_product_group_users | {_conversation_creator_user}, + project_id=None, + conversation_message=updated_message, + ) return updated_message @@ -172,6 +227,7 @@ async def update_message( async def delete_message( app: web.Application, *, + product_name: ProductName, user_id: UserID, project_id: ProjectID | None, conversation_id: ConversationID, @@ -188,12 +244,32 @@ async def delete_message( if project_id: await notify_conversation_message_deleted( app, - recipients=await _get_recipients(app, project_id), + recipients=await _conversation_service.get_recipients_from_project( + app, project_id + ), user_group_id=_user_group_id, project_id=project_id, conversation_id=conversation_id, message_id=message_id, ) + else: + _conversation = await _conversation_service.get_conversation( + app, conversation_id=conversation_id + ) + _conversation_creator_user = await users_service.get_user_id_from_gid( + app, primary_gid=_conversation.user_group_id + ) + _product_group_users = await _get_recipients_from_product_support_group( + app, product_name=product_name + ) + await notify_conversation_message_deleted( + app, + recipients=_product_group_users | {_conversation_creator_user}, + user_group_id=_user_group_id, + project_id=None, + conversation_id=conversation_id, + message_id=message_id, + ) async def list_messages_for_conversation( diff --git a/services/web/server/src/simcore_service_webserver/conversations/_conversation_service.py b/services/web/server/src/simcore_service_webserver/conversations/_conversation_service.py index d1b87dd284eb..36805b64bde3 100644 --- a/services/web/server/src/simcore_service_webserver/conversations/_conversation_service.py +++ b/services/web/server/src/simcore_service_webserver/conversations/_conversation_service.py @@ -32,7 +32,9 @@ _logger = logging.getLogger(__name__) -async def _get_recipients(app: web.Application, project_id: ProjectID) -> set[UserID]: +async def get_recipients_from_project( + app: web.Application, project_id: ProjectID +) -> set[UserID]: groups = await list_project_groups(app, project_id=project_id) return { user @@ -68,7 +70,7 @@ async def create_conversation( if project_uuid: await notify_conversation_created( app, - recipients=await _get_recipients(app, project_uuid), + recipients=await get_recipients_from_project(app, project_uuid), project_id=project_uuid, conversation=created_conversation, ) @@ -122,7 +124,7 @@ async def update_conversation( if project_id: await notify_conversation_updated( app, - recipients=await _get_recipients(app, project_id), + recipients=await get_recipients_from_project(app, project_id), project_id=project_id, conversation=updated_conversation, ) @@ -148,7 +150,7 @@ async def delete_conversation( if project_id: await notify_conversation_deleted( app, - recipients=await _get_recipients(app, project_id), + recipients=await get_recipients_from_project(app, project_id), product_name=product_name, user_group_id=_user_group_id, project_id=project_id, diff --git a/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py b/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py index 83740fce3920..8c04d62b2d33 100644 --- a/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py +++ b/services/web/server/src/simcore_service_webserver/groups/_groups_repository.py @@ -465,11 +465,11 @@ async def _get_user_in_group_or_raise( return row -async def list_users_in_group( +async def list_users_in_group_with_caller_check( app: web.Application, connection: AsyncConnection | None = None, *, - caller_id: UserID, + caller_user_id: UserID, group_id: GroupID, ) -> list[GroupMember]: async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn: @@ -487,7 +487,7 @@ async def list_users_in_group( .where( (user_to_groups.c.gid == group_id) & ( - (user_to_groups.c.uid == caller_id) + (user_to_groups.c.uid == caller_user_id) | ( (groups.c.type == GroupType.PRIMARY) & users.c.role.in_([r for r in UserRole if r > UserRole.GUEST]) @@ -504,14 +504,17 @@ async def list_users_in_group( # Drop access-rights if primary group if group_row.type == GroupType.PRIMARY: query = sa.select( - *_group_user_cols(caller_id), + *_group_user_cols(caller_user_id), ) else: _check_group_permissions( - group_row, caller_id=caller_id, group_id=group_id, permission="read" + group_row, + caller_id=caller_user_id, + group_id=group_id, + permission="read", ) query = sa.select( - *_group_user_cols(caller_id), + *_group_user_cols(caller_user_id), user_to_groups.c.access_rights, ) @@ -527,6 +530,42 @@ async def list_users_in_group( ] +async def list_users_in_group( + app: web.Application, + connection: AsyncConnection | None = None, + *, + group_id: GroupID, +) -> list[GroupMember]: + """ + Returns all users in a group without any permission checking. + This is a pure function that doesn't validate caller permissions. + """ + async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn: + # Check if group exists + group_exists = await conn.scalar( + sa.select(groups.c.gid).where(groups.c.gid == group_id) + ) + if not group_exists: + raise GroupNotFoundError(gid=group_id) + + # Get all users in the group + query = ( + sa.select(*_GROUP_COLUMNS) + .select_from( + groups.join( + user_to_groups, user_to_groups.c.gid == groups.c.gid, isouter=True + ).join(users, users.c.id == user_to_groups.c.uid) + ) + .where(user_to_groups.c.gid == group_id) + ) + + result = await conn.stream(query) + return [ + GroupMember.model_validate(row, from_attributes=True) + async for row in result + ] + + async def get_user_in_group( app: web.Application, connection: AsyncConnection | None = None, diff --git a/services/web/server/src/simcore_service_webserver/groups/_groups_rest.py b/services/web/server/src/simcore_service_webserver/groups/_groups_rest.py index 18032f8ea376..07a0b5026d48 100644 --- a/services/web/server/src/simcore_service_webserver/groups/_groups_rest.py +++ b/services/web/server/src/simcore_service_webserver/groups/_groups_rest.py @@ -73,9 +73,9 @@ async def list_groups(request: web.Request): GroupGet.from_domain_model(*gi) for gi in groups_by_type.standard ], all=GroupGet.from_domain_model(*groups_by_type.everyone), - product=GroupGet.from_domain_model(*my_product_group) - if my_product_group - else None, + product=( + GroupGet.from_domain_model(*my_product_group) if my_product_group else None + ), ) return envelope_json_response(my_groups) @@ -173,7 +173,7 @@ async def get_all_group_users(request: web.Request): req_ctx = GroupsRequestContext.model_validate(request) path_params = parse_request_path_parameters_as(GroupsPathParams, request) - users_in_group = await _groups_service.list_group_members( + users_in_group = await _groups_service.list_group_members_with_caller_check( request.app, req_ctx.user_id, path_params.gid ) diff --git a/services/web/server/src/simcore_service_webserver/groups/_groups_service.py b/services/web/server/src/simcore_service_webserver/groups/_groups_service.py index d9e5465efd20..67ff9d1dfafa 100644 --- a/services/web/server/src/simcore_service_webserver/groups/_groups_service.py +++ b/services/web/server/src/simcore_service_webserver/groups/_groups_service.py @@ -154,14 +154,20 @@ async def delete_standard_group( # -async def list_group_members( +async def list_group_members_with_caller_check( app: web.Application, user_id: UserID, group_id: GroupID ) -> list[GroupMember]: - return await _groups_repository.list_users_in_group( - app, caller_id=user_id, group_id=group_id + return await _groups_repository.list_users_in_group_with_caller_check( + app, caller_user_id=user_id, group_id=group_id ) +async def list_group_members( + app: web.Application, group_id: GroupID +) -> list[GroupMember]: + return await _groups_repository.list_users_in_group(app, group_id=group_id) + + async def get_group_member( app: web.Application, user_id: UserID, diff --git a/services/web/server/src/simcore_service_webserver/groups/api.py b/services/web/server/src/simcore_service_webserver/groups/api.py index a01fe9ef63f0..dfe60c5866f9 100644 --- a/services/web/server/src/simcore_service_webserver/groups/api.py +++ b/services/web/server/src/simcore_service_webserver/groups/api.py @@ -9,6 +9,7 @@ get_product_group_for_user, is_user_by_email_in_group, list_all_user_groups_ids, + list_group_members, list_user_groups_ids_with_read_access, list_user_groups_with_read_access, ) @@ -23,5 +24,6 @@ "list_all_user_groups_ids", "list_user_groups_ids_with_read_access", "list_user_groups_with_read_access", + "list_group_members", # nopycln: file ) diff --git a/services/web/server/src/simcore_service_webserver/projects/_conversations_service.py b/services/web/server/src/simcore_service_webserver/projects/_conversations_service.py index a7f384ea24b9..c52e8ef01528 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_conversations_service.py +++ b/services/web/server/src/simcore_service_webserver/projects/_conversations_service.py @@ -175,6 +175,7 @@ async def create_project_conversation_message( ) return await conversations_service.create_message( app, + product_name=product_name, user_id=user_id, project_id=project_uuid, conversation_id=conversation_id, @@ -229,6 +230,7 @@ async def update_project_conversation_message( ) return await conversations_service.update_message( app, + product_name=product_name, project_id=project_uuid, conversation_id=conversation_id, message_id=message_id, @@ -254,6 +256,7 @@ async def delete_project_conversation_message( ) await conversations_service.delete_message( app, + product_name=product_name, user_id=user_id, project_id=project_uuid, conversation_id=conversation_id, diff --git a/services/web/server/src/simcore_service_webserver/templates/common/request_support.jinja2 b/services/web/server/src/simcore_service_webserver/templates/common/request_support.jinja2 index 8924f2e33e17..fe2ebaa25238 100644 --- a/services/web/server/src/simcore_service_webserver/templates/common/request_support.jinja2 +++ b/services/web/server/src/simcore_service_webserver/templates/common/request_support.jinja2 @@ -15,7 +15,7 @@ TEST extra_context: {{ extra_context }}

- We have received a support request from {{ first_name }} {{ last_name }} ({{ user_email }}) regarding an account in {{ product.display_name }} on {{ host }}. + We have received a support request from {{ first_name }} {{ last_name }} ({{ user_email }}) on {{ host }}.

diff --git a/services/web/server/tests/unit/with_dbs/04/conversations/test_conversations_rest.py b/services/web/server/tests/unit/with_dbs/04/conversations/test_conversations_rest.py index d46b09708e3b..10a6a9d4c3e3 100644 --- a/services/web/server/tests/unit/with_dbs/04/conversations/test_conversations_rest.py +++ b/services/web/server/tests/unit/with_dbs/04/conversations/test_conversations_rest.py @@ -19,6 +19,7 @@ from servicelib.aiohttp import status from simcore_service_webserver.conversations import _conversation_service from simcore_service_webserver.db.models import UserRole +from simcore_service_webserver.projects.models import ProjectDict @pytest.fixture @@ -411,34 +412,40 @@ async def test_conversations_error_handling( async def test_conversations_without_type_query_param( client: TestClient, logged_user: UserInfoDict, + user_project: ProjectDict, ): """Test that endpoints require type query parameter""" - base_url = client.app.router["list_conversations"].url_for() - # Create a conversation first - body = {"name": "Test Conversation", "type": "SUPPORT"} - resp = await client.post(f"{base_url}", json=body) + # Create a conversation via project endpoint first + project_id = f"{user_project['uuid']}" + project_conversation_url = client.app.router["create_project_conversation"].url_for( + project_id=f"{project_id}" + ) + body = {"name": "Test Conversation", "type": "PROJECT_STATIC"} + resp = await client.post(f"{project_conversation_url}", json=body) data, _ = await assert_status(resp, status.HTTP_201_CREATED) conversation_id = data["conversationId"] - # Test endpoints without type parameter should fail - resp = await client.get(f"{base_url}") + # Test list endpoint without type parameter should fail + list_url = client.app.router["list_conversations"].url_for() + resp = await client.get(f"{list_url}") await assert_status(resp, status.HTTP_422_UNPROCESSABLE_ENTITY) + # All other endpoints should return 400, because we currently support only SUPPORT type get_url = client.app.router["get_conversation"].url_for( conversation_id=conversation_id ) resp = await client.get(f"{get_url}") - await assert_status(resp, status.HTTP_422_UNPROCESSABLE_ENTITY) + await assert_status(resp, status.HTTP_400_BAD_REQUEST) update_url = client.app.router["update_conversation"].url_for( conversation_id=conversation_id ) resp = await client.patch(f"{update_url}", json={"name": "Updated"}) - await assert_status(resp, status.HTTP_422_UNPROCESSABLE_ENTITY) + await assert_status(resp, status.HTTP_400_BAD_REQUEST) delete_url = client.app.router["delete_conversation"].url_for( conversation_id=conversation_id ) resp = await client.delete(f"{delete_url}") - await assert_status(resp, status.HTTP_422_UNPROCESSABLE_ENTITY) + await assert_status(resp, status.HTTP_400_BAD_REQUEST)