From ef16702a0d991bdb8ced154002ecdd4211fae6e1 Mon Sep 17 00:00:00 2001 From: Steve Kowalik Date: Tue, 28 Aug 2018 15:34:34 +1000 Subject: [PATCH] Add support for required approving review count (#888) The GitHub API currently has a beta extension to the Branch Protection API that allows inspection and setting of the number of required approving reviews required for a PR to be merged. Add support for it. --- github/Branch.py | 23 +++++++++++++++---- github/Consts.py | 3 +++ github/RequiredPullRequestReviews.py | 11 +++++++++ github/tests/Branch.py | 18 +++++++++++++-- .../ReplayData/Branch.testEditProtection.txt | 8 +++---- ...ctionDismissalUsersWithUserOwnedBranch.txt | 2 +- ...ectionPushRestrictionsAndDismissalUser.txt | 4 ++-- ...ionPushRestrictionsWithUserOwnedBranch.txt | 2 +- ...nch.testEditRequiredPullRequestReviews.txt | 8 +++---- ...eviewsWithTooLargeApprovingReviewCount.txt | 10 ++++++++ ...ReviewsWithUserBranchAndDismissalUsers.txt | 2 +- .../Branch.testRemoveProtection.txt | 2 +- ...h.testRemoveRequiredPullRequestReviews.txt | 4 ++-- .../ReplayData/BranchProtection.setUp.txt | 2 +- .../RequiredPullRequestReviews.setUp.txt | 4 ++-- ...questReviews.testOrganizationOwnedTeam.txt | 2 +- github/tests/RequiredPullRequestReviews.py | 1 + 17 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithTooLargeApprovingReviewCount.txt diff --git a/github/Branch.py b/github/Branch.py index 05f46454af..0cf690b835 100644 --- a/github/Branch.py +++ b/github/Branch.py @@ -40,6 +40,7 @@ import github.RequiredPullRequestReviews import github.RequiredStatusChecks +import Consts class Branch(github.GithubObject.NonCompletableGithubObject): """ @@ -99,11 +100,12 @@ def get_protection(self): """ headers, data = self._requester.requestJsonAndCheck( "GET", - self.protection_url + self.protection_url, + headers={'Accept': Consts.mediaTypeRequireMultipleApprovingReviews} ) return github.BranchProtection.BranchProtection(self._requester, headers, data, completed=True) - def edit_protection(self, strict=github.GithubObject.NotSet, contexts=github.GithubObject.NotSet, enforce_admins=github.GithubObject.NotSet, dismissal_users=github.GithubObject.NotSet, dismissal_teams=github.GithubObject.NotSet, dismiss_stale_reviews=github.GithubObject.NotSet, require_code_owner_reviews=github.GithubObject.NotSet, user_push_restrictions=github.GithubObject.NotSet, team_push_restrictions=github.GithubObject.NotSet): + def edit_protection(self, strict=github.GithubObject.NotSet, contexts=github.GithubObject.NotSet, enforce_admins=github.GithubObject.NotSet, dismissal_users=github.GithubObject.NotSet, dismissal_teams=github.GithubObject.NotSet, dismiss_stale_reviews=github.GithubObject.NotSet, require_code_owner_reviews=github.GithubObject.NotSet, required_approving_review_count=github.GithubObject.NotSet, user_push_restrictions=github.GithubObject.NotSet, team_push_restrictions=github.GithubObject.NotSet): """ :calls: `PUT /repos/:owner/:repo/branches/:branch/protection `_ :strict: bool @@ -113,6 +115,7 @@ def edit_protection(self, strict=github.GithubObject.NotSet, contexts=github.Git :dismissal_teams: list of strings :dismiss_stale_reviews: bool :require_code_owner_reviews: bool + :required_approving_review_count: int :user_push_restrictions: list of strings :team_push_restrictions: list of strings @@ -127,6 +130,7 @@ def edit_protection(self, strict=github.GithubObject.NotSet, contexts=github.Git assert dismissal_teams is github.GithubObject.NotSet or all(isinstance(element, (str, unicode)) or isinstance(element, (str, unicode)) for element in dismissal_teams), dismissal_teams assert dismiss_stale_reviews is github.GithubObject.NotSet or isinstance(dismiss_stale_reviews, bool), dismiss_stale_reviews assert require_code_owner_reviews is github.GithubObject.NotSet or isinstance(require_code_owner_reviews, bool), require_code_owner_reviews + assert required_approving_review_count is github.GithubObject.NotSet or isinstance(required_approving_review_count, int), required_approving_review_count post_parameters = {} if strict is not github.GithubObject.NotSet or contexts is not github.GithubObject.NotSet: @@ -143,12 +147,14 @@ def edit_protection(self, strict=github.GithubObject.NotSet, contexts=github.Git else: post_parameters["enforce_admins"] = None - if dismissal_users is not github.GithubObject.NotSet or dismissal_teams is not github.GithubObject.NotSet or dismiss_stale_reviews is not github.GithubObject.NotSet or require_code_owner_reviews is not github.GithubObject.NotSet: + if dismissal_users is not github.GithubObject.NotSet or dismissal_teams is not github.GithubObject.NotSet or dismiss_stale_reviews is not github.GithubObject.NotSet or require_code_owner_reviews is not github.GithubObject.NotSet or required_approving_review_count is not github.GithubObject.NotSet: post_parameters["required_pull_request_reviews"] = {} if dismiss_stale_reviews is not github.GithubObject.NotSet: post_parameters["required_pull_request_reviews"]["dismiss_stale_reviews"] = dismiss_stale_reviews if require_code_owner_reviews is not github.GithubObject.NotSet: post_parameters["required_pull_request_reviews"]["require_code_owner_reviews"] = require_code_owner_reviews + if required_approving_review_count is not github.GithubObject.NotSet: + post_parameters["required_pull_request_reviews"]["required_approving_review_count"] = required_approving_review_count if dismissal_users is not github.GithubObject.NotSet: post_parameters["required_pull_request_reviews"]["dismissal_restrictions"] = {"users": dismissal_users} if dismissal_teams is not github.GithubObject.NotSet: @@ -169,6 +175,7 @@ def edit_protection(self, strict=github.GithubObject.NotSet, contexts=github.Git headers, data = self._requester.requestJsonAndCheck( "PUT", self.protection_url, + headers={'Accept': Consts.mediaTypeRequireMultipleApprovingReviews}, input=post_parameters ) @@ -228,22 +235,25 @@ def get_required_pull_request_reviews(self): """ headers, data = self._requester.requestJsonAndCheck( "GET", - self.protection_url + "/required_pull_request_reviews" + self.protection_url + "/required_pull_request_reviews", + headers={'Accept': Consts.mediaTypeRequireMultipleApprovingReviews} ) return github.RequiredPullRequestReviews.RequiredPullRequestReviews(self._requester, headers, data, completed=True) - def edit_required_pull_request_reviews(self, dismissal_users=github.GithubObject.NotSet, dismissal_teams=github.GithubObject.NotSet, dismiss_stale_reviews=github.GithubObject.NotSet, require_code_owner_reviews=github.GithubObject.NotSet): + def edit_required_pull_request_reviews(self, dismissal_users=github.GithubObject.NotSet, dismissal_teams=github.GithubObject.NotSet, dismiss_stale_reviews=github.GithubObject.NotSet, require_code_owner_reviews=github.GithubObject.NotSet, required_approving_review_count=github.GithubObject.NotSet): """ :calls: `PATCH /repos/:owner/:repo/branches/:branch/protection/required_pull_request_reviews `_ :dismissal_users: list of strings :dismissal_teams: list of strings :dismiss_stale_reviews: bool :require_code_owner_reviews: bool + :required_approving_review_count: int """ assert dismissal_users is github.GithubObject.NotSet or all(isinstance(element, (str, unicode)) or isinstance(element, (str, unicode)) for element in dismissal_users), dismissal_users assert dismissal_teams is github.GithubObject.NotSet or all(isinstance(element, (str, unicode)) or isinstance(element, (str, unicode)) for element in dismissal_teams), dismissal_teams assert dismiss_stale_reviews is github.GithubObject.NotSet or isinstance(dismiss_stale_reviews, bool), dismiss_stale_reviews assert require_code_owner_reviews is github.GithubObject.NotSet or isinstance(require_code_owner_reviews, bool), require_code_owner_reviews + assert required_approving_review_count is github.GithubObject.NotSet or isinstance(required_approving_review_count, int), required_approving_review_count post_parameters = {} if dismissal_users is not github.GithubObject.NotSet: @@ -256,9 +266,12 @@ def edit_required_pull_request_reviews(self, dismissal_users=github.GithubObject post_parameters["dismiss_stale_reviews"] = dismiss_stale_reviews if require_code_owner_reviews is not github.GithubObject.NotSet: post_parameters["require_code_owner_reviews"] = require_code_owner_reviews + if required_approving_review_count is not github.GithubObject.NotSet: + post_parameters["required_approving_review_count"] = required_approving_review_count headers, data = self._requester.requestJsonAndCheck( "PATCH", self.protection_url + "/required_pull_request_reviews", + headers={'Accept': Consts.mediaTypeRequireMultipleApprovingReviews}, input=post_parameters ) diff --git a/github/Consts.py b/github/Consts.py index 4b1621cc43..4bf902e961 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -80,3 +80,6 @@ # https://developer.github.com/changes/2018-01-10-lock-reason-api-preview/ mediaTypeLockReasonPreview = "application/vnd.github.sailor-v-preview+json" + +# https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/ +mediaTypeRequireMultipleApprovingReviews = "application/vnd.github.luke-cage-preview+json" diff --git a/github/RequiredPullRequestReviews.py b/github/RequiredPullRequestReviews.py index df622a90eb..c3da7be413 100644 --- a/github/RequiredPullRequestReviews.py +++ b/github/RequiredPullRequestReviews.py @@ -52,6 +52,14 @@ def require_code_owner_reviews(self): self._completeIfNotSet(self._require_code_owner_reviews) return self._require_code_owner_reviews.value + @property + def required_approving_review_count(self): + """ + :type: int + """ + self._completeIfNotSet(self._required_approving_review_count) + return self._required_approving_review_count.value + @property def url(self): """ @@ -79,6 +87,7 @@ def dismissal_teams(self): def _initAttributes(self): self._dismiss_stale_reviews = github.GithubObject.NotSet self._require_code_owner_reviews = github.GithubObject.NotSet + self._required_approving_review_count = github.GithubObject.NotSet self._users = github.GithubObject.NotSet self._teams = github.GithubObject.NotSet @@ -92,5 +101,7 @@ def _useAttributes(self, attributes): self._dismiss_stale_reviews = self._makeBoolAttribute(attributes["dismiss_stale_reviews"]) if "require_code_owner_reviews" in attributes: # pragma no branch self._require_code_owner_reviews = self._makeBoolAttribute(attributes["require_code_owner_reviews"]) + if "required_approving_review_count" in attributes: # pragma no branch + self._required_approving_review_count = self._makeIntAttribute(attributes["required_approving_review_count"]) if "url" in attributes: # pragma no branch self._url = self._makeStringAttribute(attributes["url"]) diff --git a/github/tests/Branch.py b/github/tests/Branch.py index 77ccbee4c7..110b0969bc 100644 --- a/github/tests/Branch.py +++ b/github/tests/Branch.py @@ -53,13 +53,14 @@ def testAttributes(self): self.assertEqual(self.branch.__repr__(), 'Branch(name="topic/RewriteWithGeneratedCode")') def testEditProtection(self): - self.protected_branch.edit_protection(strict=True, require_code_owner_reviews=True) + self.protected_branch.edit_protection(strict=True, require_code_owner_reviews=True, required_approving_review_count=2) branch_protection = self.protected_branch.get_protection() self.assertTrue(branch_protection.required_status_checks.strict) self.assertEqual(branch_protection.required_status_checks.contexts, []) self.assertTrue(branch_protection.enforce_admins) self.assertFalse(branch_protection.required_pull_request_reviews.dismiss_stale_reviews) self.assertTrue(branch_protection.required_pull_request_reviews.require_code_owner_reviews) + self.assertEqual(branch_protection.required_pull_request_reviews.required_approving_review_count, 2) def testEditProtectionDismissalUsersWithUserOwnedBranch(self): with self.assertRaises(github.GithubException) as raisedexp: @@ -127,10 +128,22 @@ def testRemoveRequiredStatusChecks(self): ) def testEditRequiredPullRequestReviews(self): - self.protected_branch.edit_required_pull_request_reviews(dismiss_stale_reviews=True) + self.protected_branch.edit_required_pull_request_reviews(dismiss_stale_reviews=True, required_approving_review_count=2) required_pull_request_reviews = self.protected_branch.get_required_pull_request_reviews() self.assertTrue(required_pull_request_reviews.dismiss_stale_reviews) self.assertTrue(required_pull_request_reviews.require_code_owner_reviews) + self.assertEqual(required_pull_request_reviews.required_approving_review_count, 2) + + def testEditRequiredPullRequestReviewsWithTooLargeApprovingReviewCount(self): + with self.assertRaises(github.GithubException) as raisedexp: + self.protected_branch.edit_required_pull_request_reviews(required_approving_review_count=9) + self.assertEqual(raisedexp.exception.status, 422) + self.assertEqual( + raisedexp.exception.data, { + u'documentation_url': u'https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch', + u'message': u'Invalid request.\n\n9 must be less than or equal to 6.' + } + ) def testEditRequiredPullRequestReviewsWithUserBranchAndDismissalUsers(self): with self.assertRaises(github.GithubException) as raisedexp: @@ -148,6 +161,7 @@ def testRemoveRequiredPullRequestReviews(self): required_pull_request_reviews = self.protected_branch.get_required_pull_request_reviews() self.assertFalse(required_pull_request_reviews.dismiss_stale_reviews) self.assertFalse(required_pull_request_reviews.require_code_owner_reviews) + self.assertEqual(required_pull_request_reviews.required_approving_review_count, 1) def testAdminEnforcement(self): self.protected_branch.remove_admin_enforcement() diff --git a/github/tests/ReplayData/Branch.testEditProtection.txt b/github/tests/ReplayData/Branch.testEditProtection.txt index 48597c0b7b..f9a6873678 100644 --- a/github/tests/ReplayData/Branch.testEditProtection.txt +++ b/github/tests/ReplayData/Branch.testEditProtection.txt @@ -3,8 +3,8 @@ PUT api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection -{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python'} -{"restrictions": null, "required_pull_request_reviews": {"require_code_owner_reviews": true}, "required_status_checks": {"contexts": [], "strict": true}, "enforce_admins": null} +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} +{"restrictions": null, "required_pull_request_reviews": {"require_code_owner_reviews": true, "required_approving_review_count": 2}, "required_status_checks": {"contexts": [], "strict": true}, "enforce_admins": null} 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4994'), ('content-length', '0'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"e0dc2dfc56971f4a36de1216356ea98b"'), ('date', 'Sat, 05 May 2018 06:05:54 GMT'), ('content-type', 'application/json; charset=utf-8')] '' @@ -14,9 +14,9 @@ GET api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4994'), ('content-length', '0'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"81ba94a48ad867b26d48c023ac584f43"'), ('date', 'Mon, 07 May 2018 13:42:41 GMT'), ('content-type', 'application/json; charset=utf-8')] -{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection","required_pull_request_reviews":{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":false,"require_code_owner_reviews":true},"required_status_checks":{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks","strict":true,"contexts":[],"contexts_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks/contexts"},"enforce_admins":{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/enforce_admins","enabled":true}} +{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection","required_pull_request_reviews":{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":false,"require_code_owner_reviews":true,"required_approving_review_count":2},"required_status_checks":{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks","strict":true,"contexts":[],"contexts_url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_status_checks/contexts"},"enforce_admins":{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/enforce_admins","enabled":true}} diff --git a/github/tests/ReplayData/Branch.testEditProtectionDismissalUsersWithUserOwnedBranch.txt b/github/tests/ReplayData/Branch.testEditProtectionDismissalUsersWithUserOwnedBranch.txt index 4c138edff3..27fead7e15 100644 --- a/github/tests/ReplayData/Branch.testEditProtectionDismissalUsersWithUserOwnedBranch.txt +++ b/github/tests/ReplayData/Branch.testEditProtectionDismissalUsersWithUserOwnedBranch.txt @@ -3,7 +3,7 @@ PUT api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection -{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} {"restrictions": null, "required_pull_request_reviews": {"dismissal_restrictions": {"users": ["jacquev6"]}}, "required_status_checks": null, "enforce_admins": null} 422 [('status', '422 Unprocessable Entity'), ('x-ratelimit-remaining', '4994'), ('content-length', '0'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"e0dc2dfc56971f4a36de1216356ea98b"'), ('date', 'Sun, 13 May 2018 10:42:14 GMT'), ('content-type', 'application/json; charset=utf-8')] diff --git a/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsAndDismissalUser.txt b/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsAndDismissalUser.txt index 3027deb5a8..9ed6dafc71 100644 --- a/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsAndDismissalUser.txt +++ b/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsAndDismissalUser.txt @@ -3,7 +3,7 @@ PUT api.github.com None /repos/PyGithub/PyGithub/branches/master/protection -{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} {"restrictions": {"teams": [], "users": ["jacquev6"]}, "required_pull_request_reviews": {"dismissal_restrictions": {"users": ["jacquev6"]}}, "required_status_checks": null, "enforce_admins": null} 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4994'), ('content-length', '0'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"e0dc2dfc56971f4a36de1216356ea98b"'), ('date', 'Sun, 13 May 2018 13:21:24 GMT'), ('content-type', 'application/json; charset=utf-8')] @@ -14,7 +14,7 @@ GET api.github.com None /repos/PyGithub/PyGithub/branches/master/protection -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('x-runtime-rack', '0.049160'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"a4ef2f2bcfdf33fadd5c8fa6867ebc3a"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('status', '200 OK'), ('x-ratelimit-remaining', '4986'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', '860C:2DC5:28789D:34E3E8:5AF7FF18'), ('date', 'Sun, 13 May 2018 09:02:17 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1526204223')] diff --git a/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsWithUserOwnedBranch.txt b/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsWithUserOwnedBranch.txt index 7a88ff9a85..154f9e3d64 100644 --- a/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsWithUserOwnedBranch.txt +++ b/github/tests/ReplayData/Branch.testEditProtectionPushRestrictionsWithUserOwnedBranch.txt @@ -3,7 +3,7 @@ PUT api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection -{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} {"restrictions": {"users": ["jacquev6"], "teams": []}, "required_pull_request_reviews": null, "required_status_checks": null, "enforce_admins": null} 422 [('status', '422 Unprocessable Entity'), ('x-ratelimit-remaining', '4994'), ('content-length', '0'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"e0dc2dfc56971f4a36de1216356ea98b"'), ('date', 'Sat, 05 May 2018 06:05:54 GMT'), ('content-type', 'application/json; charset=utf-8')] diff --git a/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviews.txt b/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviews.txt index 875396d9c9..06f8454d7a 100644 --- a/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviews.txt +++ b/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviews.txt @@ -3,8 +3,8 @@ PATCH api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews -{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python'} -{"dismiss_stale_reviews":true} +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} +{"dismiss_stale_reviews":true, "required_approving_review_count": 2} 200 [('status', '200 OK'), ('x-ratelimit-remaining', '4994'), ('content-length', '0'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"81ba94a48ad867b26d48c023ac584f43"'), ('date', 'Mon, 07 May 2018 13:42:41 GMT'), ('content-type', 'application/json; charset=utf-8')] '' @@ -14,9 +14,9 @@ GET api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('x-runtime-rack', '0.041627'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"3488d056130a553f9c03f03ed12d07db"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('status', '200 OK'), ('x-ratelimit-remaining', '4988'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', '8C96:2DD0:1347823:192926E:5AF58689'), ('date', 'Fri, 11 May 2018 12:03:36 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1526042267')] -{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":true,"require_code_owner_reviews":true} +{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":2} diff --git a/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithTooLargeApprovingReviewCount.txt b/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithTooLargeApprovingReviewCount.txt new file mode 100644 index 0000000000..6ea236eb86 --- /dev/null +++ b/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithTooLargeApprovingReviewCount.txt @@ -0,0 +1,10 @@ +https +PATCH +api.github.com +None +/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} +{"required_approving_review_count": 9} +422 +[('status', '422 Unprocessable Entity'), ('content-length', '227'), ('x-content-type-options', 'nosniff'), ('content-security-policy', "default-src 'none'"), ('access-control-expose-headers', 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', 'AE1E5031:A39B:2D392574:568E6BC1'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-ratelimit-remaining', '4991'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-xss-protection', '1; mode=block'), ('access-control-allow-credentials', 'true'), ('date', 'Tue, 28 Aug 2018 04:44:52 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-frame-options', 'deny'), ('x-ratelimit-reset', '1452177625')] +{"documentation_url":"https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch","message":"Invalid request.\n\n9 must be less than or equal to 6."} diff --git a/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithUserBranchAndDismissalUsers.txt b/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithUserBranchAndDismissalUsers.txt index 211789d110..6cc15cc4d7 100644 --- a/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithUserBranchAndDismissalUsers.txt +++ b/github/tests/ReplayData/Branch.testEditRequiredPullRequestReviewsWithUserBranchAndDismissalUsers.txt @@ -3,7 +3,7 @@ PATCH api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews -{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'Content-Type': 'application/json', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} {"dismissal_restrictions":{"users":["jacquev6"]}} 422 [('status', '422 Unprocessable Entity'), ('content-length', '227'), ('x-content-type-options', 'nosniff'), ('content-security-policy', "default-src 'none'"), ('access-control-expose-headers', 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', 'AE1E5031:A39B:2D392574:568E6BC1'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-ratelimit-remaining', '4991'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-xss-protection', '1; mode=block'), ('access-control-allow-credentials', 'true'), ('date', 'Mon, 07 May 2018 12:46:55 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-frame-options', 'deny'), ('x-ratelimit-reset', '1452177625')] diff --git a/github/tests/ReplayData/Branch.testRemoveProtection.txt b/github/tests/ReplayData/Branch.testRemoveProtection.txt index 948f57e71c..4aac89d9ec 100644 --- a/github/tests/ReplayData/Branch.testRemoveProtection.txt +++ b/github/tests/ReplayData/Branch.testRemoveProtection.txt @@ -25,7 +25,7 @@ GET api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 404 [('status', '404 Not Found'), ('content-length', '126'), ('x-content-type-options', 'nosniff'), ('content-security-policy', "default-src 'none'"), ('access-control-expose-headers', 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', 'AE1E5031:A39B:2D392574:568E6BC1'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-ratelimit-remaining', '4991'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-xss-protection', '1; mode=block'), ('access-control-allow-credentials', 'true'), ('date', 'Mon, 07 May 2018 12:46:55 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-frame-options', 'deny'), ('x-ratelimit-reset', '1452177625')] diff --git a/github/tests/ReplayData/Branch.testRemoveRequiredPullRequestReviews.txt b/github/tests/ReplayData/Branch.testRemoveRequiredPullRequestReviews.txt index 857c3eb328..57860f6143 100644 --- a/github/tests/ReplayData/Branch.testRemoveRequiredPullRequestReviews.txt +++ b/github/tests/ReplayData/Branch.testRemoveRequiredPullRequestReviews.txt @@ -14,8 +14,8 @@ GET api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('x-runtime-rack', '0.056567'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"6686cddc9495f58aa23d6956c10cc1a6"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('status', '200 OK'), ('x-ratelimit-remaining', '4980'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', '8DC0:2DD0:134EC8E:193336D:5AF588B1'), ('date', 'Fri, 11 May 2018 12:12:39 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1526042267')] -{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":false,"require_code_owner_reviews":false} +{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":false,"require_code_owner_reviews":false,"required_approving_review_count":1} diff --git a/github/tests/ReplayData/BranchProtection.setUp.txt b/github/tests/ReplayData/BranchProtection.setUp.txt index eac4884e56..f3072a3022 100644 --- a/github/tests/ReplayData/BranchProtection.setUp.txt +++ b/github/tests/ReplayData/BranchProtection.setUp.txt @@ -36,7 +36,7 @@ GET api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('x-runtime-rack', '0.050971'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"e0dc2dfc56971f4a36de1216356ea98b"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('status', '200 OK'), ('x-ratelimit-remaining', '4993'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', 'E962:55F3:1A4A1F1:404B411:5AF928BE'), ('date', 'Mon, 14 May 2018 06:12:48 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1526281649')] diff --git a/github/tests/ReplayData/RequiredPullRequestReviews.setUp.txt b/github/tests/ReplayData/RequiredPullRequestReviews.setUp.txt index 533d3551e8..b3afaac11e 100644 --- a/github/tests/ReplayData/RequiredPullRequestReviews.setUp.txt +++ b/github/tests/ReplayData/RequiredPullRequestReviews.setUp.txt @@ -36,9 +36,9 @@ GET api.github.com None /repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('status', '200 OK'), ('x-runtime-rack', '0.049518'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"f972722557f6bbc814f109abae4df24e"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-ratelimit-remaining', '4996'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', 'C9A0:2DCE:4A1B5C:60E4A6:5AF302A0'), ('date', 'Wed, 09 May 2018 14:16:17 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1525878932')] -{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":true,"require_code_owner_reviews":true} +{"url":"https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews","dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":3} diff --git a/github/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt b/github/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt index c4c2a88a60..31fe425e15 100644 --- a/github/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt +++ b/github/tests/ReplayData/RequiredPullRequestReviews.testOrganizationOwnedTeam.txt @@ -14,7 +14,7 @@ GET api.github.com None /repos/PyGithub/PyGithub/branches/integrations/protection/required_pull_request_reviews -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.luke-cage-preview+json'} None 200 [('status', '200 OK'), ('x-runtime-rack', '0.049518'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-oauth-scopes', 'public_repo, repo:status'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('x-accepted-oauth-scopes', ''), ('etag', 'W/"f972722557f6bbc814f109abae4df24e"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-ratelimit-remaining', '4996'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('transfer-encoding', 'chunked'), ('x-github-request-id', 'C9A0:2DCE:4A1B5C:60E4A6:5AF302A0'), ('date', 'Wed, 09 May 2018 14:16:17 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('content-encoding', 'gzip'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1525878932')] diff --git a/github/tests/RequiredPullRequestReviews.py b/github/tests/RequiredPullRequestReviews.py index 36dbc3450d..12a8df3f39 100644 --- a/github/tests/RequiredPullRequestReviews.py +++ b/github/tests/RequiredPullRequestReviews.py @@ -35,6 +35,7 @@ def setUp(self): def testAttributes(self): self.assertTrue(self.required_pull_request_reviews.dismiss_stale_reviews) self.assertTrue(self.required_pull_request_reviews.require_code_owner_reviews) + self.assertEqual(self.required_pull_request_reviews.required_approving_review_count, 3) self.assertEqual(self.required_pull_request_reviews.url, "https://api.github.com/repos/jacquev6/PyGithub/branches/integrations/protection/required_pull_request_reviews") self.assertIs(self.required_pull_request_reviews.dismissal_users, None) self.assertIs(self.required_pull_request_reviews.dismissal_teams, None)