11from pathlib import Path
2- from typing import Any , Optional
2+ from typing import Any
33from unittest import mock
44from unittest .mock import MagicMock , call , patch
55
66import operatorcert .entrypoints .check_permissions as check_permissions
77import pytest
8- from github import UnknownObjectException
8+ from github import GithubException , UnknownObjectException
99from operatorcert .operator_repo import Repo as OperatorRepo
1010from tests .operator_repo import bundle_files , create_files
1111
@@ -133,20 +133,41 @@ def _mock_label(name: str) -> MagicMock:
133133
134134
135135@pytest .mark .parametrize (
136- "is_org_member, is_partner, permission_partner, permission_community, permission_partner_called, permission_community_called, expected_result" ,
136+ "is_org_member, is_partner, owner_can_write, permission_partner, permission_community, permission_partner_called, permission_community_called, expected_result" ,
137137 [
138- pytest .param (True , False , False , False , False , False , True , id = "org member" ),
139138 pytest .param (
140- False , True , True , False , True , False , True , id = "partner - approved "
139+ True , False , False , False , False , False , False , True , id = "org member "
141140 ),
142141 pytest .param (
143- False , True , False , False , True , False , False , id = "partner - denied "
142+ False , True , False , True , False , True , False , True , id = "partner - approved "
144143 ),
145144 pytest .param (
146- False , False , False , True , False , True , True , id = "community - approved "
145+ False , True , False , False , False , True , False , False , id = "partner - denied "
147146 ),
148147 pytest .param (
149- False , False , False , False , False , True , False , id = "community - denied"
148+ False , False , True , False , False , False , True , True , id = "owner - approved"
149+ ),
150+ pytest .param (
151+ False ,
152+ False ,
153+ False ,
154+ False ,
155+ True ,
156+ False ,
157+ True ,
158+ True ,
159+ id = "community - approved" ,
160+ ),
161+ pytest .param (
162+ False ,
163+ False ,
164+ False ,
165+ False ,
166+ False ,
167+ False ,
168+ True ,
169+ False ,
170+ id = "community - denied" ,
150171 ),
151172 ],
152173)
@@ -158,14 +179,17 @@ def _mock_label(name: str) -> MagicMock:
158179)
159180@patch ("operatorcert.entrypoints.check_permissions.OperatorReview.is_partner" )
160181@patch ("operatorcert.entrypoints.check_permissions.OperatorReview.is_org_member" )
182+ @patch ("operatorcert.entrypoints.check_permissions.OperatorReview.pr_owner_can_write" )
161183def test_OperatorReview_check_permissions (
184+ mock_pr_owner_can_write : MagicMock ,
162185 mock_is_org_member : MagicMock ,
163186 mock_is_partner : MagicMock ,
164187 mock_check_permission_for_partner : MagicMock ,
165188 mock_check_permission_for_community : MagicMock ,
166189 review_community : check_permissions .OperatorReview ,
167190 is_org_member : bool ,
168191 is_partner : bool ,
192+ owner_can_write : bool ,
169193 permission_partner : bool ,
170194 permission_community : bool ,
171195 permission_partner_called : bool ,
@@ -174,6 +198,7 @@ def test_OperatorReview_check_permissions(
174198) -> None :
175199 mock_is_org_member .return_value = is_org_member
176200 mock_is_partner .return_value = is_partner
201+ mock_pr_owner_can_write .return_value = owner_can_write
177202 mock_check_permission_for_partner .return_value = permission_partner
178203 mock_check_permission_for_community .return_value = permission_community
179204 assert review_community .check_permissions () == expected_result
@@ -216,6 +241,29 @@ def test_OperatorReview_is_org_member(
216241 assert review_community .is_org_member () == False
217242
218243
244+ @patch ("operatorcert.entrypoints.check_permissions.Github" )
245+ @patch ("operatorcert.entrypoints.check_permissions.Auth.Token" )
246+ def test_OperatorReview_pr_owner_can_write (
247+ mock_token : MagicMock ,
248+ mock_github : MagicMock ,
249+ review_community : check_permissions .OperatorReview ,
250+ ) -> None :
251+ mock_repo = MagicMock ()
252+ mock_github .return_value .get_repo .return_value = mock_repo
253+
254+ # User can write to the repository
255+ mock_repo .get_collaborator_permission .return_value = "write"
256+ assert review_community .pr_owner_can_write () == True
257+
258+ # User cannot write to the repository
259+ mock_repo .get_collaborator_permission .return_value = "read"
260+ assert review_community .pr_owner_can_write () == False
261+
262+ # Cannot get collaborator permission
263+ mock_repo .get_collaborator_permission .side_effect = GithubException (404 , "" , {})
264+ assert review_community .pr_owner_can_write () == False
265+
266+
219267@pytest .mark .parametrize (
220268 ["project" , "valid" ],
221269 [
@@ -331,10 +379,13 @@ def test_OperatorReview_request_review_from_maintainers(
331379 [
332380 "gh" ,
333381 "pr" ,
334- "edit " ,
382+ "comment " ,
335383 review_community .pull_request_url ,
336- "--add-reviewer" ,
337- "maintainer1,maintainer2" ,
384+ "--body" ,
385+ "This PR requires a review from repository maintainers.\n "
386+ f"@maintainer1, @maintainer2: please review the PR and approve it with an "
387+ "`approved` label if the pipeline is still running or merge the PR "
388+ "directly after review if the pipeline already passed successfully." ,
338389 ]
339390 )
340391
@@ -446,8 +497,27 @@ def test_check_permissions(
446497 )
447498
448499
500+ @patch ("operatorcert.entrypoints.check_permissions.Github" )
501+ @patch ("operatorcert.entrypoints.check_permissions.Auth.Token" )
502+ @patch ("operatorcert.entrypoints.check_permissions.add_labels_to_pull_request" )
503+ def test_add_label_approved (
504+ mock_add_labels : MagicMock ,
505+ mock_token : MagicMock ,
506+ mock_github : MagicMock ,
507+ ) -> None :
508+ mock_repo = MagicMock ()
509+ mock_github .return_value .get_repo .return_value = mock_repo
510+ mock_pull = MagicMock ()
511+ mock_repo .get_pull .return_value = mock_pull
512+
513+ check_permissions .add_label_approved ("https://github.com/my-org/repo-123/pulls/1" )
514+ mock_github .return_value .get_repo .assert_has_calls ([call ("my-org/repo-123" )])
515+ mock_repo .get_pull .assert_called_once_with (1 )
516+ mock_add_labels .assert_called_once_with (mock_pull , ["approved" ])
517+
518+
449519@patch ("operatorcert.entrypoints.check_permissions.json.dump" )
450- @patch ("operatorcert.entrypoints.check_permissions.run_command " )
520+ @patch ("operatorcert.entrypoints.check_permissions.add_label_approved " )
451521@patch ("operatorcert.entrypoints.check_permissions.check_permissions" )
452522@patch ("operatorcert.entrypoints.check_permissions.OperatorRepo" )
453523@patch ("operatorcert.entrypoints.check_permissions.setup_logger" )
@@ -457,7 +527,7 @@ def test_main(
457527 mock_setup_logger : MagicMock ,
458528 mock_operator_repo : MagicMock ,
459529 mock_check_permissions : MagicMock ,
460- mock_run_command : MagicMock ,
530+ mock_add_label_approved : MagicMock ,
461531 mock_json_dump : MagicMock ,
462532 monkeypatch : Any ,
463533 tmp_path : Path ,
@@ -480,9 +550,7 @@ def test_main(
480550 check_permissions .main ()
481551
482552 mock_check_permissions .assert_called_once_with (base_repo , head_repo , args )
483- mock_run_command .assert_called_once_with (
484- ["gh" , "pr" , "review" , args .pull_request_url , "--approve" ]
485- )
553+ mock_add_label_approved .assert_called_once_with (args .pull_request_url )
486554
487555 expected_output = {
488556 "approved" : mock_check_permissions .return_value ,
0 commit comments