diff --git a/src/openstack_mcp_server/tools/identity_tools.py b/src/openstack_mcp_server/tools/identity_tools.py index fe08e25..5432a29 100644 --- a/src/openstack_mcp_server/tools/identity_tools.py +++ b/src/openstack_mcp_server/tools/identity_tools.py @@ -30,6 +30,7 @@ def register_tools(self, mcp: FastMCP): mcp.tool()(self.get_project) mcp.tool()(self.create_project) mcp.tool()(self.delete_project) + mcp.tool()(self.update_project) def get_regions(self) -> list[Region]: """ @@ -319,3 +320,49 @@ def delete_project(self, id: str) -> None: conn = get_openstack_conn() conn.identity.delete_project(project=id, ignore_missing=False) return None + + def update_project( + self, + id: str, + name: str | None = None, + description: str | None = None, + is_enabled: bool | None = None, + domain_id: str | None = None, + parent_id: str | None = None, + ) -> Project: + """ + Update a project. + + :param id: The ID of the project. + :param name: The name of the project. + :param description: The description of the project. + :param is_enabled: Whether the project is enabled. + :param domain_id: The ID of the domain. + :param parent_id: The ID of the parent project. + + :return: The updated Project object. + """ + conn = get_openstack_conn() + + args = {} + if name is not None: + args["name"] = name + if description is not None: + args["description"] = description + if is_enabled is not None: + args["is_enabled"] = is_enabled + if domain_id is not None: + args["domain_id"] = domain_id + if parent_id is not None: + args["parent_id"] = parent_id + + updated_project = conn.identity.update_project(project=id, **args) + + return Project( + id=updated_project.id, + name=updated_project.name, + description=updated_project.description, + is_enabled=updated_project.is_enabled, + domain_id=updated_project.domain_id, + parent_id=updated_project.parent_id, + ) diff --git a/tests/tools/test_identity_tools.py b/tests/tools/test_identity_tools.py index 94fa649..b78e7ce 100644 --- a/tests/tools/test_identity_tools.py +++ b/tests/tools/test_identity_tools.py @@ -983,3 +983,73 @@ def test_delete_project_not_found(self, mock_get_openstack_conn_identity): project="project1111111111111111111111111", ignore_missing=False, ) + + def test_update_project_success(self, mock_get_openstack_conn_identity): + """Test updating a identity project successfully.""" + mock_conn = mock_get_openstack_conn_identity + + # Create mock project object + mock_project = Mock() + mock_project.id = "project1111111111111111111111111" + mock_project.name = "ProjectOne" + mock_project.description = "Project One description" + mock_project.is_enabled = True + mock_project.domain_id = "domain1111111111111111111111111" + mock_project.parent_id = "parentproject1111111111111111111" + + # Configure mock project.update_project() + mock_conn.identity.update_project.return_value = mock_project + + # Test update_project() + identity_tools = self.get_identity_tools() + result = identity_tools.update_project( + id="project1111111111111111111111111", + name="ProjectOne", + description="Project One description", + is_enabled=True, + domain_id="domain1111111111111111111111111", + parent_id="parentproject1111111111111111111", + ) + + # Verify results + assert result == Project( + id="project1111111111111111111111111", + name="ProjectOne", + description="Project One description", + is_enabled=True, + domain_id="domain1111111111111111111111111", + parent_id="parentproject1111111111111111111", + ) + + # Verify mock calls + mock_conn.identity.update_project.assert_called_once_with( + project="project1111111111111111111111111", + name="ProjectOne", + description="Project One description", + is_enabled=True, + domain_id="domain1111111111111111111111111", + parent_id="parentproject1111111111111111111", + ) + + def test_update_project_empty_id(self, mock_get_openstack_conn_identity): + """Test updating a identity project with an empty ID.""" + mock_conn = mock_get_openstack_conn_identity + + # Configure mock to raise BadRequestException + mock_conn.identity.update_project.side_effect = ( + exceptions.BadRequestException( + "Field required", + ) + ) + + # Test update_project() + identity_tools = self.get_identity_tools() + + with pytest.raises( + exceptions.BadRequestException, + match="Field required", + ): + identity_tools.update_project(id="") + + # Verify mock calls + mock_conn.identity.update_project.assert_called_once_with(project="")