From a2b48a66a40291383a26f775f1ad32f4f530fe6f Mon Sep 17 00:00:00 2001 From: Mordechai Zuber Date: Wed, 30 Aug 2017 14:12:42 +0300 Subject: [PATCH] ProtectedBranches API changes for Required Review Enforcement (#1523) * Add `BranchProtectionRequiredPullRequestReviews` and `BranchProtectionRequiredPullRequestReviewsUpdate` models * Add missing ctors and fix naming Tests where updated to use the minimum nesseccary constructor * Fix debugger display * Update BranchProtection response model to include new dismissal restrictions fields and tidy up existing properties ctors and DebuggerDisplay * Update BranchProtectionUpdate request model to include new dismissal restrictions fields/classes and tidy up existing properties and DebuggerDisplay * Update BranchProtection tests to use new RequiredReviews and dismissal restrictions options * Add specific client endpoints for GetReviewEnforcement UpdateReviewEnforcement and RemoveReviewEnforcement * Add unit and integration tests for new client methods * Implement Observable client methods and unit tests * Add integration tests for Observable client * Run CodeFormatter to fix up whitespace * Clarify review dismissal restriction behaviour in code comments --- .../IObservableRepositoryBranchesClient.cs | 63 ++ .../ObservableRepositoryBranchesClient.cs | 109 ++++ .../Clients/RepositoryBranchesClientTests.cs | 323 +++++++++- .../OrganizationRepositoryWithTeamContext.cs | 7 +- ...ObservableRepositoryBranchesClientTests.cs | 556 ++++++++++++++++++ .../Clients/RepositoryBranchesClientTests.cs | 151 ++++- ...ObservableRepositoryBranchesClientTests.cs | 134 +++++ Octokit/Clients/IRepositoryBranchesClient.cs | 63 ++ Octokit/Clients/RepositoryBranchesClient.cs | 121 ++++ Octokit/Helpers/AcceptHeaders.cs | 2 +- Octokit/Helpers/ApiUrls.cs | 21 + .../Models/Request/BranchProtectionUpdate.cs | 224 ++++++- Octokit/Models/Response/BranchProtection.cs | 102 +++- 13 files changed, 1839 insertions(+), 37 deletions(-) create mode 100644 Octokit.Tests.Integration/Reactive/ObservableRepositoryBranchesClientTests.cs diff --git a/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs index 5bf66450..b7836177 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs @@ -295,6 +295,69 @@ namespace Octokit.Reactive /// The contexts to remove IObservable DeleteRequiredStatusChecksContexts(long repositoryId, string branch, IReadOnlyList contexts); + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + IObservable GetReviewEnforcement(string owner, string name, string branch); + + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + IObservable GetReviewEnforcement(long repositoryId, string branch); + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + IObservable UpdateReviewEnforcement(string owner, string name, string branch, BranchProtectionRequiredReviewsUpdate update); + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + IObservable UpdateReviewEnforcement(long repositoryId, string branch, BranchProtectionRequiredReviewsUpdate update); + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + IObservable RemoveReviewEnforcement(string owner, string name, string branch); + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + IObservable RemoveReviewEnforcement(long repositoryId, string branch); + /// /// Get admin enforcement of protected branch /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs index b3080a21..6b800dd9 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs @@ -466,6 +466,107 @@ namespace Octokit.Reactive return _client.DeleteRequiredStatusChecksContexts(repositoryId, branch, contexts).ToObservable().SelectMany(x => x); } + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public IObservable GetReviewEnforcement(string owner, string name, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return _client.GetReviewEnforcement(owner, name, branch).ToObservable(); + } + + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public IObservable GetReviewEnforcement(long repositoryId, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return _client.GetReviewEnforcement(repositoryId, branch).ToObservable(); + } + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public IObservable UpdateReviewEnforcement(string owner, string name, string branch, BranchProtectionRequiredReviewsUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, nameof(update)); + + return _client.UpdateReviewEnforcement(owner, name, branch, update).ToObservable(); + } + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public IObservable UpdateReviewEnforcement(long repositoryId, string branch, BranchProtectionRequiredReviewsUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, nameof(update)); + + return _client.UpdateReviewEnforcement(repositoryId, branch, update).ToObservable(); + } + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public IObservable RemoveReviewEnforcement(string owner, string name, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return _client.RemoveReviewEnforcement(owner, name, branch).ToObservable(); + } + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public IObservable RemoveReviewEnforcement(long repositoryId, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return _client.RemoveReviewEnforcement(repositoryId, branch).ToObservable(); + } + /// /// Get admin enforcement of protected branch /// @@ -517,6 +618,14 @@ namespace Octokit.Reactive return _client.AddAdminEnforcement(owner, name, branch).ToObservable(); } + /// + /// Add admin enforcement to protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch public IObservable AddAdminEnforcement(long repositoryId, string branch) { Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); diff --git a/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs index 6b72a8d6..75414753 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs @@ -219,11 +219,16 @@ public class RepositoryBranchesClientTests var repoName = _userRepoContext.RepositoryName; var protection = await _client.GetBranchProtection(repoOwner, repoName, "master"); - Assert.True(protection.EnforceAdmins.Enabled); Assert.True(protection.RequiredStatusChecks.Strict); Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Null(protection.Restrictions); + + Assert.True(protection.EnforceAdmins.Enabled); } [IntegrationTest] @@ -232,11 +237,16 @@ public class RepositoryBranchesClientTests var repoId = _userRepoContext.RepositoryId; var protection = await _client.GetBranchProtection(repoId, "master"); - Assert.True(protection.EnforceAdmins.Enabled); Assert.True(protection.RequiredStatusChecks.Strict); Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Null(protection.Restrictions); + + Assert.True(protection.EnforceAdmins.Enabled); } [IntegrationTest] @@ -246,12 +256,18 @@ public class RepositoryBranchesClientTests var repoName = _orgRepoContext.RepositoryContext.RepositoryName; var protection = await _client.GetBranchProtection(repoOwner, repoName, "master"); - Assert.True(protection.EnforceAdmins.Enabled); Assert.True(protection.RequiredStatusChecks.Strict); Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + Assert.Equal(1, protection.RequiredPullRequestReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, protection.RequiredPullRequestReviews.DismissalRestrictions.Users.Count); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Equal(1, protection.Restrictions.Teams.Count); Assert.Equal(0, protection.Restrictions.Users.Count); + + Assert.True(protection.EnforceAdmins.Enabled); } [IntegrationTest] @@ -260,12 +276,18 @@ public class RepositoryBranchesClientTests var repoId = _orgRepoContext.RepositoryContext.RepositoryId; var protection = await _client.GetBranchProtection(repoId, "master"); - Assert.True(protection.EnforceAdmins.Enabled); Assert.True(protection.RequiredStatusChecks.Strict); Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + Assert.Equal(1, protection.RequiredPullRequestReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, protection.RequiredPullRequestReviews.DismissalRestrictions.Users.Count); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Equal(1, protection.Restrictions.Teams.Count); Assert.Equal(0, protection.Restrictions.Users.Count); + + Assert.True(protection.EnforceAdmins.Enabled); } public void Dispose() @@ -299,15 +321,22 @@ public class RepositoryBranchesClientTests var repoOwner = _userRepoContext.RepositoryOwner; var repoName = _userRepoContext.RepositoryName; var update = new BranchProtectionSettingsUpdate( - new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" })); + new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(false, true), + false); var protection = await _client.UpdateBranchProtection(repoOwner, repoName, "master", update); - Assert.False(protection.EnforceAdmins.Enabled); Assert.False(protection.RequiredStatusChecks.Strict); Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Null(protection.Restrictions); + + Assert.False(protection.EnforceAdmins.Enabled); } [IntegrationTest] @@ -315,15 +344,22 @@ public class RepositoryBranchesClientTests { var repoId = _userRepoContext.RepositoryId; var update = new BranchProtectionSettingsUpdate( - new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" })); + new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(false, true), + false); var protection = await _client.UpdateBranchProtection(repoId, "master", update); - Assert.False(protection.EnforceAdmins.Enabled); Assert.False(protection.RequiredStatusChecks.Strict); Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Null(protection.Restrictions); + + Assert.False(protection.EnforceAdmins.Enabled); } [IntegrationTest] @@ -333,17 +369,23 @@ public class RepositoryBranchesClientTests var repoName = _orgRepoContext.RepositoryContext.RepositoryName; var update = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), false, false), new BranchProtectionPushRestrictionsUpdate(), false); var protection = await _client.UpdateBranchProtection(repoOwner, repoName, "master", update); - Assert.False(protection.EnforceAdmins.Enabled); Assert.False(protection.RequiredStatusChecks.Strict); Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.False(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Empty(protection.Restrictions.Teams); Assert.Empty(protection.Restrictions.Users); + + Assert.False(protection.EnforceAdmins.Enabled); } [IntegrationTest] @@ -352,17 +394,23 @@ public class RepositoryBranchesClientTests var repoId = _orgRepoContext.RepositoryContext.RepositoryId; var update = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), false, false), new BranchProtectionPushRestrictionsUpdate(), false); var protection = await _client.UpdateBranchProtection(repoId, "master", update); - Assert.False(protection.EnforceAdmins.Enabled); Assert.False(protection.RequiredStatusChecks.Strict); Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.False(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.Empty(protection.Restrictions.Teams); Assert.Empty(protection.Restrictions.Users); + + Assert.False(protection.EnforceAdmins.Enabled); } public void Dispose() @@ -736,6 +784,261 @@ public class RepositoryBranchesClientTests } } + public class TheGetReviewEnforcementMethod : IDisposable + { + IRepositoryBranchesClient _client; + RepositoryContext _userRepoContext; + OrganizationRepositoryWithTeamContext _orgRepoContext; + + public TheGetReviewEnforcementMethod() + { + var github = Helper.GetAuthenticatedClient(); + _client = github.Repository.Branch; + + _userRepoContext = github.CreateRepositoryWithProtectedBranch().Result; + _orgRepoContext = github.CreateOrganizationRepositoryWithProtectedBranch().Result; + } + + [IntegrationTest] + public async Task GetsReviewEnforcement() + { + var repoOwner = _userRepoContext.RepositoryOwner; + var repoName = _userRepoContext.RepositoryName; + var requiredReviews = await _client.GetReviewEnforcement(repoOwner, repoName, "master"); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task GetsReviewEnforcementWithRepositoryId() + { + var repoId = _userRepoContext.RepositoryId; + var requiredReviews = await _client.GetReviewEnforcement(repoId, "master"); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task GetsReviewEnforcementForOrgRepo() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var requiredReviews = await _client.GetReviewEnforcement(repoOwner, repoName, "master"); + + Assert.Equal(1, requiredReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, requiredReviews.DismissalRestrictions.Users.Count); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task GetsReviewEnforcementForOrgRepoWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var requiredReviews = await _client.GetReviewEnforcement(repoId, "master"); + + Assert.Equal(1, requiredReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, requiredReviews.DismissalRestrictions.Users.Count); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + public void Dispose() + { + if (_userRepoContext != null) + _userRepoContext.Dispose(); + + if (_orgRepoContext != null) + _orgRepoContext.Dispose(); + } + } + + public class TheUpdateReviewEnforcementMethod : IDisposable + { + IRepositoryBranchesClient _client; + RepositoryContext _userRepoContext; + OrganizationRepositoryWithTeamContext _orgRepoContext; + + public TheUpdateReviewEnforcementMethod() + { + var github = Helper.GetAuthenticatedClient(); + _client = github.Repository.Branch; + + _userRepoContext = github.CreateRepositoryWithProtectedBranch().Result; + _orgRepoContext = github.CreateOrganizationRepositoryWithProtectedBranch().Result; + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcement() + { + var repoOwner = _userRepoContext.RepositoryOwner; + var repoName = _userRepoContext.RepositoryName; + var update = new BranchProtectionRequiredReviewsUpdate(false, true); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoOwner, repoName, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementWithRepositoryId() + { + var repoId = _userRepoContext.RepositoryId; + var update = new BranchProtectionRequiredReviewsUpdate(false, true); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoId, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepo() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoOwner, repoName, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepoWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoId, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepoWithAdminOnly() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(true), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoOwner, repoName, "master", update); + + Assert.Empty(requiredReviews.DismissalRestrictions.Teams); + Assert.Empty(requiredReviews.DismissalRestrictions.Users); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepoWithAdminOnlyWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(true), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoId, "master", update); + + Assert.Empty(requiredReviews.DismissalRestrictions.Teams); + Assert.Empty(requiredReviews.DismissalRestrictions.Users); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + public void Dispose() + { + if (_userRepoContext != null) + _userRepoContext.Dispose(); + + if (_orgRepoContext != null) + _orgRepoContext.Dispose(); + } + } + + public class TheRemoveReviewEnforcementMethod + { + IGitHubClient _github; + IRepositoryBranchesClient _client; + + public TheRemoveReviewEnforcementMethod() + { + _github = Helper.GetAuthenticatedClient(); + _client = _github.Repository.Branch; + } + + [IntegrationTest] + public async Task RemovesReviewEnforcement() + { + using (var context = await _github.CreateRepositoryWithProtectedBranch()) + { + var repoOwner = context.RepositoryOwner; + var repoName = context.RepositoryName; + var deleted = await _client.RemoveReviewEnforcement(repoOwner, repoName, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task RemovesReviewEnforcementWithRepositoryId() + { + using (var context = await _github.CreateRepositoryWithProtectedBranch()) + { + var repoId = context.RepositoryId; + var deleted = await _client.RemoveReviewEnforcement(repoId, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task RemovesReviewEnforcementForOrgRepo() + { + using (var context = await _github.CreateOrganizationRepositoryWithProtectedBranch()) + { + var repoOwner = context.RepositoryContext.RepositoryOwner; + var repoName = context.RepositoryContext.RepositoryName; + var deleted = await _client.RemoveReviewEnforcement(repoOwner, repoName, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task RemovesReviewEnforcementForOrgRepoWithRepositoryId() + { + using (var context = await _github.CreateOrganizationRepositoryWithProtectedBranch()) + { + var repoId = context.RepositoryContext.RepositoryId; + var deleted = await _client.RemoveReviewEnforcement(repoId, "master"); + + Assert.True(deleted); + } + } + } + public class TheGetAdminEnforcementMethod : IDisposable { private readonly IRepositoryBranchesClient _client; diff --git a/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs b/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs index 4211ef93..7322913b 100644 --- a/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs +++ b/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs @@ -30,7 +30,11 @@ namespace Octokit.Tests.Integration.Helpers var contextUserRepo = await client.CreateRepositoryContext(userRepo); // Protect master branch - var update = new BranchProtectionSettingsUpdate(new BranchProtectionRequiredStatusChecksUpdate(true, new[] { "build", "test" }), null, true); + var update = new BranchProtectionSettingsUpdate( + new BranchProtectionRequiredStatusChecksUpdate(true, new[] { "build", "test" }), + new BranchProtectionRequiredReviewsUpdate(true, true), + null, + true); await client.Repository.Branch.UpdateBranchProtection(contextUserRepo.RepositoryOwner, contextUserRepo.RepositoryName, "master", update); @@ -56,6 +60,7 @@ namespace Octokit.Tests.Integration.Helpers // Protect master branch var protection = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredStatusChecksUpdate(true, new[] { "build", "test" }), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(new BranchProtectionTeamCollection { contextOrgTeam.TeamName }), true, true), new BranchProtectionPushRestrictionsUpdate(new BranchProtectionTeamCollection { contextOrgTeam.TeamName }), true); await client.Repository.Branch.UpdateBranchProtection(contextOrgRepo.RepositoryOwner, contextOrgRepo.RepositoryName, "master", protection); diff --git a/Octokit.Tests.Integration/Reactive/ObservableRepositoryBranchesClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableRepositoryBranchesClientTests.cs new file mode 100644 index 00000000..db4ea1b5 --- /dev/null +++ b/Octokit.Tests.Integration/Reactive/ObservableRepositoryBranchesClientTests.cs @@ -0,0 +1,556 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Octokit.Reactive; +using Octokit.Tests.Integration.Helpers; +using Xunit; + +namespace Octokit.Tests.Integration +{ + public class ObservableRepositoryBranchesClientTests + { + public class TheGetBranchProtectionMethod : IDisposable + { + IObservableRepositoryBranchesClient _client; + RepositoryContext _userRepoContext; + OrganizationRepositoryWithTeamContext _orgRepoContext; + + public TheGetBranchProtectionMethod() + { + var github = Helper.GetAuthenticatedClient(); + _client = new ObservableRepositoryBranchesClient(github); + + _userRepoContext = github.CreateRepositoryWithProtectedBranch().Result; + _orgRepoContext = github.CreateOrganizationRepositoryWithProtectedBranch().Result; + } + + [IntegrationTest] + public async Task GetsBranchProtection() + { + var repoOwner = _userRepoContext.RepositoryOwner; + var repoName = _userRepoContext.RepositoryName; + var protection = await _client.GetBranchProtection(repoOwner, repoName, "master"); + + Assert.True(protection.RequiredStatusChecks.Strict); + Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Null(protection.Restrictions); + + Assert.True(protection.EnforceAdmins.Enabled); + } + + [IntegrationTest] + public async Task GetsBranchProtectionWithRepositoryId() + { + var repoId = _userRepoContext.RepositoryId; + var protection = await _client.GetBranchProtection(repoId, "master"); + + Assert.True(protection.RequiredStatusChecks.Strict); + Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Null(protection.Restrictions); + + Assert.True(protection.EnforceAdmins.Enabled); + } + + [IntegrationTest] + public async Task GetsBranchProtectionForOrgRepo() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var protection = await _client.GetBranchProtection(repoOwner, repoName, "master"); + + Assert.True(protection.RequiredStatusChecks.Strict); + Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Equal(1, protection.RequiredPullRequestReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, protection.RequiredPullRequestReviews.DismissalRestrictions.Users.Count); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Equal(1, protection.Restrictions.Teams.Count); + Assert.Equal(0, protection.Restrictions.Users.Count); + + Assert.True(protection.EnforceAdmins.Enabled); + } + + [IntegrationTest] + public async Task GetsBranchProtectionForOrgRepoWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var protection = await _client.GetBranchProtection(repoId, "master"); + + Assert.True(protection.RequiredStatusChecks.Strict); + Assert.Equal(2, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Equal(1, protection.RequiredPullRequestReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, protection.RequiredPullRequestReviews.DismissalRestrictions.Users.Count); + Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Equal(1, protection.Restrictions.Teams.Count); + Assert.Equal(0, protection.Restrictions.Users.Count); + + Assert.True(protection.EnforceAdmins.Enabled); + } + + public void Dispose() + { + if (_userRepoContext != null) + _userRepoContext.Dispose(); + + if (_orgRepoContext != null) + _orgRepoContext.Dispose(); + } + } + + public class TheUpdateBranchProtectionMethod : IDisposable + { + IObservableRepositoryBranchesClient _client; + RepositoryContext _userRepoContext; + OrganizationRepositoryWithTeamContext _orgRepoContext; + + public TheUpdateBranchProtectionMethod() + { + var github = Helper.GetAuthenticatedClient(); + _client = new ObservableRepositoryBranchesClient(github); + + _userRepoContext = github.CreateRepositoryWithProtectedBranch().Result; + _orgRepoContext = github.CreateOrganizationRepositoryWithProtectedBranch().Result; + } + + [IntegrationTest] + public async Task UpdatesBranchProtection() + { + var repoOwner = _userRepoContext.RepositoryOwner; + var repoName = _userRepoContext.RepositoryName; + var update = new BranchProtectionSettingsUpdate( + new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(false, true), + false); + + var protection = await _client.UpdateBranchProtection(repoOwner, repoName, "master", update); + + Assert.False(protection.RequiredStatusChecks.Strict); + Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Null(protection.Restrictions); + + Assert.False(protection.EnforceAdmins.Enabled); + } + + [IntegrationTest] + public async Task UpdatesBranchProtectionWithRepositoryId() + { + var repoId = _userRepoContext.RepositoryId; + var update = new BranchProtectionSettingsUpdate( + new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(false, true), + false); + + var protection = await _client.UpdateBranchProtection(repoId, "master", update); + + Assert.False(protection.RequiredStatusChecks.Strict); + Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Null(protection.Restrictions); + + Assert.False(protection.EnforceAdmins.Enabled); + } + + [IntegrationTest] + public async Task UpdatesBranchProtectionForOrgRepo() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var update = new BranchProtectionSettingsUpdate( + new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), false, false), + new BranchProtectionPushRestrictionsUpdate(), + false); + + var protection = await _client.UpdateBranchProtection(repoOwner, repoName, "master", update); + + Assert.False(protection.RequiredStatusChecks.Strict); + Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.False(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Empty(protection.Restrictions.Teams); + Assert.Empty(protection.Restrictions.Users); + + Assert.False(protection.EnforceAdmins.Enabled); + } + + [IntegrationTest] + public async Task UpdatesBranchProtectionForOrgRepoWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var update = new BranchProtectionSettingsUpdate( + new BranchProtectionRequiredStatusChecksUpdate(false, new[] { "new" }), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), false, false), + new BranchProtectionPushRestrictionsUpdate(), + false); + + var protection = await _client.UpdateBranchProtection(repoId, "master", update); + + Assert.False(protection.RequiredStatusChecks.Strict); + Assert.Equal(1, protection.RequiredStatusChecks.Contexts.Count); + + Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); + Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); + Assert.False(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + + Assert.Empty(protection.Restrictions.Teams); + Assert.Empty(protection.Restrictions.Users); + + Assert.False(protection.EnforceAdmins.Enabled); + } + + public void Dispose() + { + if (_userRepoContext != null) + _userRepoContext.Dispose(); + + if (_orgRepoContext != null) + _orgRepoContext.Dispose(); + } + } + + public class TheDeleteBranchProtectionMethod + { + IGitHubClient _github; + IObservableRepositoryBranchesClient _client; + + public TheDeleteBranchProtectionMethod() + { + _github = Helper.GetAuthenticatedClient(); + _client = new ObservableRepositoryBranchesClient(_github); + } + + [IntegrationTest] + public async Task DeletesBranchProtection() + { + using (var context = await _github.CreateRepositoryWithProtectedBranch()) + { + var repoOwner = context.RepositoryOwner; + var repoName = context.RepositoryName; + var deleted = await _client.DeleteBranchProtection(repoOwner, repoName, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task DeletesBranchProtectionWithRepositoryId() + { + using (var context = await _github.CreateRepositoryWithProtectedBranch()) + { + var repoId = context.RepositoryId; + var deleted = await _client.DeleteBranchProtection(repoId, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task DeletesBranchProtectionForOrgRepo() + { + using (var context = await _github.CreateOrganizationRepositoryWithProtectedBranch()) + { + var repoOwner = context.RepositoryContext.RepositoryOwner; + var repoName = context.RepositoryContext.RepositoryName; + var deleted = await _client.DeleteBranchProtection(repoOwner, repoName, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task DeletesBranchProtectionForOrgRepoWithRepositoryId() + { + using (var context = await _github.CreateOrganizationRepositoryWithProtectedBranch()) + { + var repoId = context.RepositoryContext.RepositoryId; + var deleted = await _client.DeleteBranchProtection(repoId, "master"); + + Assert.True(deleted); + } + } + } + + public class TheGetReviewEnforcementMethod : IDisposable + { + IObservableRepositoryBranchesClient _client; + RepositoryContext _userRepoContext; + OrganizationRepositoryWithTeamContext _orgRepoContext; + + public TheGetReviewEnforcementMethod() + { + var github = Helper.GetAuthenticatedClient(); + _client = new ObservableRepositoryBranchesClient(github); + + _userRepoContext = github.CreateRepositoryWithProtectedBranch().Result; + _orgRepoContext = github.CreateOrganizationRepositoryWithProtectedBranch().Result; + } + + [IntegrationTest] + public async Task GetsReviewEnforcement() + { + var repoOwner = _userRepoContext.RepositoryOwner; + var repoName = _userRepoContext.RepositoryName; + var requiredReviews = await _client.GetReviewEnforcement(repoOwner, repoName, "master"); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task GetsReviewEnforcementWithRepositoryId() + { + var repoId = _userRepoContext.RepositoryId; + var requiredReviews = await _client.GetReviewEnforcement(repoId, "master"); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task GetsReviewEnforcementForOrgRepo() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var requiredReviews = await _client.GetReviewEnforcement(repoOwner, repoName, "master"); + + Assert.Equal(1, requiredReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, requiredReviews.DismissalRestrictions.Users.Count); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task GetsReviewEnforcementForOrgRepoWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var requiredReviews = await _client.GetReviewEnforcement(repoId, "master"); + + Assert.Equal(1, requiredReviews.DismissalRestrictions.Teams.Count); + Assert.Equal(0, requiredReviews.DismissalRestrictions.Users.Count); + Assert.True(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + public void Dispose() + { + if (_userRepoContext != null) + _userRepoContext.Dispose(); + + if (_orgRepoContext != null) + _orgRepoContext.Dispose(); + } + } + + public class TheUpdateReviewEnforcementMethod : IDisposable + { + IObservableRepositoryBranchesClient _client; + RepositoryContext _userRepoContext; + OrganizationRepositoryWithTeamContext _orgRepoContext; + + public TheUpdateReviewEnforcementMethod() + { + var github = Helper.GetAuthenticatedClient(); + _client = new ObservableRepositoryBranchesClient(github); + + _userRepoContext = github.CreateRepositoryWithProtectedBranch().Result; + _orgRepoContext = github.CreateOrganizationRepositoryWithProtectedBranch().Result; + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcement() + { + var repoOwner = _userRepoContext.RepositoryOwner; + var repoName = _userRepoContext.RepositoryName; + var update = new BranchProtectionRequiredReviewsUpdate(false, true); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoOwner, repoName, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementWithRepositoryId() + { + var repoId = _userRepoContext.RepositoryId; + var update = new BranchProtectionRequiredReviewsUpdate(false, true); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoId, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.True(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepo() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoOwner, repoName, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepoWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(false), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoId, "master", update); + + Assert.Null(requiredReviews.DismissalRestrictions); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepoWithAdminOnly() + { + var repoOwner = _orgRepoContext.RepositoryContext.RepositoryOwner; + var repoName = _orgRepoContext.RepositoryContext.RepositoryName; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(true), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoOwner, repoName, "master", update); + + Assert.Empty(requiredReviews.DismissalRestrictions.Teams); + Assert.Empty(requiredReviews.DismissalRestrictions.Users); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + [IntegrationTest] + public async Task UpdatesReviewEnforcementForOrgRepoWithAdminOnlyWithRepositoryId() + { + var repoId = _orgRepoContext.RepositoryContext.RepositoryId; + var update = new BranchProtectionRequiredReviewsUpdate( + new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(true), + false, + false); + + var requiredReviews = await _client.UpdateReviewEnforcement(repoId, "master", update); + + Assert.Empty(requiredReviews.DismissalRestrictions.Teams); + Assert.Empty(requiredReviews.DismissalRestrictions.Users); + Assert.False(requiredReviews.DismissStaleReviews); + Assert.False(requiredReviews.RequireCodeOwnerReviews); + } + + public void Dispose() + { + if (_userRepoContext != null) + _userRepoContext.Dispose(); + + if (_orgRepoContext != null) + _orgRepoContext.Dispose(); + } + } + + public class TheRemoveReviewEnforcementMethod + { + IGitHubClient _github; + IObservableRepositoryBranchesClient _client; + + public TheRemoveReviewEnforcementMethod() + { + _github = Helper.GetAuthenticatedClient(); + _client = new ObservableRepositoryBranchesClient(_github); + } + + [IntegrationTest] + public async Task RemovesReviewEnforcement() + { + using (var context = await _github.CreateRepositoryWithProtectedBranch()) + { + var repoOwner = context.RepositoryOwner; + var repoName = context.RepositoryName; + var deleted = await _client.RemoveReviewEnforcement(repoOwner, repoName, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task RemovesReviewEnforcementWithRepositoryId() + { + using (var context = await _github.CreateRepositoryWithProtectedBranch()) + { + var repoId = context.RepositoryId; + var deleted = await _client.RemoveReviewEnforcement(repoId, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task RemovesReviewEnforcementForOrgRepo() + { + using (var context = await _github.CreateOrganizationRepositoryWithProtectedBranch()) + { + var repoOwner = context.RepositoryContext.RepositoryOwner; + var repoName = context.RepositoryContext.RepositoryName; + var deleted = await _client.RemoveReviewEnforcement(repoOwner, repoName, "master"); + + Assert.True(deleted); + } + } + + [IntegrationTest] + public async Task RemovesReviewEnforcementForOrgRepoWithRepositoryId() + { + using (var context = await _github.CreateOrganizationRepositoryWithProtectedBranch()) + { + var repoId = context.RepositoryContext.RepositoryId; + var deleted = await _client.RemoveReviewEnforcement(repoId, "master"); + + Assert.True(deleted); + } + } + } + } +} diff --git a/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs b/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs index 2650890e..5ad8a378 100644 --- a/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs @@ -648,6 +648,153 @@ namespace Octokit.Tests.Clients } } + public class TheGetReviewEnforcementMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.GetReviewEnforcement("owner", "repo", "branch"); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "repos/owner/repo/branches/branch/protection/required_pull_request_reviews"), null, previewAcceptsHeader); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.GetReviewEnforcement(1, "branch"); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "repositories/1/branches/branch/protection/required_pull_request_reviews"), null, previewAcceptsHeader); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryBranchesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetReviewEnforcement(null, "repo", "branch")); + await Assert.ThrowsAsync(() => client.GetReviewEnforcement("owner", null, "branch")); + await Assert.ThrowsAsync(() => client.GetReviewEnforcement("owner", "repo", null)); + + await Assert.ThrowsAsync(() => client.GetReviewEnforcement(1, null)); + + await Assert.ThrowsAsync(() => client.GetReviewEnforcement("", "repo", "branch")); + await Assert.ThrowsAsync(() => client.GetReviewEnforcement("owner", "", "branch")); + await Assert.ThrowsAsync(() => client.GetReviewEnforcement("owner", "repo", "")); + + await Assert.ThrowsAsync(() => client.GetReviewEnforcement(1, "")); + } + } + + public class TheUpdateReviewEnforcementMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + var update = new BranchProtectionRequiredReviewsUpdate(false, false); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.UpdateReviewEnforcement("owner", "repo", "branch", update); + + connection.Received() + .Patch(Arg.Is(u => u.ToString() == "repos/owner/repo/branches/branch/protection/required_pull_request_reviews"), Arg.Any(), previewAcceptsHeader); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + var update = new BranchProtectionRequiredReviewsUpdate(false, false); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.UpdateReviewEnforcement(1, "branch", update); + + connection.Received() + .Patch(Arg.Is(u => u.ToString() == "repositories/1/branches/branch/protection/required_pull_request_reviews"), Arg.Any(), previewAcceptsHeader); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryBranchesClient(Substitute.For()); + + var update = new BranchProtectionRequiredReviewsUpdate(false, false); + + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement(null, "repo", "branch", update)); + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement("owner", null, "branch", update)); + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement("owner", "repo", null, update)); + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement("owner", "repo", "branch", null)); + + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement(1, null, update)); + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement(1, "branch", null)); + + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement("", "repo", "branch", update)); + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement("owner", "", "branch", update)); + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement("owner", "repo", "", update)); + + await Assert.ThrowsAsync(() => client.UpdateReviewEnforcement(1, "", update)); + } + } + + public class TheRemoveReviewEnforcementMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.RemoveReviewEnforcement("owner", "repo", "branch"); + + connection.Connection.Received() + .Delete(Arg.Is(u => u.ToString() == "repos/owner/repo/branches/branch/protection/required_pull_request_reviews"), Arg.Any(), previewAcceptsHeader); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.RemoveReviewEnforcement(1, "branch"); + + connection.Connection.Received() + .Delete(Arg.Is(u => u.ToString() == "repositories/1/branches/branch/protection/required_pull_request_reviews"), Arg.Any(), previewAcceptsHeader); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryBranchesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement(null, "repo", "branch")); + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement("owner", null, "branch")); + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement("owner", "repo", null)); + + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement(1, null)); + + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement("", "repo", "branch")); + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement("owner", "", "branch")); + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement("owner", "repo", "")); + + await Assert.ThrowsAsync(() => client.RemoveReviewEnforcement(1, "")); + } + } + public class TheGetAdminEnforcementMethod { [Fact] @@ -695,7 +842,7 @@ namespace Octokit.Tests.Clients } } - public class TheAddAdminEnforcement + public class TheAddAdminEnforcementMethod { [Fact] public void RequestsTheCorrectUrl() @@ -742,7 +889,7 @@ namespace Octokit.Tests.Clients } } - public class TheRemoveAdminEnforcement + public class TheRemoveAdminEnforcementMethod { [Fact] public void RequestsTheCorrectUrl() diff --git a/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs index 6ff3ed92..a0bbeed1 100644 --- a/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs @@ -624,6 +624,140 @@ namespace Octokit.Tests.Reactive } } + public class TheGetReviewEnforcementMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + + client.GetReviewEnforcement("owner", "repo", "branch"); + + gitHubClient.Repository.Branch.Received().GetReviewEnforcement("owner", "repo", "branch"); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + + client.GetReviewEnforcement(1, "branch"); + + gitHubClient.Repository.Branch.Received().GetReviewEnforcement(1, "branch"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryBranchesClient(Substitute.For()); + + Assert.Throws(() => client.GetReviewEnforcement(null, "repo", "branch")); + Assert.Throws(() => client.GetReviewEnforcement("owner", null, "branch")); + Assert.Throws(() => client.GetReviewEnforcement("owner", "repo", null)); + + Assert.Throws(() => client.GetReviewEnforcement(1, null)); + + Assert.Throws(() => client.GetReviewEnforcement("", "repo", "branch")); + Assert.Throws(() => client.GetReviewEnforcement("owner", "", "branch")); + Assert.Throws(() => client.GetReviewEnforcement("owner", "repo", "")); + + Assert.Throws(() => client.GetReviewEnforcement(1, "")); + } + } + + public class TheUpdateReviewEnforcement + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + var update = new BranchProtectionRequiredReviewsUpdate(false, false); + + client.UpdateReviewEnforcement("owner", "repo", "branch", update); + + gitHubClient.Repository.Branch.Received().UpdateReviewEnforcement("owner", "repo", "branch", update); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + var update = new BranchProtectionRequiredReviewsUpdate(false, false); + + client.UpdateReviewEnforcement(1, "branch", update); + + gitHubClient.Repository.Branch.Received().UpdateReviewEnforcement(1, "branch", update); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryBranchesClient(Substitute.For()); + var update = new BranchProtectionRequiredReviewsUpdate(false, false); + + Assert.Throws(() => client.UpdateReviewEnforcement(null, "repo", "branch", update)); + Assert.Throws(() => client.UpdateReviewEnforcement("owner", null, "branch", update)); + Assert.Throws(() => client.UpdateReviewEnforcement("owner", "repo", null, update)); + Assert.Throws(() => client.UpdateReviewEnforcement("owner", "repo", "branch", null)); + + Assert.Throws(() => client.UpdateReviewEnforcement(1, null, update)); + Assert.Throws(() => client.UpdateReviewEnforcement(1, "branch", null)); + + Assert.Throws(() => client.UpdateReviewEnforcement("", "repo", "branch", update)); + Assert.Throws(() => client.UpdateReviewEnforcement("owner", "", "branch", update)); + Assert.Throws(() => client.UpdateReviewEnforcement("owner", "repo", "", update)); + + Assert.Throws(() => client.UpdateReviewEnforcement(1, "", update)); + } + } + + public class TheRemoveReviewEnforcement + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + + client.RemoveReviewEnforcement("owner", "repo", "branch"); + + gitHubClient.Repository.Branch.Received().RemoveReviewEnforcement("owner", "repo", "branch"); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + + client.RemoveReviewEnforcement(1, "branch"); + + gitHubClient.Repository.Branch.Received().RemoveReviewEnforcement(1, "branch"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryBranchesClient(Substitute.For()); + + Assert.Throws(() => client.RemoveReviewEnforcement(null, "repo", "branch")); + Assert.Throws(() => client.RemoveReviewEnforcement("owner", null, "branch")); + Assert.Throws(() => client.RemoveReviewEnforcement("owner", "repo", null)); + + Assert.Throws(() => client.RemoveReviewEnforcement(1, null)); + + Assert.Throws(() => client.RemoveReviewEnforcement("", "repo", "branch")); + Assert.Throws(() => client.RemoveReviewEnforcement("owner", "", "branch")); + Assert.Throws(() => client.RemoveReviewEnforcement("owner", "repo", "")); + + Assert.Throws(() => client.RemoveReviewEnforcement(1, "")); + } + } + public class TheGetAdminEnforcementMethod { [Fact] diff --git a/Octokit/Clients/IRepositoryBranchesClient.cs b/Octokit/Clients/IRepositoryBranchesClient.cs index 9f0c2e56..20e15fad 100644 --- a/Octokit/Clients/IRepositoryBranchesClient.cs +++ b/Octokit/Clients/IRepositoryBranchesClient.cs @@ -301,6 +301,69 @@ namespace Octokit /// The contexts to remove Task> DeleteRequiredStatusChecksContexts(long repositoryId, string branch, IReadOnlyList contexts); + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + Task GetReviewEnforcement(string owner, string name, string branch); + + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + Task GetReviewEnforcement(long repositoryId, string branch); + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + Task UpdateReviewEnforcement(string owner, string name, string branch, BranchProtectionRequiredReviewsUpdate update); + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + Task UpdateReviewEnforcement(long repositoryId, string branch, BranchProtectionRequiredReviewsUpdate update); + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + Task RemoveReviewEnforcement(string owner, string name, string branch); + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + Task RemoveReviewEnforcement(long repositoryId, string branch); + /// /// Get admin enforcement of protected branch /// diff --git a/Octokit/Clients/RepositoryBranchesClient.cs b/Octokit/Clients/RepositoryBranchesClient.cs index e1ae5752..b0ec26da 100644 --- a/Octokit/Clients/RepositoryBranchesClient.cs +++ b/Octokit/Clients/RepositoryBranchesClient.cs @@ -505,6 +505,127 @@ namespace Octokit return ApiConnection.Delete>(ApiUrls.RepoRequiredStatusChecksContexts(repositoryId, branch), contexts, AcceptHeaders.ProtectedBranchesApiPreview); } + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public Task GetReviewEnforcement(string owner, string name, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return ApiConnection.Get(ApiUrls.RepoProtectedBranchReviewEnforcement(owner, name, branch), null, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Get required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public Task GetReviewEnforcement(long repositoryId, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return ApiConnection.Get(ApiUrls.RepoProtectedBranchReviewEnforcement(repositoryId, branch), null, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public Task UpdateReviewEnforcement(string owner, string name, string branch, BranchProtectionRequiredReviewsUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, nameof(update)); + + return ApiConnection.Patch(ApiUrls.RepoProtectedBranchReviewEnforcement(owner, name, branch), update, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Update required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public Task UpdateReviewEnforcement(long repositoryId, string branch, BranchProtectionRequiredReviewsUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, nameof(update)); + + return ApiConnection.Patch(ApiUrls.RepoProtectedBranchReviewEnforcement(repositoryId, branch), update, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public async Task RemoveReviewEnforcement(string owner, string name, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + var endpoint = ApiUrls.RepoProtectedBranchReviewEnforcement(owner, name, branch); + + try + { + var httpStatusCode = await Connection.Delete(endpoint, null, AcceptHeaders.ProtectedBranchesApiPreview).ConfigureAwait(false); + return httpStatusCode == HttpStatusCode.NoContent; + } + catch (NotFoundException) + { + return false; + } + } + + /// + /// Remove required pull request review enforcement of protected branch + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public async Task RemoveReviewEnforcement(long repositoryId, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + var endpoint = ApiUrls.RepoProtectedBranchReviewEnforcement(repositoryId, branch); + + try + { + var httpStatusCode = await Connection.Delete(endpoint, null, AcceptHeaders.ProtectedBranchesApiPreview).ConfigureAwait(false); + return httpStatusCode == HttpStatusCode.NoContent; + } + catch (NotFoundException) + { + return false; + } + } + /// /// Get admin enforcement of protected branch /// diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index ffe35494..f283982c 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -21,7 +21,7 @@ namespace Octokit public const string StarCreationTimestamps = "application/vnd.github.v3.star+json"; public const string IssueLockingUnlockingApiPreview = "application/vnd.github.the-key-preview+json"; - + public const string SquashCommitPreview = "application/vnd.github.polaris-preview+json"; public const string MigrationsApiPreview = "application/vnd.github.wyandotte-preview+json"; diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 96304dca..ef471b4a 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1908,6 +1908,27 @@ namespace Octokit return "repositories/{0}/branches/{1}/protection/required_status_checks/contexts".FormatUri(repositoryId, branchName); } + /// + /// Returns the for required_pull_request_reviews for a protected branch + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public static Uri RepoProtectedBranchReviewEnforcement(string owner, string name, string branchName) + { + return "repos/{0}/{1}/branches/{2}/protection/required_pull_request_reviews".FormatUri(owner, name, branchName); + } + + /// + /// Returns the for required_pull_request_reviews for a protected branch + /// + /// The Id of the repository + /// The name of the branch + public static Uri RepoProtectedBranchReviewEnforcement(long repositoryId, string branchName) + { + return "repositories/{0}/branches/{1}/protection/required_pull_request_reviews".FormatUri(repositoryId, branchName); + } + /// /// Returns the for admin enforcement for a protected branch /// diff --git a/Octokit/Models/Request/BranchProtectionUpdate.cs b/Octokit/Models/Request/BranchProtectionUpdate.cs index 6f9d0619..1d7181f6 100644 --- a/Octokit/Models/Request/BranchProtectionUpdate.cs +++ b/Octokit/Models/Request/BranchProtectionUpdate.cs @@ -19,22 +19,25 @@ namespace Octokit /// /// Create a BranchProtection update request /// - /// Specifies whether the protections applied to this branch also apply to repository admins - public BranchProtectionSettingsUpdate(bool enforceAdmins) + /// Specifies the requested status check settings. Pass null to disable status checks + public BranchProtectionSettingsUpdate(BranchProtectionRequiredStatusChecksUpdate requiredStatusChecks) { - EnforceAdmins = enforceAdmins; - RequiredStatusChecks = null; + RequiredStatusChecks = requiredStatusChecks; + RequiredPullRequestReviews = null; Restrictions = null; + EnforceAdmins = false; } /// /// Create a BranchProtection update request /// - /// Specifies the requested status check settings. Pass null to disable status checks - public BranchProtectionSettingsUpdate(BranchProtectionRequiredStatusChecksUpdate requiredStatusChecks) + /// Specifies if reviews are required to merge the pull request. Pass null to disable restrictions + public BranchProtectionSettingsUpdate(BranchProtectionRequiredReviewsUpdate requiredPullRequestReviews) { - RequiredStatusChecks = requiredStatusChecks; + RequiredStatusChecks = null; + RequiredPullRequestReviews = requiredPullRequestReviews; Restrictions = null; + EnforceAdmins = false; } /// @@ -44,7 +47,21 @@ namespace Octokit public BranchProtectionSettingsUpdate(BranchProtectionPushRestrictionsUpdate restrictions) { RequiredStatusChecks = null; + RequiredPullRequestReviews = null; Restrictions = restrictions; + EnforceAdmins = false; + } + + /// + /// Create a BranchProtection update request + /// + /// Specifies whether the protections applied to this branch also apply to repository admins + public BranchProtectionSettingsUpdate(bool enforceAdmins) + { + RequiredStatusChecks = null; + RequiredPullRequestReviews = null; + Restrictions = null; + EnforceAdmins = enforceAdmins; } /// @@ -53,17 +70,43 @@ namespace Octokit /// Specifies the requested status check settings. Pass null to disable status checks /// Specifies the requested push access restrictions (applies only to Organization owned repositories). Pass null to disable push access restrictions /// Specifies whether the protections applied to this branch also apply to repository admins + [Obsolete("This constructor will be removed for housekeeping purposes, please use another ctor")] public BranchProtectionSettingsUpdate(BranchProtectionRequiredStatusChecksUpdate requiredStatusChecks, BranchProtectionPushRestrictionsUpdate restrictions, bool enforceAdmins) { RequiredStatusChecks = requiredStatusChecks; + RequiredPullRequestReviews = null; Restrictions = restrictions; EnforceAdmins = enforceAdmins; } /// - /// Specifies whether the protections applied to this branch also apply to repository admins + /// Create a BranchProtection update request /// - public bool EnforceAdmins { get; set; } + /// Specifies the requested status check settings. Pass null to disable status checks + /// Specifies if reviews are required to merge the pull request. Pass null to disable required reviews + /// Specifies whether the protections applied to this branch also apply to repository admins + public BranchProtectionSettingsUpdate(BranchProtectionRequiredStatusChecksUpdate requiredStatusChecks, BranchProtectionRequiredReviewsUpdate requiredPullRequestReviews, bool enforceAdmins) + { + RequiredStatusChecks = requiredStatusChecks; + RequiredPullRequestReviews = requiredPullRequestReviews; + Restrictions = null; + EnforceAdmins = enforceAdmins; + } + + /// + /// Create a BranchProtection update request + /// + /// Specifies the requested status check settings. Pass null to disable status checks + /// Specifies if reviews are required to merge the pull request. Pass null to disable required reviews + /// Specifies the requested push access restrictions (applies only to Organization owned repositories). Pass null to disable push access restrictions + /// Specifies whether the protections applied to this branch also apply to repository admins + public BranchProtectionSettingsUpdate(BranchProtectionRequiredStatusChecksUpdate requiredStatusChecks, BranchProtectionRequiredReviewsUpdate requiredPullRequestReviews, BranchProtectionPushRestrictionsUpdate restrictions, bool enforceAdmins) + { + RequiredStatusChecks = requiredStatusChecks; + RequiredPullRequestReviews = requiredPullRequestReviews; + Restrictions = restrictions; + EnforceAdmins = enforceAdmins; + } /// /// Status check settings for the protected branch @@ -71,20 +114,32 @@ namespace Octokit [SerializeNull] public BranchProtectionRequiredStatusChecksUpdate RequiredStatusChecks { get; protected set; } + /// + /// Required Pull Request review settings for the protected branch + /// + [SerializeNull] + public BranchProtectionRequiredReviewsUpdate RequiredPullRequestReviews { get; protected set; } + /// /// Push access restrictions for the protected branch /// [SerializeNull] public BranchProtectionPushRestrictionsUpdate Restrictions { get; protected set; } + /// + /// Specifies whether the protections applied to this branch also apply to repository admins + /// + public bool EnforceAdmins { get; set; } + internal string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "StatusChecks: {0} Restrictions: {1} EnforceAdmins: {2}", - RequiredStatusChecks == null ? "disabled" : RequiredStatusChecks.DebuggerDisplay, - Restrictions == null ? "disabled" : Restrictions.DebuggerDisplay, + "RequiredStatusChecks: {0} RequiredPullRequestReviews: {1} Restrictions: {2} EnforceAdmins: {3}", + RequiredStatusChecks?.DebuggerDisplay ?? "disabled", + RequiredPullRequestReviews?.DebuggerDisplay ?? "disabled", + Restrictions?.DebuggerDisplay ?? "disabled", EnforceAdmins); } } @@ -236,4 +291,149 @@ namespace Octokit } } } + + /// + /// Specifies settings for requiring pull request reviews before merging a pull request. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class BranchProtectionRequiredReviewsUpdate + { + /// + /// Settings for requiring reviews before a pull request can be merged. + /// + /// Dismiss approved reviews automatically when a new commit is pushed. + /// Blocks merge until code owners have reviewed. + public BranchProtectionRequiredReviewsUpdate(bool dismissStaleReviews, bool requireCodeOwnerReviews) + { + DismissStaleReviews = dismissStaleReviews; + RequireCodeOwnerReviews = requireCodeOwnerReviews; + } + + /// + /// Settings for requiring reviews before a pull request can be merged. + /// + /// Specify which users and teams can dismiss pull request reviews (applies only to Organization owned repositories). + /// Dismiss approved reviews automatically when a new commit is pushed. + /// Blocks merge until code owners have reviewed. + public BranchProtectionRequiredReviewsUpdate(BranchProtectionRequiredReviewsDismissalRestrictionsUpdate dismissalRestrictions, bool dismissStaleReviews, bool requireCodeOwnerReviews) + { + Ensure.ArgumentNotNull(dismissalRestrictions, nameof(dismissalRestrictions)); + + DismissalRestrictions = dismissalRestrictions; + DismissStaleReviews = dismissStaleReviews; + RequireCodeOwnerReviews = requireCodeOwnerReviews; + } + + /// + /// Specify which users and teams can dismiss pull request reviews. + /// + public BranchProtectionRequiredReviewsDismissalRestrictionsUpdate DismissalRestrictions { get; protected set; } + + /// + /// Dismiss approved reviews automatically when a new commit is pushed. + /// + public bool DismissStaleReviews { get; protected set; } + + /// + /// Blocks merge until code owners have reviewed. + /// + public bool RequireCodeOwnerReviews { get; protected set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "DismissalRestrictions: {0} DismissStaleReviews: {1} RequireCodeOwnerReviews: {2}", + DismissalRestrictions?.DebuggerDisplay ?? "disabled", + DismissStaleReviews, + RequireCodeOwnerReviews); + } + } + } + + /// + /// Specifies whether review dismissal for the protected branch will be restricted to Admins, specified Teams/Users or unrestricted + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class BranchProtectionRequiredReviewsDismissalRestrictionsUpdate + { + /// + /// Specify whether dismissing reviews is restricted or not + /// + /// True to restrict review dismissal to Administrators, false to disable restrictions + public BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(bool enabled) + { + if (enabled) + { + // Empty Teams/Users list means restrictions are enabled with only Admins being able to dismiss reviews + Teams = new BranchProtectionTeamCollection(); + Users = new BranchProtectionUserCollection(); + } + else + { + // To disable the review dismissal restriction, the API requires an object with empty members to be passed + Teams = null; + Users = null; + } + } + + /// + /// Restrict dismissing reviews to the specified teams (in addition to Administrators). + /// + /// Teams allowed to dismiss reviews + public BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(BranchProtectionTeamCollection teams) + { + Ensure.ArgumentNotNull(teams, "teams"); + + Teams = teams; + Users = new BranchProtectionUserCollection(); + } + + /// + /// Restrict dismissing reviews to the specified people (in addition to Administrators). + /// + /// Users allowed to dismiss reviews + public BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(BranchProtectionUserCollection users) + { + Ensure.ArgumentNotNull(users, "users"); + + Teams = new BranchProtectionTeamCollection(); + Users = users; + } + + /// + /// Restrict dismissing reviews to the specified teams and people (in addition to Administrators). + /// + /// Teams allowed to dismiss reviews + /// Users allowed to dismiss reviews + public BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(BranchProtectionTeamCollection teams, BranchProtectionUserCollection users) + { + Ensure.ArgumentNotNull(teams, "teams"); + Ensure.ArgumentNotNull(users, "users"); + + Teams = teams; + Users = users; + } + + /// + /// Teams allowed to dismiss reviews + /// + public BranchProtectionTeamCollection Teams { get; private set; } + + /// + /// Users allowed to dismiss reviews + /// + public BranchProtectionUserCollection Users { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "Teams: {0} Users: {1}", + Teams == null ? "" : Teams.DebuggerDisplay, + Users == null ? "" : Users.DebuggerDisplay); + } + } + } } diff --git a/Octokit/Models/Response/BranchProtection.cs b/Octokit/Models/Response/BranchProtection.cs index 36e3111c..46625587 100644 --- a/Octokit/Models/Response/BranchProtection.cs +++ b/Octokit/Models/Response/BranchProtection.cs @@ -19,10 +19,12 @@ namespace Octokit { public BranchProtectionSettings() { } - public BranchProtectionSettings(BranchProtectionRequiredStatusChecks requiredStatusChecks, BranchProtectionPushRestrictions restrictions) + public BranchProtectionSettings(BranchProtectionRequiredStatusChecks requiredStatusChecks, BranchProtectionPushRestrictions restrictions, BranchProtectionRequiredReviews requiredPullRequestReviews, EnforceAdmins enforceAdmins) { RequiredStatusChecks = requiredStatusChecks; Restrictions = restrictions; + RequiredPullRequestReviews = requiredPullRequestReviews; + EnforceAdmins = enforceAdmins; } /// @@ -30,26 +32,33 @@ namespace Octokit /// public BranchProtectionRequiredStatusChecks RequiredStatusChecks { get; protected set; } + /// + /// Required review settings for the protected branch + /// + public BranchProtectionRequiredReviews RequiredPullRequestReviews { get; protected set; } + /// /// Push access restrictions for the protected branch /// public BranchProtectionPushRestrictions Restrictions { get; protected set; } + /// + /// Specifies whether the protections applied to this branch also apply to repository admins + /// + public EnforceAdmins EnforceAdmins { get; protected set; } + internal string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "StatusChecks: {0} Restrictions: {1}", - RequiredStatusChecks == null ? "disabled" : RequiredStatusChecks.DebuggerDisplay, - Restrictions == null ? "disabled" : Restrictions.DebuggerDisplay); + "RequiredStatusChecks: {0} RequiredPullRequestReviews {1} Restrictions: {2} EnforceAdmins: {3}", + RequiredStatusChecks?.DebuggerDisplay ?? "disabled", + RequiredPullRequestReviews?.DebuggerDisplay ?? "disabled", + Restrictions?.DebuggerDisplay ?? "disabled", + EnforceAdmins?.DebuggerDisplay ?? "disabled"); } } - - /// - /// Specifies whether the protections applied to this branch also apply to repository admins - /// - public EnforceAdmins EnforceAdmins { get; protected set; } } /// @@ -135,8 +144,79 @@ namespace Octokit { return string.Format(CultureInfo.InvariantCulture, "Teams: {0} Users: {1}", - Teams == null ? "" : String.Join(",", Teams), - Users == null ? "" : String.Join(",", Users)); + Teams == null ? "" : String.Join(",", Teams.Select(x => x.Name)), + Users == null ? "" : String.Join(",", Users.Select(x => x.Login))); + } + } + } + + /// + /// Specifies if pull request reviews are required before merging a pull request. Can optionally enforce the policy on repository administrators also. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class BranchProtectionRequiredReviews + { + public BranchProtectionRequiredReviews() { } + + /// + /// Specify which users and teams can dismiss pull request reviews. + /// + public BranchProtectionRequiredReviewsDismissalRestrictions DismissalRestrictions { get; protected set; } + + /// + /// Dismiss approved reviews automatically when a new commit is pushed. + /// + public bool DismissStaleReviews { get; protected set; } + + /// + /// Blocks merge until code owners have reviewed. + /// + public bool RequireCodeOwnerReviews { get; protected set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "DismissalRestrictions: {0} DismissStaleReviews: {1} RequireCodeOwnerReviews: {2}", + DismissalRestrictions?.DebuggerDisplay ?? "disabled", + DismissStaleReviews, + RequireCodeOwnerReviews); + } + } + } + + /// + /// Specifies people or teams allowed to push to the protected branch. Required status checks will still prevent these people from merging if the checks fail + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class BranchProtectionRequiredReviewsDismissalRestrictions + { + public BranchProtectionRequiredReviewsDismissalRestrictions() { } + + public BranchProtectionRequiredReviewsDismissalRestrictions(IReadOnlyList teams, IReadOnlyList users) + { + Teams = teams; + Users = users; + } + + /// + /// The specified Teams that can dismiss reviews + /// + public IReadOnlyList Teams { get; private set; } + + /// + /// The specified Users who can dismiss reviews + /// + public IReadOnlyList Users { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "Teams: {0} Users: {1}", + Teams == null ? "" : String.Join(",", Teams.Select(x => x.Name)), + Users == null ? "" : String.Join(",", Users.Select(x => x.Login))); } } }