diff --git a/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs b/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs index fdb89318db..2f339c8fca 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs @@ -43,6 +43,16 @@ public interface IObservableRepositoryInvitationsClient [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] IObservable GetAllForCurrent(); + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + /// Options for changing the API response + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllForCurrent(ApiOptions options); + /// /// Gets all the invitations on a repository. /// @@ -52,6 +62,16 @@ public interface IObservableRepositoryInvitationsClient /// The id of the repository IObservable GetAllForRepository(long repositoryId); + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// /// Options for changing the API response + IObservable GetAllForRepository(long repositoryId, ApiOptions options); + /// /// Updates a repository invitation. /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs b/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs index d8368e6d4a..25e565fb9f 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs @@ -80,7 +80,20 @@ public IObservable Edit(long repositoryId, int invitationI /// public IObservable GetAllForCurrent() { - return _connection.GetAndFlattenAllPages(ApiUrls.UserInvitations(), null, AcceptHeaders.InvitationsApiPreview, ApiOptions.None); + return GetAllForCurrent(ApiOptions.None); + } + + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + /// Options for changing the API response + public IObservable GetAllForCurrent(ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + return _connection.GetAndFlattenAllPages(ApiUrls.UserInvitations(), null, AcceptHeaders.InvitationsApiPreview, options); } /// @@ -92,7 +105,21 @@ public IObservable GetAllForCurrent() /// The id of the repository public IObservable GetAllForRepository(long repositoryId) { - return _connection.GetAndFlattenAllPages(ApiUrls.RepositoryInvitations(repositoryId), null, AcceptHeaders.InvitationsApiPreview, ApiOptions.None); + return GetAllForRepository(repositoryId, ApiOptions.None); + } + + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Options for changing the API response + public IObservable GetAllForRepository(long repositoryId, ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + return _connection.GetAndFlattenAllPages(ApiUrls.RepositoryInvitations(repositoryId), null, AcceptHeaders.InvitationsApiPreview, options); } } } diff --git a/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs index 17df3f2022..39b552b57e 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs @@ -1,4 +1,5 @@ -using Octokit; +using System.Collections.Generic; +using Octokit; using Octokit.Tests.Integration; using Octokit.Tests.Integration.Helpers; using System.Linq; @@ -12,7 +13,6 @@ public class TheGetAllForRepositoryMethod [IntegrationTest] public async Task CanGetAllInvitations() { - var collaborator = "octocat"; var github = Helper.GetAuthenticatedClient(); var repoName = Helper.MakeNameWithTimestamp("public-repo"); @@ -22,9 +22,9 @@ public async Task CanGetAllInvitations() var permission = new CollaboratorRequest(Permission.Push); // invite a collaborator - var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, collaborator, permission); + var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, context.RepositoryOwner, permission); - Assert.Equal(collaborator, response.Invitee.Login); + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); Assert.Equal(InvitationPermissionType.Write, response.Permissions); var invitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id); @@ -38,6 +38,115 @@ public async Task CanGetAllInvitations() Assert.Equal(invitations[0].Repository.Id, response.Repository.Id); } } + + [DualAccountTest] + public async Task ReturnsCorrectCountOfInvitationsWithStart() + { + var collaborator1 = Helper.CredentialsSecondUser.Login; + var github = Helper.GetAuthenticatedClient(); + var repoName = Helper.MakeNameWithTimestamp("public-repo"); + + using (var context = await github.CreateRepositoryContext(new NewRepository(repoName))) + { + var fixture = github.Repository.Collaborator; + var permission = new CollaboratorRequest(Permission.Push); + + // invite a collaborator + var response1 = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, collaborator1, permission); + + Assert.Equal(collaborator1, response1.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response1.Permissions); + + var response2 = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response2.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response2.Permissions); + + var options = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var invitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id, options); + + Assert.Equal(1, invitations.Count); + } + } + + [DualAccountTest] + public async Task ReturnsCorrectCountOfInvitationsWithoutStart() + { + var collaborator = Helper.CredentialsSecondUser.Login; + var github = Helper.GetAuthenticatedClient(); + var repoName = Helper.MakeNameWithTimestamp("public-repo"); + + using (var context = await github.CreateRepositoryContext(new NewRepository(repoName))) + { + var fixture = github.Repository.Collaborator; + var permission = new CollaboratorRequest(Permission.Push); + + // invite a collaborator + var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, collaborator, permission); + + Assert.Equal(collaborator, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + var options = new ApiOptions + { + PageSize = 1, + PageCount = 1, + }; + + var invitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id, options); + + Assert.Equal(1, invitations.Count); + } + } + + [IntegrationTest] + public async Task ReturnsDistinctInvitationsBasedOnStart() + { + var collaborator1 = Helper.CredentialsSecondUser.Login; + var github = Helper.GetAuthenticatedClient(); + var repoName = Helper.MakeNameWithTimestamp("public-repo"); + + using (var context = await github.CreateRepositoryContext(new NewRepository(repoName))) + { + var fixture = github.Repository.Collaborator; + var permission = new CollaboratorRequest(Permission.Push); + + // invite a collaborator + var response1 = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, collaborator1, permission); + + Assert.Equal(collaborator1, response1.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response1.Permissions); + + var response2 = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response2.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response2.Permissions); + + var startOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + var skipStartOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var firstInvitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id, startOptions); + var secondInvitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id, skipStartOptions); + + Assert.NotEqual(firstInvitations[0].Invitee.Login, secondInvitations[0].Invitee.Login); + } + } } public class TheGetAllForCurrentMethod @@ -70,6 +179,164 @@ public async Task CanGetAllInvitations() Assert.NotNull(invitations.FirstOrDefault(i => i.Repository.Id == response.Repository.Id)); } } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfInvitationsWithStart() + { + var github = Helper.GetAuthenticatedClient(); + var repoNames = Enumerable.Range(0, 2).Select(i => Helper.MakeNameWithTimestamp($"public-repo{i}")).ToList(); + + var contexts = new List(); + try + { + foreach (var repoName in repoNames) + { + contexts.Add(await github.CreateRepositoryContext(new NewRepository(repoName))); + } + var fixture = github.Repository.Collaborator; + var permission = new CollaboratorRequest(Permission.Push); + + // invite a collaborator to all repos + foreach (var context in contexts) + { + var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + } + + var startOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + + var invitations = await github.Repository.Invitation.GetAllForCurrent(startOptions); + Assert.Equal(1, invitations.Count); + } + finally + { + if (contexts != null) + { + foreach (var context in contexts) + { + context?.Dispose(); + } + } + } + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfInvitationsWithoutStart() + { + var github = Helper.GetAuthenticatedClient(); + var repoNames = Enumerable.Range(0, 2).Select(i => Helper.MakeNameWithTimestamp($"public-repo{i}")).ToList(); + + var contexts = new List(); + try + { + foreach (var repoName in repoNames) + { + contexts.Add(await github.CreateRepositoryContext(new NewRepository(repoName))); + } + var fixture = github.Repository.Collaborator; + var permission = new CollaboratorRequest(Permission.Push); + + // invite a collaborator to all repos + foreach (var context in contexts) + { + var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + } + + var startOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + + var invitations = await github.Repository.Invitation.GetAllForCurrent(startOptions); + Assert.Equal(1, invitations.Count); + } + finally + { + if (contexts != null) + { + foreach (var context in contexts) + { + context?.Dispose(); + } + } + } + } + + [IntegrationTest] + public async Task ReturnsDistinctInvitationsBasedOnStart() + { + var github = Helper.GetAuthenticatedClient(); + var repoNames = Enumerable.Range(0, 2).Select(i => Helper.MakeNameWithTimestamp($"public-repo{i}")).ToList(); + + var contexts = new List(); + try + { + foreach (var repoName in repoNames) + { + contexts.Add(await github.CreateRepositoryContext(new NewRepository(repoName))); + } + var fixture = github.Repository.Collaborator; + var permission = new CollaboratorRequest(Permission.Push); + + // invite a collaborator to all repos + foreach (var context in contexts) + { + var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + } + + var startOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1 + }; + + var skipStartOptions = new ApiOptions + { + PageSize = 1, + PageCount = 1, + StartPage = 2 + }; + + var firstInvitations = await github.Repository.Invitation.GetAllForCurrent(startOptions); + var secondInvitations = await github.Repository.Invitation.GetAllForCurrent(skipStartOptions); + + var invitations = firstInvitations.Concat(secondInvitations).ToArray(); + var invitationsLength = invitations.Length; + for (int i = 0; i < invitationsLength; i++) + { + for (int j = i+1; j < invitationsLength; j++) + { + Assert.NotEqual(invitations[i].Repository.FullName, invitations[j].Repository.FullName); + } + } + } + finally + { + if(contexts != null) + { + foreach (var context in contexts) + { + context?.Dispose(); + } + } + } + } } public class TheAcceptMethod diff --git a/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs b/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs index d103e51292..cfb23c44bf 100644 --- a/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs +++ b/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs @@ -2,6 +2,7 @@ using Octokit; using System; using System.Threading.Tasks; +using Octokit.Tests; using Xunit; public class RepositoryInvitationsClientTests @@ -17,6 +18,15 @@ public void EnsuresNonNullArguments() public class TheGetAllForRepositoryMethod { + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await Assert.ThrowsAsync(() => client.GetAllForRepository(1, null)); + } + [Fact] public async Task RequestsCorrectUrl() { @@ -25,12 +35,21 @@ public async Task RequestsCorrectUrl() await client.GetAllForRepository(1); - connection.Received().GetAll(Arg.Is(u => u.ToString() == "repositories/1/invitations"), "application/vnd.github.swamp-thing-preview+json"); + connection.Received().GetAll(Arg.Is(u => u.ToString() == "repositories/1/invitations"), null, "application/vnd.github.swamp-thing-preview+json", Args.ApiOptions); } } public class TheGetAllForCurrentMethod { + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await Assert.ThrowsAsync(() => client.GetAllForCurrent(null)); + } + [Fact] public async Task RequestsCorrectUrl() { @@ -39,7 +58,7 @@ public async Task RequestsCorrectUrl() await client.GetAllForCurrent(); - connection.Received().GetAll(Arg.Is(u => u.ToString() == "user/repository_invitations"), "application/vnd.github.swamp-thing-preview+json"); + connection.Received().GetAll(Arg.Is(u => u.ToString() == "user/repository_invitations"), null, "application/vnd.github.swamp-thing-preview+json", Args.ApiOptions); } } diff --git a/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs index 2a7d9bfe83..fedadb185b 100644 --- a/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs @@ -18,6 +18,15 @@ public void EnsuresNonNullArguments() public class TheGetAllForRepositoryMethod { + [Fact] + public void EnsuresNonNullArguments() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + Assert.Throws(() => client.GetAllForRepository(42, null)); + } + [Fact] public void RequestsCorrectUrl() { @@ -32,6 +41,15 @@ public void RequestsCorrectUrl() public class TheGetAllForCurrentMethod { + [Fact] + public void EnsuresNonNullArguments() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + Assert.Throws(() => client.GetAllForCurrent(null)); + } + [Fact] public void RequestsCorrectUrl() { diff --git a/Octokit/Clients/IRepositoryInvitationsClient.cs b/Octokit/Clients/IRepositoryInvitationsClient.cs index a24682fafa..d733471483 100644 --- a/Octokit/Clients/IRepositoryInvitationsClient.cs +++ b/Octokit/Clients/IRepositoryInvitationsClient.cs @@ -51,9 +51,19 @@ public interface IRepositoryInvitationsClient /// /// Thrown when a general API error occurs. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - [ExcludeFromPaginationApiOptionsConventionTest("TODO: Implement pagination for this method")] Task> GetAllForCurrent(); + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + /// Options for changing the API response + /// Thrown when a general API error occurs. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllForCurrent(ApiOptions options); + /// /// Gets all the invitations on a repository. /// @@ -62,9 +72,19 @@ public interface IRepositoryInvitationsClient /// /// The id of the repository /// Thrown when a general API error occurs. - [ExcludeFromPaginationApiOptionsConventionTest("TODO: Implement pagination for this method")] Task> GetAllForRepository(long repositoryId); + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Options for changing the API response + /// Thrown when a general API error occurs. + Task> GetAllForRepository(long repositoryId, ApiOptions options); + /// /// Updates a repository invitation. /// diff --git a/Octokit/Clients/RepositoryInvitationsClient.cs b/Octokit/Clients/RepositoryInvitationsClient.cs index 92a50cba6c..fa326a3547 100644 --- a/Octokit/Clients/RepositoryInvitationsClient.cs +++ b/Octokit/Clients/RepositoryInvitationsClient.cs @@ -91,7 +91,21 @@ public async Task Delete(long repositoryId, int invitationId) /// Thrown when a general API error occurs. public Task> GetAllForCurrent() { - return ApiConnection.GetAll(ApiUrls.UserInvitations(), AcceptHeaders.InvitationsApiPreview); + return GetAllForCurrent(ApiOptions.None); + } + + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + /// Options for changing the API response + /// Thrown when a general API error occurs. + public Task> GetAllForCurrent(ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + return ApiConnection.GetAll(ApiUrls.UserInvitations(), null, AcceptHeaders.InvitationsApiPreview, options); } /// @@ -104,7 +118,22 @@ public Task> GetAllForCurrent() /// Thrown when a general API error occurs. public Task> GetAllForRepository(long repositoryId) { - return ApiConnection.GetAll(ApiUrls.RepositoryInvitations(repositoryId), AcceptHeaders.InvitationsApiPreview); + return GetAllForRepository(repositoryId, ApiOptions.None); + } + + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Options for changing the API response + /// Thrown when a general API error occurs. + public Task> GetAllForRepository(long repositoryId, ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + return ApiConnection.GetAll(ApiUrls.RepositoryInvitations(repositoryId), null, AcceptHeaders.InvitationsApiPreview, options); } ///