From cba0a8ba324f40b0d1ebda22ed695c47d5dd8b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Alberto=20Monta=C3=B1o=20Fetecua?= Date: Sun, 18 Aug 2024 19:04:34 -0500 Subject: [PATCH] feat: implemented retrieval of integrations --- src/apps/integrations/api/integrations.py | 28 +++-- src/apps/integrations/crud/integrations.py | 10 ++ src/apps/integrations/domain.py | 32 ++++++ src/apps/integrations/errors.py | 10 ++ src/apps/integrations/loris/api/applets.py | 19 +++- .../loris/domain/loris_integrations.py | 31 +++--- src/apps/integrations/loris/router.py | 14 ++- src/apps/integrations/loris/service/loris.py | 27 +++++ .../loris/service/loris_client.py | 11 +- src/apps/integrations/router.py | 23 +++- .../service/future_integration.py | 35 ++++++ src/apps/integrations/service/integrations.py | 104 +++++++++++++----- .../integrations/tests/test_integrations.py | 71 ++++-------- .../tests/unit/domain/test_integration.py | 6 + src/apps/workspaces/errors.py | 4 + src/apps/workspaces/service/check_access.py | 3 +- 16 files changed, 312 insertions(+), 116 deletions(-) create mode 100644 src/apps/integrations/service/future_integration.py diff --git a/src/apps/integrations/api/integrations.py b/src/apps/integrations/api/integrations.py index 6e707b99df4..cb9d863e9f9 100644 --- a/src/apps/integrations/api/integrations.py +++ b/src/apps/integrations/api/integrations.py @@ -1,9 +1,9 @@ +import uuid + from fastapi import Body, Depends from apps.authentication.deps import get_current_user -from apps.integrations.domain import Integration, IntegrationFilter -from apps.integrations.loris.domain.loris_integrations import IntegrationsCreate -from apps.integrations.loris.domain.loris_projects import LorisProjects +from apps.integrations.domain import Integration, IntegrationFilter, Integration from apps.integrations.service import IntegrationService from apps.shared.domain import ResponseMulti from apps.shared.query_params import QueryParams, parse_query_params @@ -12,7 +12,7 @@ from infrastructure.database import atomic from infrastructure.database.deps import get_session -__all__ = ["enable_integration", "disable_integration", "create_integration"] +__all__ = ["enable_integration", "disable_integration", "create_integration", "retrieve_integration"] async def enable_integration( @@ -35,11 +35,23 @@ async def disable_integration( async def create_integration( + session=Depends(get_session), + user: User = Depends(get_current_user), + integrationsCreate: Integration = Body(...), +) -> Integration: + await CheckAccessService(session, user.id).check_integrations_create_access( + integrationsCreate.applet_id, integrationsCreate.integration_type + ) + async with atomic(session): + return await IntegrationService(session, user).create_integration(integrationsCreate) + + +async def retrieve_integration( type: str, + applet_id: uuid.UUID, session=Depends(get_session), user: User = Depends(get_current_user), - params: IntegrationsCreate = Body(...), -) -> LorisProjects: - await CheckAccessService(session, user.id).check_integrations_create_access(params.applet_id, type) +) -> Integration: + await CheckAccessService(session, user.id).check_integrations_retrieve_access(applet_id, type) async with atomic(session): - return await IntegrationService(session, user).create_integration(type, params) + return await IntegrationService(session, user).retrieve_integration(applet_id, type) diff --git a/src/apps/integrations/crud/integrations.py b/src/apps/integrations/crud/integrations.py index 371f0d4caa2..e60e86d7e39 100644 --- a/src/apps/integrations/crud/integrations.py +++ b/src/apps/integrations/crud/integrations.py @@ -1,3 +1,5 @@ +from sqlalchemy import select +from sqlalchemy.engine import Result from sqlalchemy.exc import IntegrityError from apps.integrations.db.schemas import IntegrationsSchema @@ -20,3 +22,11 @@ async def create(self, schema: IntegrationsSchema) -> IntegrationsSchema: type=schema.type, applet_id=schema.applet_id ) return new_integrations + + async def retrieve(self, schema: IntegrationsSchema) -> IntegrationsSchema: + query = select(IntegrationsSchema) + query = query.where(IntegrationsSchema.applet_id == schema.applet_id) + query = query.where(IntegrationsSchema.type == schema.type) + query = query.limit(1) + result: Result = await self._execute(query) + return result.scalars().first() diff --git a/src/apps/integrations/domain.py b/src/apps/integrations/domain.py index 17880590e98..b219aae9af7 100644 --- a/src/apps/integrations/domain.py +++ b/src/apps/integrations/domain.py @@ -1,14 +1,46 @@ +import json +import uuid from enum import Enum +from typing import Union +from apps.integrations.db.schemas import IntegrationsSchema +from apps.integrations.loris.domain.loris_integrations import LorisIntegration from apps.shared.domain import InternalModel class AvailableIntegrations(str, Enum): LORIS = "LORIS" + FUTURE = "FUTURE" +class FutureIntegration(InternalModel): + endpoint: str + api_key: str + + @classmethod + def from_schema(cls, schema: IntegrationsSchema): + new_future_integration_dict = json.loads(schema.configuration.replace("'", '"')) + new_future_integration_dict["api_key"] = "*****" + + new_future_integration = cls( + endpoint=new_future_integration_dict["endpoint"], + api_key=new_future_integration_dict["api_key"], + ) + return new_future_integration + class Integration(InternalModel): integration_type: AvailableIntegrations + applet_id: uuid.UUID + configuration: Union[LorisIntegration, FutureIntegration] + + @classmethod + def from_schema(cls, schema: IntegrationsSchema): + new_integration = cls( + applet_id=schema.applet_id, + integration_type=schema.type, + configuration=schema.configuration + ) + return new_integration class IntegrationFilter(InternalModel): diff --git a/src/apps/integrations/errors.py b/src/apps/integrations/errors.py index 6c8dbf9a482..fe7845f5727 100644 --- a/src/apps/integrations/errors.py +++ b/src/apps/integrations/errors.py @@ -11,5 +11,15 @@ class IntegrationsConfigurationsTypeAlreadyAssignedToAppletError(ValidationError message = _("Provided Integration Type `{type}` has previously been tied to applet `{applet_id}`.") +class UnexpectedPropertiesForIntegration(ValidationError): + message = _( + """Provided configurations `{provided_keys}` for Integration Type `{type}` were not expected. Expected keys are: `{expected_keys}`""" + ) + + class UnsupportedIntegrationError(ValidationError): message = _("The specified integration type `{type}` is not supported") + + +class UnavailableIntegrationError(ValidationError): + message = _("The specified integration type `{type}` does not exist for applet `{applet_id}`") diff --git a/src/apps/integrations/loris/api/applets.py b/src/apps/integrations/loris/api/applets.py index b3ff19127d5..4b31738b7f6 100644 --- a/src/apps/integrations/loris/api/applets.py +++ b/src/apps/integrations/loris/api/applets.py @@ -7,16 +7,13 @@ from apps.answers.deps.preprocess_arbitrary import get_answer_session from apps.authentication.deps import get_current_user from apps.integrations.loris.domain.domain import PublicListOfVisits, UploadableAnswersResponse, VisitsForUsers +from apps.integrations.loris.domain.loris_projects import LorisProjects from apps.integrations.loris.service.loris import LorisIntegrationService from apps.users.domain import User from apps.workspaces.service.check_access import CheckAccessService from infrastructure.database.deps import get_session -__all__ = [ - "start_transmit_process", - "visits_list", - "users_info_with_visits", -] +__all__ = ["start_transmit_process", "visits_list", "users_info_with_visits", "get_loris_projects"] async def integration(applet_id: uuid.UUID, session, user, users_and_visits): @@ -59,3 +56,15 @@ async def users_info_with_visits( # TODO move to worker info, count = await loris_service.get_uploadable_answers() return UploadableAnswersResponse(result=info, count=count) + + +async def get_loris_projects( + hostname: str, + username: str, + password: str, + user: User = Depends(get_current_user), + session=Depends(get_session), +) -> LorisProjects: + return await LorisIntegrationService( + uuid.UUID("00000000-0000-0000-0000-000000000000"), session=session, user=user + ).get_loris_projects(hostname, username, password) diff --git a/src/apps/integrations/loris/domain/loris_integrations.py b/src/apps/integrations/loris/domain/loris_integrations.py index 97bd87aff05..322a8fffc5f 100644 --- a/src/apps/integrations/loris/domain/loris_integrations.py +++ b/src/apps/integrations/loris/domain/loris_integrations.py @@ -1,29 +1,24 @@ -import uuid +import json from apps.integrations.db.schemas import IntegrationsSchema -from apps.integrations.domain import AvailableIntegrations from apps.shared.domain import InternalModel -class IntegrationMeta(InternalModel): +class LorisIntegration(InternalModel): hostname: str username: str password: str - project: str | None - - -class Integrations(InternalModel): - applet_id: uuid.UUID - type: AvailableIntegrations - configuration: IntegrationMeta + project: str @classmethod def from_schema(cls, schema: IntegrationsSchema): - return cls(applet_id=schema.applet_id, type=schema.type, configuration=schema.configuration) - - -class IntegrationsCreate(InternalModel): - applet_id: uuid.UUID - hostname: str - username: str - password: str + new_loris_integration_dict = json.loads(schema.configuration.replace("'", '"')) + new_loris_integration_dict["password"] = "*****" + + new_loris_integration = cls( + hostname=new_loris_integration_dict["hostname"], + username=new_loris_integration_dict["username"], + password=new_loris_integration_dict["password"], + project=new_loris_integration_dict["project"] + ) + return new_loris_integration diff --git a/src/apps/integrations/loris/router.py b/src/apps/integrations/loris/router.py index 12ea21ddc23..a25ccd821eb 100644 --- a/src/apps/integrations/loris/router.py +++ b/src/apps/integrations/loris/router.py @@ -1,7 +1,7 @@ from fastapi.routing import APIRouter from starlette import status -from apps.integrations.loris.api import start_transmit_process, users_info_with_visits, visits_list +from apps.integrations.loris.api import start_transmit_process, users_info_with_visits, visits_list, get_loris_projects from apps.integrations.loris.api.consent import ( consent_create, consent_get_by_id, @@ -9,6 +9,7 @@ consent_update, ) from apps.integrations.loris.domain.domain import PublicConsent, PublicListOfVisits, UploadableAnswersResponse +from apps.integrations.loris.domain.loris_projects import LorisProjects from apps.shared.domain import Response from apps.shared.domain.response import ( AUTHENTICATION_ERROR_RESPONSES, @@ -105,3 +106,14 @@ **AUTHENTICATION_ERROR_RESPONSES, }, )(users_info_with_visits) + + +router.get( + "/projects", + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"model": LorisProjects}, + **DEFAULT_OPENAPI_RESPONSE, + **AUTHENTICATION_ERROR_RESPONSES, + }, +)(get_loris_projects) diff --git a/src/apps/integrations/loris/service/loris.py b/src/apps/integrations/loris/service/loris.py index 88da2ba2ef6..aaa4c505f90 100644 --- a/src/apps/integrations/loris/service/loris.py +++ b/src/apps/integrations/loris/service/loris.py @@ -19,6 +19,8 @@ from apps.answers.errors import ReportServerError from apps.answers.service import ReportServerService from apps.applets.crud.applets_history import AppletHistoriesCRUD +from apps.integrations.crud.integrations import IntegrationsCRUD, IntegrationsSchema +from apps.integrations.domain import AvailableIntegrations from apps.integrations.loris.crud.user_relationship import MlLorisUserRelationshipCRUD from apps.integrations.loris.db.schemas import MlLorisUserRelationshipSchema from apps.integrations.loris.domain.domain import ( @@ -29,7 +31,10 @@ UploadableAnswersData, UserVisits, ) +from apps.integrations.loris.domain.loris_integrations import LorisIntegration +from apps.integrations.loris.domain.loris_projects import LorisProjects from apps.integrations.loris.errors import LorisServerError +from apps.integrations.loris.service.loris_client import LorisClient from apps.subjects.crud import SubjectsCrud from apps.users.domain import User from apps.workspaces.crud.user_applet_access import UserAppletAccessCRUD @@ -55,6 +60,7 @@ def __init__(self, applet_id: uuid.UUID, session, user: User, answer_session=Non self.applet_id = applet_id self.session = session self.user = user + self.type = AvailableIntegrations.LORIS self._answer_session = answer_session @property @@ -853,3 +859,24 @@ async def _create_integration_alerts(self, applet_id: uuid.UUID, message: str): except Exception as e: sentry_sdk.capture_exception(e) break + + async def create_loris_integration(self, hostname, username, password, project) -> LorisIntegration: + integration_schema = await IntegrationsCRUD(self.session).create( + IntegrationsSchema( + applet_id=self.applet_id, + type=self.type, + configuration={ + "hostname": hostname, + "username": username, + "password": password, + "project": project, + }, + ) + ) + return LorisIntegration.from_schema(integration_schema) + + async def get_loris_projects(self, hostname, username, password) -> LorisProjects: + token = await LorisClient.login_to_loris(hostname, username, password) + projects_raw = await LorisClient.list_projects(hostname, token) + projects = list(projects_raw["Projects"].keys()) + return LorisProjects(projects=projects) diff --git a/src/apps/integrations/loris/service/loris_client.py b/src/apps/integrations/loris/service/loris_client.py index 46993b8893b..3425aee54b5 100644 --- a/src/apps/integrations/loris/service/loris_client.py +++ b/src/apps/integrations/loris/service/loris_client.py @@ -1,7 +1,7 @@ import json import aiohttp -from aiohttp.client_exceptions import ClientConnectorError +from aiohttp.client_exceptions import ClientConnectorError, ContentTypeError from apps.integrations.loris.errors import LorisBadCredentialsError, LorisInvalidHostname, LorisInvalidTokenError from apps.shared.domain.custom_validations import InvalidUrlError, validate_url @@ -22,11 +22,14 @@ async def login_to_loris(self, hostname: str, username: str, password: str) -> s async with aiohttp.ClientSession(timeout=timeout) as session: try: async with session.post( - f"{hostname}/login", + f"{hostname}/api/v0.0.3/login", data=json.dumps(loris_login_data), ) as resp: if resp.status == 200: - response_data = await resp.json() + try: + response_data = await resp.json() + except ContentTypeError as cce: + raise LorisBadCredentialsError(message=cce.message) return response_data["token"] else: error_message = await resp.text() @@ -47,7 +50,7 @@ async def list_projects(self, hostname: str, token: str): } timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout) as session: - url = f"{hostname}/projects" + url = f"{hostname}/api/v0.0.3/projects" async with session.get( url=url, headers=headers, diff --git a/src/apps/integrations/router.py b/src/apps/integrations/router.py index 7af69731e16..cde7819f6c2 100644 --- a/src/apps/integrations/router.py +++ b/src/apps/integrations/router.py @@ -1,8 +1,8 @@ from fastapi.routing import APIRouter from starlette import status -from apps.integrations.api import create_integration, disable_integration, enable_integration -from apps.integrations.loris.domain.loris_projects import LorisProjects +from apps.integrations.api import create_integration, disable_integration, enable_integration, retrieve_integration +from apps.integrations.domain import Integration from apps.shared.domain import Response from apps.shared.domain.response import AUTHENTICATION_ERROR_RESPONSES, DEFAULT_OPENAPI_RESPONSE @@ -10,7 +10,7 @@ router.post( - "/", + "/enable_integration", description="This endpoint is used to enable integration\ options for a workspace", status_code=status.HTTP_200_OK, @@ -21,7 +21,7 @@ )(enable_integration) router.delete( - "/", + "/disable_integration", description="This endpoint is used to remove integrations\ from a workspace", status_code=status.HTTP_204_NO_CONTENT, @@ -32,12 +32,23 @@ )(disable_integration) router.post( - "/{type}", + path="/", description="This endpoint is used to create integrations", status_code=status.HTTP_201_CREATED, responses={ - status.HTTP_201_CREATED: {"model": Response[LorisProjects]}, + status.HTTP_201_CREATED: {"model": Response[Integration]}, **DEFAULT_OPENAPI_RESPONSE, **AUTHENTICATION_ERROR_RESPONSES, }, )(create_integration) + +router.get( + "/", + description="This endpoint is used to get integrations of an applet given a type", + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"model": Response[Integration]}, + **DEFAULT_OPENAPI_RESPONSE, + **AUTHENTICATION_ERROR_RESPONSES, + }, +)(retrieve_integration) diff --git a/src/apps/integrations/service/future_integration.py b/src/apps/integrations/service/future_integration.py new file mode 100644 index 00000000000..3d6ffaa73fc --- /dev/null +++ b/src/apps/integrations/service/future_integration.py @@ -0,0 +1,35 @@ +import uuid + +from apps.integrations.crud.integrations import IntegrationsCRUD, IntegrationsSchema +from apps.integrations.domain import AvailableIntegrations, FutureIntegration + + +class FutureIntegrationService: + """Template for future MindLogger integrations. + + This is an example of an integration that can be used + as a template in the Future for other MindLogger integrations. + + Attributes: + applet_id: A uuid.UUID that identifies the applet tied to the integration. + session: A database session. + type: An AvailableIntegrations enum that identifies the integration type. + """ + + def __init__(self, applet_id: uuid.UUID, session) -> None: + self.applet_id = applet_id + self.session = session + self.type = AvailableIntegrations.FUTURE + + async def create_future_integration(self, endpoint, api_key) -> FutureIntegration: + integration_schema = await IntegrationsCRUD(self.session).create( + IntegrationsSchema( + applet_id=self.applet_id, + type=self.type, + configuration={ + "endpoint": endpoint, + "api_key": api_key, + }, + ) + ) + return FutureIntegration.from_schema(integration_schema) diff --git a/src/apps/integrations/service/integrations.py b/src/apps/integrations/service/integrations.py index af41fc5e049..a08524519e7 100644 --- a/src/apps/integrations/service/integrations.py +++ b/src/apps/integrations/service/integrations.py @@ -1,10 +1,11 @@ +from typing import cast + from apps.integrations.crud.integrations import IntegrationsCRUD from apps.integrations.db.schemas import IntegrationsSchema -from apps.integrations.domain import AvailableIntegrations, Integration -from apps.integrations.errors import UniqueIntegrationError, UnsupportedIntegrationError -from apps.integrations.loris.domain.loris_integrations import IntegrationsCreate -from apps.integrations.loris.domain.loris_projects import LorisProjects -from apps.integrations.loris.service.loris_client import LorisClient +from apps.integrations.domain import AvailableIntegrations, FutureIntegration, Integration +from apps.integrations.errors import UnavailableIntegrationError, UnexpectedPropertiesForIntegration, UniqueIntegrationError, UnsupportedIntegrationError +from apps.integrations.loris.service.loris import LorisIntegration, LorisIntegrationService +from apps.integrations.service.future_integration import FutureIntegrationService from apps.shared.query_params import QueryParams from apps.users.domain import User from apps.workspaces.crud.workspaces import UserWorkspaceCRUD @@ -45,30 +46,81 @@ async def disable_integration(self, query: QueryParams): workspace.integrations = None await UserWorkspaceCRUD(self.session).save(workspace) - async def create_integration( - self, - type: str, - params: IntegrationsCreate, - ) -> LorisProjects: - match type: + async def create_integration(self, newIntegration: Integration) -> Integration: + match newIntegration.integration_type: case AvailableIntegrations.LORIS: - return await self._create_integration(type, params) + loris_config = cast(LorisIntegration, newIntegration.configuration) + expected_keys = ["hostname", "username", "password", "project"] + config_dict = loris_config.dict() + if None in [config_dict.get(k, None) for k in expected_keys]: + raise UnexpectedPropertiesForIntegration( + provided_keys=list(config_dict.keys()), + expected_keys=expected_keys, + type=AvailableIntegrations.LORIS, + ) + loris_integration = await LorisIntegrationService( + newIntegration.applet_id, self.session, self.user + ).create_loris_integration( + hostname=loris_config.hostname, + username=loris_config.username, + password=loris_config.password, + project=loris_config.project, + ) + return Integration( + integration_type=AvailableIntegrations.LORIS, + applet_id=newIntegration.applet_id, + configuration=loris_integration, + ) + case AvailableIntegrations.FUTURE: + future_conf = cast(FutureIntegration, newIntegration.configuration) + expected_keys = ["endpoint", "api_key"] + config_dict = future_conf.dict() + if None in [config_dict.get(k, None) for k in expected_keys]: + raise UnexpectedPropertiesForIntegration( + provided_keys=list(config_dict.keys()), + expected_keys=expected_keys, + type=AvailableIntegrations.LORIS, + ) + future_integration = await FutureIntegrationService( + newIntegration.applet_id, + self.session, + ).create_future_integration( + endpoint=future_conf.endpoint, + api_key=future_conf.api_key, + ) + return Integration( + integration_type=AvailableIntegrations.FUTURE, + applet_id=newIntegration.applet_id, + configuration=future_integration, + ) case _: - raise UnsupportedIntegrationError(type=type) + raise UnsupportedIntegrationError(type=newIntegration.integration_type) - async def _create_integration(self, type, params) -> LorisProjects: - token = await LorisClient.login_to_loris(params.hostname, params.username, params.password) - projects_raw = await LorisClient.list_projects(params.hostname, token) - await IntegrationsCRUD(self.session).create( + async def retrieve_integration(self, applet_id, integration_type) -> Integration: + integration_schema = await IntegrationsCRUD(self.session).retrieve( IntegrationsSchema( - applet_id=params.applet_id, - type=type, - configuration={ - "hostname": params.hostname, - "username": params.username, - "password": params.password, - }, + applet_id=applet_id, + type=integration_type, ) ) - projects = list(projects_raw["Projects"].keys()) - return LorisProjects(projects=projects) + + if integration_schema is None: + raise UnavailableIntegrationError(applet_id=applet_id, type=integration_type) + + match integration_type: + case AvailableIntegrations.LORIS: + loris_integration = LorisIntegration.from_schema(integration_schema) + return Integration( + integration_type=AvailableIntegrations.LORIS, + applet_id=applet_id, + configuration=loris_integration, + ) + case AvailableIntegrations.FUTURE: + future_integration = FutureIntegration.from_schema(integration_schema) + return Integration( + integration_type=AvailableIntegrations.FUTURE, + applet_id=applet_id, + configuration=future_integration, + ) + case _: + raise UnsupportedIntegrationError(type=integration_type) diff --git a/src/apps/integrations/tests/test_integrations.py b/src/apps/integrations/tests/test_integrations.py index 910cce79fe1..f0d48d7c2e9 100644 --- a/src/apps/integrations/tests/test_integrations.py +++ b/src/apps/integrations/tests/test_integrations.py @@ -1,6 +1,3 @@ -import uuid - -from apps.integrations.domain import AvailableIntegrations from apps.integrations.errors import UniqueIntegrationError from apps.integrations.router import router as integration_router from apps.shared.test import BaseTest @@ -12,61 +9,41 @@ class TestIntegrationRouter(BaseTest): fixtures = [ "workspaces/fixtures/workspaces.json", ] - enable_integration_url = integration_router.url_path_for("enable_integration") - disable_integration_url = integration_router.url_path_for("disable_integration") - create_loris_integration_url = integration_router.url_path_for( - "create_integration", type=AvailableIntegrations.LORIS - ) - - async def test_enable_integration( - self, - client: TestClient, - tom: User, - ): - integration_data = [ - {"integrationType": "LORIS"}, - ] - client.login(tom) - response = await client.post(self.enable_integration_url, data=integration_data) - assert response.status_code == 200 - async def test_disable_integration( + async def test_create_integration_access_denied( self, client: TestClient, tom: User, ): + create_loris_integration_url_data = { + "applet_id": "8fb291b2-5ecf-4f21-ada8-04ca48451660", + "integration_type": "LORIS", + "configuration": { + "hostname": "https://loris.cmiml.net", + "username": "lorisfrontadmin", + "password": "password", + "project": "loris_project", + }, + } client.login(tom) - response = await client.delete( - self.disable_integration_url, - ) - assert response.status_code == 204 - - async def test_enable_integration_unique_error( - self, - client: TestClient, - tom: User, - ): - integration_data = [ - {"integrationType": "LORIS"}, - {"integrationType": "LORIS"}, - ] - client.login(tom) - response = await client.post(self.enable_integration_url, data=integration_data) + response = await client.post("integrations/", data=create_loris_integration_url_data) assert response.status_code == 400 - result = response.json()["result"] - assert len(result) == 1 - assert result[0]["message"] == UniqueIntegrationError.message - async def test_create_integration( self, client: TestClient, tom: User, ): - create_loris_integration_url_data = [ - {"applet_id": uuid.UUID, "hostname": "https://someloris.org", "username": "david", "password": "abc12345"} - ] + create_loris_integration_url_data = { + "applet_id": "8fb291b2-5ecf-4f21-ada8-04ca48451660", + "integration_type": "LORIS", + "configuration": { + "hostname": "https://loris.cmiml.net", + "username": "lorisfrontadmin", + "password": "password", + "project": "loris_project", + }, + } client.login(tom) - response = await client.post(self.create_loris_integration_url, data=create_loris_integration_url_data) - # assert response.status_code == 201 - assert response.status_code == 422 + response = await client.post("integrations/", data=create_loris_integration_url_data) + assert response.status_code == 400 diff --git a/src/apps/integrations/tests/unit/domain/test_integration.py b/src/apps/integrations/tests/unit/domain/test_integration.py index d3755e6448a..a6ea335562a 100644 --- a/src/apps/integrations/tests/unit/domain/test_integration.py +++ b/src/apps/integrations/tests/unit/domain/test_integration.py @@ -1,3 +1,5 @@ +import uuid + import pytest from apps.integrations.domain import Integration @@ -6,6 +8,8 @@ def test_integration_model(): integration_data = { "integration_type": "LORIS", + "applet_id": uuid.UUID("00000000-0000-0000-0000-000000000000"), + "configuration": {"hostname": "hostname", "username": "user", "password": "password", "project": "project"}, } item = Integration(**integration_data) assert item.integration_type == integration_data["integration_type"] @@ -14,6 +18,8 @@ def test_integration_model(): def test_integration_model_error(): integration_data = { "integration_type": "MORRIS", + "applet_id": uuid.UUID("00000000-0000-0000-0000-000000000000"), + "configuration": {"hostname": "hostname", "username": "user", "password": "password", "project": "project"}, } with pytest.raises(Exception): Integration(**integration_data) diff --git a/src/apps/workspaces/errors.py b/src/apps/workspaces/errors.py index 1883c1751a6..21e93938a13 100644 --- a/src/apps/workspaces/errors.py +++ b/src/apps/workspaces/errors.py @@ -126,6 +126,10 @@ class IntegrationsCreateAccessDenied(ValidationError): message = _("Access denied to create new integrations of type `{type}` on applet `{applet_id}`") +class IntegrationsRetrieveAccessDenied(ValidationError): + message = _("Access denied to retrieve integrations of type `{type}` on applet `{applet_id}`") + + class WorkspaceNotFoundError(Exception): ... diff --git a/src/apps/workspaces/service/check_access.py b/src/apps/workspaces/service/check_access.py index 0cd918f2d89..cb345b53f5f 100644 --- a/src/apps/workspaces/service/check_access.py +++ b/src/apps/workspaces/service/check_access.py @@ -18,6 +18,7 @@ AppletInviteAccessDenied, AppletSetScheduleAccessDenied, IntegrationsCreateAccessDenied, + IntegrationsRetrieveAccessDenied, PublishConcealAccessDenied, TransferOwnershipAccessDenied, WorkspaceAccessDenied, @@ -253,7 +254,7 @@ async def check_subject_subject_access(self, applet_id: uuid.UUID, subject_id: u async def check_answer_publishing_access(self, applet_id: uuid.UUID): await self._check_applet_roles(applet_id, [Role.OWNER]) - async def check_integrations_create_access(self, applet_id: uuid.UUID, type: str): + async def check_integrations_access(self, applet_id: uuid.UUID, type: str): access = await UserAppletAccessCRUD(self.session).get_by_roles( self.user_id, applet_id, [Role.MANAGER, Role.OWNER] )