From 7e314a1b4c6b97a470b9636a05378b956f2c8758 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Mon, 22 Jan 2024 15:33:23 +0100 Subject: [PATCH 1/2] fix(api): fix saving merge request approval rules Closes #2548 --- gitlab/v4/objects/merge_request_approvals.py | 60 ++------------------ 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/gitlab/v4/objects/merge_request_approvals.py b/gitlab/v4/objects/merge_request_approvals.py index ce11d7f3c..5f5d70005 100644 --- a/gitlab/v4/objects/merge_request_approvals.py +++ b/gitlab/v4/objects/merge_request_approvals.py @@ -1,4 +1,4 @@ -from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union +from typing import Any, cast, List, Optional, TYPE_CHECKING, Union from gitlab import exceptions as exc from gitlab.base import RESTManager, RESTObject @@ -132,42 +132,16 @@ def set_approvers( class ProjectMergeRequestApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "name" - id: int - approval_rule_id: int - merge_request_iid: int - - @exc.on_http_error(exc.GitlabUpdateError) - def save(self, **kwargs: Any) -> None: - """Save the changes made to the object to the server. - - The object is updated to match what the server returns. - - Args: - **kwargs: Extra options to send to the server (e.g. sudo) - - Raise: - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request - """ - # There is a mismatch between the name of our id attribute and the put - # REST API name for the project_id, so we override it here. - self.approval_rule_id = self.id - self.merge_request_iid = self._parent_attrs["mr_iid"] - self.id = self._parent_attrs["project_id"] - # save will update self.id with the result from the server, so no need - # to overwrite with what it was before we overwrote it. - SaveMixin.save(self, **kwargs) class ProjectMergeRequestApprovalRuleManager(CRUDMixin, RESTManager): - _path = "/projects/{project_id}/merge_requests/{mr_iid}/approval_rules" + _path = "/projects/{project_id}/merge_requests/{merge_request_iid}/approval_rules" _obj_cls = ProjectMergeRequestApprovalRule - _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + _from_parent_attrs = {"project_id": "project_id", "merge_request_iid": "iid"} _update_attrs = RequiredOptional( required=( "id", "merge_request_iid", - "approval_rule_id", "name", "approvals_required", ), @@ -177,7 +151,7 @@ class ProjectMergeRequestApprovalRuleManager(CRUDMixin, RESTManager): # groups of project-level rule will be copied. The approvals_required # specified will be used. _create_attrs = RequiredOptional( - required=("id", "merge_request_iid", "name", "approvals_required"), + required=("name", "approvals_required"), optional=("approval_project_rule_id", "user_ids", "group_ids"), ) @@ -188,32 +162,6 @@ def get( ProjectMergeRequestApprovalRule, super().get(id=id, lazy=lazy, **kwargs) ) - def create( - self, data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> RESTObject: - """Create a new object. - - Args: - data: Parameters to send to the server to create the - resource - **kwargs: Extra options to send to the server (e.g. sudo or - 'ref_name', 'stage', 'name', 'all') - - Raises: - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request - - Returns: - A new instance of the manage object class build with - the data sent by the server - """ - if TYPE_CHECKING: - assert data is not None - new_data = data.copy() - new_data["id"] = self._from_parent_attrs["project_id"] - new_data["merge_request_iid"] = self._from_parent_attrs["mr_iid"] - return CreateMixin.create(self, new_data, **kwargs) - class ProjectMergeRequestApprovalState(RESTObject): pass From d0c7fcde16a1ea3d6873307d1f5cbd5bc3b9ccd3 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Tue, 30 Jan 2024 11:40:02 +0100 Subject: [PATCH 2/2] fix(test): use different ids for merge request, approval rule, project The original bug was that the merge request identifier was used instead of the approval rule identifier. The test didn't notice that because it used `1` for all identifiers. Make these identifiers different so that a mixup will become apparent. --- .../test_project_merge_request_approvals.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/unit/objects/test_project_merge_request_approvals.py b/tests/unit/objects/test_project_merge_request_approvals.py index 7d63db519..5355dae18 100644 --- a/tests/unit/objects/test_project_merge_request_approvals.py +++ b/tests/unit/objects/test_project_merge_request_approvals.py @@ -10,7 +10,7 @@ import gitlab from gitlab.mixins import UpdateMethod -approval_rule_id = 1 +approval_rule_id = 7 approval_rule_name = "security" approvals_required = 3 user_ids = [5, 50] @@ -96,21 +96,21 @@ def resp_mr_approval_rules(): with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: rsps.add( method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules", + url="http://localhost/api/v4/projects/1/merge_requests/3/approval_rules", json=mr_ars_content, content_type="application/json", status=200, ) rsps.add( method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules/1", + url="http://localhost/api/v4/projects/1/merge_requests/3/approval_rules/7", json=mr_ars_content[0], content_type="application/json", status=200, ) rsps.add( method=responses.GET, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_state", + url="http://localhost/api/v4/projects/1/merge_requests/3/approval_state", json=mr_approval_state_content, content_type="application/json", status=200, @@ -122,7 +122,7 @@ def resp_mr_approval_rules(): rsps.add( method=responses.POST, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules", + url="http://localhost/api/v4/projects/1/merge_requests/3/approval_rules", json=new_mr_ars_content, content_type="application/json", status=200, @@ -139,7 +139,7 @@ def resp_mr_approval_rules(): rsps.add( method=responses.PUT, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules/1", + url="http://localhost/api/v4/projects/1/merge_requests/3/approval_rules/7", json=updated_mr_ars_content, content_type="application/json", status=200, @@ -152,7 +152,7 @@ def resp_delete_mr_approval_rule(): with responses.RequestsMock() as rsps: rsps.add( method=responses.DELETE, - url="http://localhost/api/v4/projects/1/merge_requests/1/approval_rules/1", + url="http://localhost/api/v4/projects/1/merge_requests/3/approval_rules/7", status=204, ) yield rsps @@ -170,7 +170,7 @@ def test_project_approval_manager_update_method_post(project): def test_list_merge_request_approval_rules(project, resp_mr_approval_rules): - approval_rules = project.mergerequests.get(1, lazy=True).approval_rules.list() + approval_rules = project.mergerequests.get(3, lazy=True).approval_rules.list() assert len(approval_rules) == 1 assert approval_rules[0].name == approval_rule_name assert approval_rules[0].id == approval_rule_id @@ -178,12 +178,12 @@ def test_list_merge_request_approval_rules(project, resp_mr_approval_rules): def test_delete_merge_request_approval_rule(project, resp_delete_mr_approval_rule): - merge_request = project.mergerequests.get(1, lazy=True) + merge_request = project.mergerequests.get(3, lazy=True) merge_request.approval_rules.delete(approval_rule_id) def test_update_merge_request_approvals_set_approvers(project, resp_mr_approval_rules): - approvals = project.mergerequests.get(1, lazy=True).approvals + approvals = project.mergerequests.get(3, lazy=True).approvals assert isinstance( approvals, gitlab.v4.objects.merge_request_approvals.ProjectMergeRequestApprovalManager, @@ -203,7 +203,7 @@ def test_update_merge_request_approvals_set_approvers(project, resp_mr_approval_ def test_create_merge_request_approvals_set_approvers(project, resp_mr_approval_rules): - approvals = project.mergerequests.get(1, lazy=True).approvals + approvals = project.mergerequests.get(3, lazy=True).approvals assert isinstance( approvals, gitlab.v4.objects.merge_request_approvals.ProjectMergeRequestApprovalManager, @@ -222,7 +222,7 @@ def test_create_merge_request_approvals_set_approvers(project, resp_mr_approval_ def test_create_merge_request_approval_rule(project, resp_mr_approval_rules): - approval_rules = project.mergerequests.get(1, lazy=True).approval_rules + approval_rules = project.mergerequests.get(3, lazy=True).approval_rules data = { "name": new_approval_rule_name, "approvals_required": new_approval_rule_approvals_required, @@ -238,7 +238,7 @@ def test_create_merge_request_approval_rule(project, resp_mr_approval_rules): def test_update_merge_request_approval_rule(project, resp_mr_approval_rules): - approval_rules = project.mergerequests.get(1, lazy=True).approval_rules + approval_rules = project.mergerequests.get(3, lazy=True).approval_rules ar_1 = approval_rules.list()[0] ar_1.user_ids = updated_approval_rule_user_ids ar_1.approvals_required = updated_approval_rule_approvals_required @@ -250,7 +250,7 @@ def test_update_merge_request_approval_rule(project, resp_mr_approval_rules): def test_get_merge_request_approval_rule(project, resp_mr_approval_rules): - merge_request = project.mergerequests.get(1, lazy=True) + merge_request = project.mergerequests.get(3, lazy=True) approval_rule = merge_request.approval_rules.get(approval_rule_id) assert isinstance( approval_rule, @@ -261,7 +261,7 @@ def test_get_merge_request_approval_rule(project, resp_mr_approval_rules): def test_get_merge_request_approval_state(project, resp_mr_approval_rules): - merge_request = project.mergerequests.get(1, lazy=True) + merge_request = project.mergerequests.get(3, lazy=True) approval_state = merge_request.approval_state.get() assert isinstance( approval_state,