@@ -23,10 +23,7 @@
diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py index 56a310771..bcb71115e 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py @@ -6,7 +6,6 @@ import sqlalchemy as sa from sqlalchemy import orm -from capellacollab.core import database from capellacollab.projects.toolmodels import models as toolmodels_models from capellacollab.projects.toolmodels.modelsources.t4c import models from capellacollab.settings.modelsources.t4c.repositories import ( @@ -59,10 +58,12 @@ def create_t4c_model( def patch_t4c_model( db: orm.Session, t4c_model: models.DatabaseT4CModel, - patch_model: models.SubmitT4CModel, + repository: repositories_models.DatabaseT4CRepository, + name: str | None = None, ) -> models.DatabaseT4CModel: - database.patch_database_with_pydantic_object(t4c_model, patch_model) - + if name is not None: + t4c_model.name = name + t4c_model.repository = repository db.commit() return t4c_model diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/exceptions.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/exceptions.py index 5a2e21a08..cb6fc9bec 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/exceptions.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/exceptions.py @@ -47,3 +47,39 @@ def __init__(self): ), err_code="T4C_INTEGRATION_USED_IN_PIPELINES", ) + + +class T4CIntegrationWrongCapellaVersion(core_exceptions.BaseError): + def __init__( + self, + t4c_server_name: str, + t4c_repository_name: str, + server_version_name: str, + server_version_id: int, + model_version_name: str, + model_version_id: int, + ): + super().__init__( + status_code=status.HTTP_400_BAD_REQUEST, + title="The TeamForCapella server is not compatible with Capella model", + reason=( + f"The repository '{t4c_repository_name}' of the TeamForCapella server '{t4c_server_name}' " + f"has version {server_version_name} (ID {server_version_id}), " + f"but the model has version {model_version_name} (ID {model_version_id}). " + "Make sure that those versions match or are compatible with each other." + ), + err_code="T4C_INTEGRATION_WRONG_CAPELLA_VERSION", + ) + + +class T4CIntegrationVersionRequired(core_exceptions.BaseError): + def __init__(self, toolmodel_slug: str): + super().__init__( + status_code=status.HTTP_400_BAD_REQUEST, + title="The Capella model requires a version to proceed", + reason=( + f"To link a TeamForCapella repository, the Capella model '{toolmodel_slug}' has to have a version. " + "Please add a version first." + ), + err_code="T4C_INTEGRATION_NO_VERSION", + ) diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py index 2dbbd53db..b1713da78 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py @@ -54,6 +54,12 @@ class SubmitT4CModel(core_pydantic.BaseModel): t4c_repository_id: int +class PatchT4CModel(core_pydantic.BaseModel): + name: str | None = None + t4c_instance_id: int | None = None + t4c_repository_id: int | None = None + + class SimpleT4CModel(core_pydantic.BaseModel): project_name: str repository_name: str diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py index 349d2c0ef..981f523fa 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py @@ -15,14 +15,14 @@ from capellacollab.projects.toolmodels.backups import crud as backups_crud from capellacollab.projects.users import models as projects_users_models from capellacollab.settings.modelsources.t4c import ( - injectables as settings_t4c_injecatbles, + injectables as settings_t4c_injectables, ) from capellacollab.settings.modelsources.t4c.repositories import ( injectables as settings_t4c_repositories_injectables, ) from capellacollab.users import models as users_models -from . import crud, exceptions, injectables, models +from . import crud, exceptions, injectables, models, util router = fastapi.APIRouter( dependencies=[ @@ -87,7 +87,7 @@ def create_t4c_model( ), db: orm.Session = fastapi.Depends(database.get_db), ): - instance = settings_t4c_injecatbles.get_existing_unarchived_instance( + instance = settings_t4c_injectables.get_existing_unarchived_instance( body.t4c_instance_id, db ) repository = ( @@ -95,6 +95,11 @@ def create_t4c_model( body.t4c_repository_id, db, instance ) ) + + util.verify_compatibility_of_model_and_server( + model.name, model.version, repository + ) + try: return crud.create_t4c_model(db, model, repository, body.name) except exc.IntegrityError: @@ -118,14 +123,31 @@ def create_t4c_model( ], ), ) -def edit_t4c_model( - body: models.SubmitT4CModel, +def update_t4c_model( + body: models.PatchT4CModel, t4c_model: models.DatabaseT4CModel = fastapi.Depends( injectables.get_existing_t4c_model ), db: orm.Session = fastapi.Depends(database.get_db), ): - return crud.patch_t4c_model(db, t4c_model, body) + if body.t4c_instance_id is not None: + instance = settings_t4c_injectables.get_existing_unarchived_instance( + body.t4c_instance_id, db + ) + else: + instance = t4c_model.repository.instance + + repository = ( + settings_t4c_repositories_injectables.get_existing_t4c_repository( + body.t4c_repository_id or t4c_model.repository.id, db, instance + ) + ) + + util.verify_compatibility_of_model_and_server( + t4c_model.model.name, t4c_model.model.version, repository + ) + + return crud.patch_t4c_model(db, t4c_model, repository, body.name) @router.delete( diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py new file mode 100644 index 000000000..d78af86c3 --- /dev/null +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from capellacollab.settings.modelsources.t4c.repositories import ( + models as t4c_repository_models, +) +from capellacollab.tools import models as tools_models + +from . import exceptions + + +def verify_compatibility_of_model_and_server( + model_name: str, + model_version: tools_models.DatabaseVersion | None, + t4c_repository: t4c_repository_models.DatabaseT4CRepository, +): + server = t4c_repository.instance + if model_version is None: + raise exceptions.T4CIntegrationVersionRequired(model_name) + + if ( + t4c_repository.instance.version.id + not in model_version.config.compatible_versions + [model_version.id] + ): + raise exceptions.T4CIntegrationWrongCapellaVersion( + server.name, + t4c_repository.name, + server.version.name, + server.version.id, + model_version.name, + model_version.id, + ) diff --git a/backend/capellacollab/projects/toolmodels/routes.py b/backend/capellacollab/projects/toolmodels/routes.py index 325c509ec..61b7817f3 100644 --- a/backend/capellacollab/projects/toolmodels/routes.py +++ b/backend/capellacollab/projects/toolmodels/routes.py @@ -5,13 +5,14 @@ import fastapi import slugify -from sqlalchemy import exc, orm +from sqlalchemy import orm from capellacollab.core import database from capellacollab.core import exceptions as core_exceptions from capellacollab.core.authentication import injectables as auth_injectables from capellacollab.projects import injectables as projects_injectables from capellacollab.projects import models as projects_models +from capellacollab.projects.toolmodels.modelsources.t4c import util as t4c_util from capellacollab.projects.users import models as projects_users_models from capellacollab.tools import injectables as tools_injectables from capellacollab.users import injectables as users_injectables @@ -85,14 +86,13 @@ def create_new_tool_model( if tool.integrations.jupyter: configuration["workspace"] = str(uuid.uuid4()) - try: - model = crud.create_model( - db, project, new_model, tool, configuration=configuration - ) - except exc.IntegrityError: - raise exceptions.ToolModelAlreadyExistsError( - project.slug, slugify.slugify(new_model.name) - ) + slug = slugify.slugify(new_model.name) + if crud.get_model_by_slugs(db, project.slug, slug): + raise exceptions.ToolModelAlreadyExistsError(project.slug, slug) + + model = crud.create_model( + db, project, new_model, tool, configuration=configuration + ) if tool.integrations.jupyter: workspace.create_shared_workspace( @@ -153,6 +153,11 @@ def patch_tool_model( else model.nature ) + for t4c_model in model.t4c_models: + t4c_util.verify_compatibility_of_model_and_server( + model.name, version, t4c_model.repository + ) + if body.project_slug: new_project = determine_new_project_to_move_model( body.project_slug, db, user diff --git a/backend/capellacollab/settings/modelsources/t4c/models.py b/backend/capellacollab/settings/modelsources/t4c/models.py index 0d1294257..478a8b2d9 100644 --- a/backend/capellacollab/settings/modelsources/t4c/models.py +++ b/backend/capellacollab/settings/modelsources/t4c/models.py @@ -129,7 +129,6 @@ class PatchT4CInstance(core_pydantic.BaseModel): username: str | None = None password: str | None = None protocol: Protocol | None = None - version_id: int | None = None is_archived: bool | None = None _validate_rest_api_url = pydantic.field_validator("rest_api")( diff --git a/backend/tests/projects/toolmodels/fixtures.py b/backend/tests/projects/toolmodels/fixtures.py index ebfeeba84..84f9f4e5f 100644 --- a/backend/tests/projects/toolmodels/fixtures.py +++ b/backend/tests/projects/toolmodels/fixtures.py @@ -7,11 +7,6 @@ import capellacollab.projects.models as projects_models import capellacollab.projects.toolmodels.crud as toolmodels_crud import capellacollab.projects.toolmodels.models as toolmodels_models -import capellacollab.projects.toolmodels.modelsources.git.crud as project_git_crud -import capellacollab.projects.toolmodels.modelsources.git.models as project_git_models -import capellacollab.projects.toolmodels.modelsources.t4c.crud as models_t4c_crud -import capellacollab.projects.toolmodels.modelsources.t4c.models as models_t4c_models -import capellacollab.settings.modelsources.t4c.repositories.models as settings_t4c_repositories_models import capellacollab.tools.models as tools_models @@ -45,30 +40,3 @@ def fixture_jupyter_model( tool=jupyter_tool, configuration={"workspace": "test"}, ) - - -@pytest.fixture(name="git_model") -def fixture_git_model( - db: orm.Session, capella_model: toolmodels_models.DatabaseToolModel -) -> project_git_models.DatabaseGitModel: - git_model = project_git_models.PostGitModel( - path="https://example.com/test/project", - entrypoint="test/test.aird", - revision="main", - username="user", - password="password", - ) - return project_git_crud.add_git_model_to_capellamodel( - db, capella_model, git_model - ) - - -@pytest.fixture(name="t4c_model") -def fixture_t4c_model( - db: orm.Session, - capella_model: toolmodels_models.DatabaseToolModel, - t4c_repository: settings_t4c_repositories_models.DatabaseT4CRepository, -) -> models_t4c_models.DatabaseT4CModel: - return models_t4c_crud.create_t4c_model( - db, capella_model, t4c_repository, "default" - ) diff --git a/backend/tests/projects/toolmodels/modelsources/fixtures.py b/backend/tests/projects/toolmodels/modelsources/fixtures.py new file mode 100644 index 000000000..4d1299d03 --- /dev/null +++ b/backend/tests/projects/toolmodels/modelsources/fixtures.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from sqlalchemy import orm + +import capellacollab.projects.toolmodels.models as toolmodels_models +import capellacollab.projects.toolmodels.modelsources.git.crud as project_git_crud +import capellacollab.projects.toolmodels.modelsources.git.models as project_git_models +import capellacollab.projects.toolmodels.modelsources.t4c.crud as models_t4c_crud +import capellacollab.projects.toolmodels.modelsources.t4c.models as models_t4c_models +import capellacollab.settings.modelsources.t4c.repositories.models as settings_t4c_repositories_models + + +@pytest.fixture(name="t4c_model") +def fixture_t4c_model( + db: orm.Session, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_repository: settings_t4c_repositories_models.DatabaseT4CRepository, +) -> models_t4c_models.DatabaseT4CModel: + return models_t4c_crud.create_t4c_model( + db, capella_model, t4c_repository, "default" + ) + + +@pytest.fixture(name="git_model") +def fixture_git_model( + db: orm.Session, capella_model: toolmodels_models.DatabaseToolModel +) -> project_git_models.DatabaseGitModel: + git_model = project_git_models.PostGitModel( + path="https://example.com/test/project", + entrypoint="test/test.aird", + revision="main", + username="user", + password="password", + ) + return project_git_crud.add_git_model_to_capellamodel( + db, capella_model, git_model + ) diff --git a/backend/tests/projects/toolmodels/modelsources/test_t4c_routes.py b/backend/tests/projects/toolmodels/modelsources/test_t4c_routes.py new file mode 100644 index 000000000..bf3d0b17d --- /dev/null +++ b/backend/tests/projects/toolmodels/modelsources/test_t4c_routes.py @@ -0,0 +1,252 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + + +import pytest +from fastapi import testclient +from sqlalchemy import orm + +import capellacollab.projects.models as projects_models +import capellacollab.settings.modelsources.t4c.repositories.crud as t4c_repositories_crud +import capellacollab.settings.modelsources.t4c.repositories.models as t4c_repositories_models +from capellacollab.projects.toolmodels import crud as toolmodels_crud +from capellacollab.projects.toolmodels import models as toolmodels_models +from capellacollab.projects.toolmodels.modelsources.t4c import crud as t4c_crud +from capellacollab.projects.toolmodels.modelsources.t4c import ( + models as t4c_models, +) +from capellacollab.settings.modelsources.t4c import crud as t4c_servers_crud +from capellacollab.settings.modelsources.t4c import ( + models as t4c_servers_models, +) +from capellacollab.settings.modelsources.t4c.repositories import ( + models as t4c_repositories_models, +) +from capellacollab.tools import models as tools_models + + +@pytest.mark.usefixtures("project_manager") +def test_list_t4c_models( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.get( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c" + ) + + assert response.status_code == 200 + assert len(response.json()) == 1 + assert response.json()[0]["name"] == t4c_model.name + + +@pytest.mark.usefixtures("project_manager") +def test_get_t4c_model( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.get( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}" + ) + + assert response.status_code == 200 + assert response.json()["name"] == t4c_model.name + assert ( + response.json()["repository"]["instance"]["id"] + == t4c_model.repository.instance.id + ) + + +@pytest.mark.usefixtures("admin") +def test_create_t4c_model( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + t4c_repository: t4c_repositories_models.DatabaseT4CRepository, + capella_model: toolmodels_models.DatabaseToolModel, +): + response = client.post( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c", + json={ + "name": "project", + "t4c_instance_id": t4c_repository.instance.id, + "t4c_repository_id": t4c_repository.id, + }, + ) + + assert response.status_code == 200 + assert response.json()["name"] == "project" + assert response.json()["repository"]["id"] == t4c_repository.id + assert ( + response.json()["repository"]["instance"]["id"] + == t4c_repository.instance.id + ) + + +@pytest.mark.usefixtures("admin", "t4c_model") +def test_create_t4c_model_twice_fails( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + t4c_repository: t4c_repositories_models.DatabaseT4CRepository, + capella_model: toolmodels_models.DatabaseToolModel, +): + response = client.post( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c", + json={ + "name": "default", + "t4c_instance_id": t4c_repository.instance.id, + "t4c_repository_id": t4c_repository.id, + }, + ) + + assert response.status_code == 409 + assert ( + response.json()["detail"]["err_code"] + == "T4C_INTEGRATION_ALREADY_EXISTS" + ) + + +@pytest.mark.usefixtures("admin") +def test_create_t4c_model_incompatible_version( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + t4c_repository: t4c_repositories_models.DatabaseT4CRepository, + capella_tool: tools_models.DatabaseTool, + db: orm.Session, + tool_version: tools_models.DatabaseVersion, +): + model = toolmodels_crud.create_model( + db, + project, + toolmodels_models.PostToolModel( + name="test", description="test", tool_id=capella_tool.id + ), + capella_tool, + tool_version, + ) + response = client.post( + f"/api/v1/projects/{project.slug}/models/{model.slug}/modelsources/t4c", + json={ + "name": "project", + "t4c_instance_id": t4c_repository.instance.id, + "t4c_repository_id": t4c_repository.id, + }, + ) + + assert response.status_code == 400 + assert ( + response.json()["detail"]["err_code"] + == "T4C_INTEGRATION_WRONG_CAPELLA_VERSION" + ) + + +@pytest.mark.usefixtures("admin") +def test_create_t4c_model_without_version( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + t4c_repository: t4c_repositories_models.DatabaseT4CRepository, + db: orm.Session, + capella_tool: tools_models.DatabaseTool, +): + model = toolmodels_crud.create_model( + db, + project, + toolmodels_models.PostToolModel( + name="test", description="test", tool_id=capella_tool.id + ), + capella_tool, + ) + response = client.post( + f"/api/v1/projects/{project.slug}/models/{model.slug}/modelsources/t4c", + json={ + "name": "project", + "t4c_instance_id": t4c_repository.instance.id, + "t4c_repository_id": t4c_repository.id, + }, + ) + + assert response.status_code == 400 + assert ( + response.json()["detail"]["err_code"] == "T4C_INTEGRATION_NO_VERSION" + ) + + +@pytest.mark.usefixtures("admin") +def test_change_server_of_t4c_model( + db: orm.Session, + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, + capella_tool_version: tools_models.DatabaseVersion, +): + server = t4c_servers_models.DatabaseT4CInstance( + name="test server 2", + license="lic", + host="localhost", + usage_api="http://localhost:8086", + rest_api="http://localhost:8080/api/v1.0", + username="user", + password="pass", + protocol=t4c_servers_models.Protocol.tcp, + version=capella_tool_version, + ) + db_server = t4c_servers_crud.create_t4c_instance(db, server) + + second_t4c_repository = t4c_repositories_crud.create_t4c_repository( + db=db, repo_name="test2", instance=db_server + ) + + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}", + json={ + "t4c_instance_id": db_server.id, + "t4c_repository_id": second_t4c_repository.id, + }, + ) + + assert response.status_code == 200 + assert response.json()["name"] == t4c_model.name + assert response.json()["repository"]["id"] == second_t4c_repository.id + assert ( + response.json()["repository"]["instance"]["id"] + == second_t4c_repository.instance.id + ) + + +@pytest.mark.usefixtures("admin") +def test_patch_name_of_t4c_model( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}", + json={"name": "new_default"}, + ) + + assert response.status_code == 200 + assert response.json()["name"] == "new_default" + assert ( + response.json()["repository"]["instance"]["id"] + == t4c_model.repository.instance.id + ) + + +@pytest.mark.usefixtures("admin") +def test_unlink_t4c_model( + db: orm.Session, + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.delete( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}", + ) + + assert response.status_code == 204 + assert t4c_crud.get_t4c_model_by_id(db, t4c_model.id) is None diff --git a/backend/tests/projects/toolmodels/test_toolmodel_routes.py b/backend/tests/projects/toolmodels/test_toolmodel_routes.py index 9ed96f258..ee3d8c8a0 100644 --- a/backend/tests/projects/toolmodels/test_toolmodel_routes.py +++ b/backend/tests/projects/toolmodels/test_toolmodel_routes.py @@ -2,46 +2,23 @@ # SPDX-License-Identifier: Apache-2.0 -from unittest import mock +from uuid import uuid4 import pytest from fastapi import testclient from sqlalchemy import orm -from capellacollab.__main__ import app -from capellacollab.projects import injectables as projects_injectables +import capellacollab.projects.users.crud as projects_users_crud +import capellacollab.projects.users.models as projects_users_models +from capellacollab.projects import crud as projects_crud from capellacollab.projects import models as projects_models -from capellacollab.projects.toolmodels import ( - injectables as toolmodels_injectables, -) from capellacollab.projects.toolmodels import models as toolmodels_models +from capellacollab.tools import crud as tools_crud +from capellacollab.tools import models as tools_models from capellacollab.users import crud as users_crud from capellacollab.users import models as users_models -@pytest.fixture(name="override_dependency") -def fixture_override_dependency(): - mock_project = mock.Mock(name="DatabaseProject") - - mock_model = mock.Mock(name="DatabaseModel") - mock_model.slug = "any-slug" - mock_model.tool = mock.Mock(name="tool") - - app.dependency_overrides[projects_injectables.get_existing_project] = ( - lambda: mock_project - ) - app.dependency_overrides[ - toolmodels_injectables.get_existing_capella_model - ] = lambda: mock_model - - yield - - del app.dependency_overrides[projects_injectables.get_existing_project] - del app.dependency_overrides[ - toolmodels_injectables.get_existing_capella_model - ] - - def test_rename_toolmodel_successful( capella_model: toolmodels_models.DatabaseToolModel, project: projects_models.DatabaseProject, @@ -66,9 +43,11 @@ def test_rename_toolmodel_successful( assert "new-name" in response.text -@pytest.mark.usefixtures("override_dependency") def test_rename_toolmodel_where_name_already_exists( client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + jupyter_model: toolmodels_models.DatabaseToolModel, executor_name: str, db: orm.Session, ): @@ -76,22 +55,13 @@ def test_rename_toolmodel_where_name_already_exists( db, executor_name, executor_name, None, users_models.Role.ADMIN ) - with mock.patch( - "capellacollab.projects.toolmodels.crud.get_model_by_slugs", - autospec=True, - ) as mock_get_model_by_slugs: - mock_get_model_by_slugs.return_value = "anything" - - response = client.patch( - "/api/v1/projects/any/models/any", - json={"name": "new-name", "version_id": -1, "nature_id": -1}, - ) + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", + json={"name": jupyter_model.name, "version_id": -1, "nature_id": -1}, + ) - assert response.status_code == 409 - assert ( - response.json()["detail"]["err_code"] == "TOOLMODEL_ALREADY_EXISTS" - ) - mock_get_model_by_slugs.assert_called_once() + assert response.status_code == 409 + assert response.json()["detail"]["err_code"] == "TOOLMODEL_ALREADY_EXISTS" def test_update_toolmodel_order_successful( @@ -112,3 +82,76 @@ def test_update_toolmodel_order_successful( assert response.status_code == 200 assert "1" in response.text + + +def test_move_toolmodel( + project: projects_models.DatabaseProject, + project_manager: users_models.DatabaseUser, + capella_model: toolmodels_models.ToolModel, + client: testclient.TestClient, + db: orm.Session, +): + second_project = projects_crud.create_project(db, str(uuid4())) + projects_users_crud.add_user_to_project( + db, + project=second_project, + user=project_manager, + role=projects_users_models.ProjectUserRole.MANAGER, + permission=projects_users_models.ProjectUserPermission.WRITE, + ) + + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", + json={"project_slug": second_project.slug}, + ) + assert response.status_code == 200 + + response = client.get( + f"/api/v1/projects/{second_project.slug}/models/{capella_model.slug}" + ) + assert response.status_code == 200 + + +@pytest.mark.usefixtures("project_manager") +def test_move_toolmodel_non_project_member( + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.ToolModel, + client: testclient.TestClient, + db: orm.Session, +): + second_project = projects_crud.create_project(db, str(uuid4())) + + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", + json={"project_slug": second_project.slug}, + ) + assert response.status_code == 403 + + +@pytest.mark.usefixtures("t4c_model", "project_manager") +def test_patch_toolmodel_version_with_invalid_t4c_link( + db: orm.Session, + client: testclient.TestClient, + capella_model: toolmodels_models.DatabaseToolModel, + capella_tool: tools_models.DatabaseTool, +): + version = tools_crud.create_version( + db, + capella_tool, + tools_models.CreateToolVersion( + name="1.0.0", config=tools_models.ToolVersionConfiguration() + ), + ) + + response = client.patch( + f"/api/v1/projects/{capella_model.project.slug}/models/{capella_model.slug}", + json={ + "version_id": version.id, + }, + ) + + assert response.status_code == 400 + assert ( + response.json()["detail"]["err_code"] + == "T4C_INTEGRATION_WRONG_CAPELLA_VERSION" + ) diff --git a/backend/tests/projects/toolmodels/test_toolmodels.py b/backend/tests/projects/toolmodels/test_toolmodels.py deleted file mode 100644 index 6b0850b58..000000000 --- a/backend/tests/projects/toolmodels/test_toolmodels.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from uuid import uuid4 - -import pytest -from fastapi import testclient -from sqlalchemy import orm - -import capellacollab.projects.users.crud as projects_users_crud -import capellacollab.projects.users.models as projects_users_models -from capellacollab.projects import crud as projects_crud -from capellacollab.projects import models as projects_models -from capellacollab.projects.toolmodels import models as toolmodels_models -from capellacollab.users import models as users_models - - -def test_move_toolmodel( - project: projects_models.DatabaseProject, - project_manager: users_models.DatabaseUser, - capella_model: toolmodels_models.ToolModel, - client: testclient.TestClient, - db: orm.Session, -): - second_project = projects_crud.create_project(db, str(uuid4())) - projects_users_crud.add_user_to_project( - db, - project=second_project, - user=project_manager, - role=projects_users_models.ProjectUserRole.MANAGER, - permission=projects_users_models.ProjectUserPermission.WRITE, - ) - - response = client.patch( - f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", - json={"project_slug": second_project.slug}, - ) - assert response.status_code == 200 - - response = client.get( - f"/api/v1/projects/{second_project.slug}/models/{capella_model.slug}" - ) - assert response.status_code == 200 - - -@pytest.mark.usefixtures("project_manager") -def test_move_toolmodel_non_project_member( - project: projects_models.DatabaseProject, - capella_model: toolmodels_models.ToolModel, - client: testclient.TestClient, - db: orm.Session, -): - second_project = projects_crud.create_project(db, str(uuid4())) - - response = client.patch( - f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", - json={"project_slug": second_project.slug}, - ) - assert response.status_code == 403 diff --git a/backend/tests/settings/teamforcapella/fixtures.py b/backend/tests/settings/teamforcapella/fixtures.py index eef937409..4033c48b7 100644 --- a/backend/tests/settings/teamforcapella/fixtures.py +++ b/backend/tests/settings/teamforcapella/fixtures.py @@ -14,7 +14,7 @@ @pytest.fixture(name="t4c_instance") def fixture_t4c_instance( db: orm.Session, - tool_version: tools_models.DatabaseVersion, + capella_tool_version: tools_models.DatabaseVersion, ) -> t4c_models.DatabaseT4CInstance: server = t4c_models.DatabaseT4CInstance( name="test server", @@ -25,7 +25,7 @@ def fixture_t4c_instance( username="user", password="pass", protocol=t4c_models.Protocol.tcp, - version=tool_version, + version=capella_tool_version, ) return t4c_crud.create_t4c_instance(db, server) diff --git a/backend/tests/tools/fixtures.py b/backend/tests/tools/fixtures.py index 452db8eba..711617ffb 100644 --- a/backend/tests/tools/fixtures.py +++ b/backend/tests/tools/fixtures.py @@ -60,20 +60,6 @@ def fixture_capella_tool(db: orm.Session) -> tools_models.DatabaseTool: return capella_tool -@pytest.fixture(name="capella_tool_version", params=["6.0.0"]) -def fixture_capella_tool_version( - db: orm.Session, - capella_tool: tools_models.DatabaseTool, - request: pytest.FixtureRequest, -) -> tools_models.DatabaseVersion: - capella_tool_version = tools_crud.get_version_by_tool_id_version_name( - db, capella_tool.id, request.param - ) - assert capella_tool_version - - return capella_tool_version - - @pytest.fixture(name="jupyter_tool") def fixture_jupyter_tool(db: orm.Session) -> tools_models.DatabaseTool: return database_migration.create_jupyter_tool(db) diff --git a/backend/tests/tools/versions/fixtures.py b/backend/tests/tools/versions/fixtures.py index cd8ac96af..254098500 100644 --- a/backend/tests/tools/versions/fixtures.py +++ b/backend/tests/tools/versions/fixtures.py @@ -9,6 +9,20 @@ from capellacollab.tools import models as tools_models +@pytest.fixture(name="capella_tool_version", params=["6.0.0"]) +def fixture_capella_tool_version( + db: orm.Session, + capella_tool: tools_models.DatabaseTool, + request: pytest.FixtureRequest, +) -> tools_models.DatabaseVersion: + capella_tool_version = tools_crud.get_version_by_tool_id_version_name( + db, capella_tool.id, request.param + ) + assert capella_tool_version + + return capella_tool_version + + @pytest.fixture(name="tool_version") def fixture_tool_version( db: orm.Session, diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 250ae5c26..f647c55e4 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -145,7 +145,91 @@ export const routes: Routes = [ redirect: (data: Data) => `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources`, }, - component: ModelDetailComponent, + children: [ + { + path: '', + data: { + breadcrumb: undefined, + }, + component: ModelDetailComponent, + }, + { + path: 'git-model', + data: { + breadcrumb: 'Git Repositories', + }, + children: [ + { + path: 'create', + data: { + breadcrumb: 'Add', + redirect: (data: Data) => + `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources/git-model/create`, + }, + component: ManageGitModelComponent, + }, + { + path: ':git-model', + data: { + breadcrumb: (data: Data) => + data.gitModel?.id || '...', + redirect: (data: Data) => + data.gitModel?.id + ? `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources/git-model/${data.gitModel?.id}` + : undefined, + }, + component: ManageGitModelComponent, + }, + ], + }, + { + path: 't4c-model', + data: { + breadcrumb: 'T4C Repositories', + }, + children: [ + { + path: 'create-existing', + data: { + breadcrumb: 'Link Existing Repository', + redirect: (data: Data) => + `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources/t4c-model/create-existing`, + }, + component: ManageT4CModelComponent, + }, + { + path: 'create-new', + data: { + breadcrumb: 'Create New Repository', + redirect: (data: Data) => + `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources/t4c-model/create-new`, + }, + component: CreateT4cModelNewRepositoryComponent, + }, + { + path: ':t4c_model_id', + data: { + breadcrumb: (data: Data) => + data.t4cModel?.id || '...', + redirect: (data: Data) => + data.t4cModel?.id + ? `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources/t4c-model/${data.t4cModel?.id}` + : undefined, + }, + component: T4CModelWrapperComponent, + children: [ + { + path: '', + data: { + breadcrumb: undefined, + }, + component: ManageT4CModelComponent, + }, + ], + }, + ], + }, + ], }, { path: 'metadata', @@ -220,83 +304,6 @@ export const routes: Routes = [ }, ], }, - { - path: 'git-model', - data: { - breadcrumb: 'Model Sources', - redirect: (data: Data) => - `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources`, - }, - children: [ - { - path: 'create', - data: { - breadcrumb: 'Create Git Model', - redirect: (data: Data) => - `/project/${data.project?.slug}/model/${data.model?.slug}/git-model/create`, - }, - component: ManageGitModelComponent, - }, - { - path: ':git-model', - data: { - breadcrumb: (data: Data) => - data.gitModel?.id || '...', - redirect: (data: Data) => - data.gitModel?.id - ? `/project/${data.project?.slug}/model/${data.model?.slug}/git-model/${data.gitModel?.id}` - : undefined, - }, - component: ManageGitModelComponent, - }, - ], - }, - { - path: 't4c-model', - data: { - breadcrumb: 'Model Sources', - redirect: (data: Data) => - `/project/${data.project?.slug}/model/${data.model?.slug}/modelsources`, - }, - children: [ - { - path: 'create-existing', - data: { - breadcrumb: 'Create T4C Model', - redirect: (data: Data) => - `/project/${data.project?.slug}/model/${data.model?.slug}/t4c-model/create-existing`, - }, - component: ManageT4CModelComponent, - }, - { - path: 'create-new', - data: { - breadcrumb: 'Create T4C Model', - redirect: (data: Data) => - `/project/${data.project?.slug}/model/${data.model?.slug}/t4c-model/create-new`, - }, - component: CreateT4cModelNewRepositoryComponent, - }, - { - path: ':t4c_model_id', - data: { - breadcrumb: (data: Data) => - data.t4cModel?.id || '...', - redirect: (data: Data) => - data.t4cModel?.id - ? `/project/${data.project?.slug}/model/${data.model?.slug}/t4c-model/${data.t4cModel?.id}` - : undefined, - }, - component: T4CModelWrapperComponent, - children: [ - { - path: '', - component: ManageT4CModelComponent, - }, - ], - }, - ], - }, ], }, ], diff --git a/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.css b/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.css deleted file mode 100644 index cb13f1cd1..000000000 --- a/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.css +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -::ng-deep .skeleton-loader.circle { - margin: 0px !important; -} - -.margin { - margin-bottom: 18.813px; -} diff --git a/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.html b/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.html index cbf5ff951..8ba9d65e9 100644 --- a/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.html +++ b/frontend/src/app/helpers/skeleton-loaders/form-field-skeleton-loader/form-field-skeleton-loader.component.html @@ -2,13 +2,14 @@ ~ SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors ~ SPDX-License-Identifier: Apache-2.0 --> -