diff --git a/Octokit.Reactive/Clients/IObservableRepoCollaboratorsClient.cs b/Octokit.Reactive/Clients/IObservableRepoCollaboratorsClient.cs index a4bb8cc03f..79e84beb26 100644 --- a/Octokit.Reactive/Clients/IObservableRepoCollaboratorsClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepoCollaboratorsClient.cs @@ -90,6 +90,19 @@ public interface IObservableRepoCollaboratorsClient /// Thrown when a general API error occurs. IObservable Add(string owner, string name, string user); + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + IObservable Add(string owner, string name, string user, CollaboratorRequest permission); + /// /// Adds a new collaborator to the repository. /// @@ -101,6 +114,64 @@ public interface IObservableRepoCollaboratorsClient /// Thrown when a general API error occurs. IObservable Add(int repositoryId, string user); + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + IObservable Add(int repositoryId, string user, CollaboratorRequest permission); + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The username of the prospective collaborator + IObservable Invite(string owner, string name, string user); + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The username of the prospective collaborator + /// The permission to set. Only valid on organization-owned repositories. + IObservable Invite(string owner, string name, string user, CollaboratorRequest permission); + + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Username of the new collaborator + /// Thrown when a general API error occurs. + IObservable Invite(int repositoryId, string user); + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + IObservable Invite(int repositoryId, string user, CollaboratorRequest permission); + /// /// Deletes a collaborator from the repository. /// @@ -124,4 +195,4 @@ public interface IObservableRepoCollaboratorsClient /// Thrown when a general API error occurs. IObservable Delete(int repositoryId, string user); } -} +} \ No newline at end of file diff --git a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs index 866f4956bb..bfa144ea53 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs @@ -559,6 +559,7 @@ public interface IObservableRepositoriesClient /// See the Repository Deploy Keys API documentation for more information. /// IObservableRepositoryDeployKeysClient DeployKeys { get; } + /// /// A client for GitHub's Repository Pages API. /// @@ -566,5 +567,13 @@ public interface IObservableRepositoriesClient /// See the Repository Pages API documentation for more information. /// IObservableRepositoryPagesClient Page { get; } + + /// + /// A client for GitHub's Repository Invitations API. + /// + /// + /// See the Repository Invitations API documentation for more information. + /// + IObservableRepositoryInvitationsClient Invitation { get; } } } diff --git a/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs b/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs new file mode 100644 index 0000000000..b2e9910978 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableRepositoryInvitationsClient.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; + +namespace Octokit.Reactive +{ + public interface IObservableRepositoryInvitationsClient + { + /// + /// Accept a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation. + IObservable Accept(int invitationId); + + /// + /// Decline a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation. + IObservable Decline(int invitationId); + + /// + /// Deletes a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The id of the invitation. + IObservable Delete(int repositoryId, int invitationId); + + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllForCurrent(); + + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + IObservable GetAllForRepository(int repositoryId); + + /// + /// Updates a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The id of the invitation. + /// The permission to set. + /// + IObservable Edit(int repositoryId, int invitationId, InvitationUpdate permissions); + } +} diff --git a/Octokit.Reactive/Clients/ObservableRepoCollaboratorsClient.cs b/Octokit.Reactive/Clients/ObservableRepoCollaboratorsClient.cs index 583adc4bf7..3eea4a4236 100644 --- a/Octokit.Reactive/Clients/ObservableRepoCollaboratorsClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepoCollaboratorsClient.cs @@ -73,7 +73,7 @@ public IObservable GetAll(string owner, string name, ApiOptions options) Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(options, "options"); - + return _connection.GetAndFlattenAllPages(ApiUrls.RepoCollaborators(owner, name), options); } @@ -132,7 +132,7 @@ public IObservable IsCollaborator(int repositoryId, string user) /// Adds a new collaborator to the repository. /// /// - /// See the API documentation for more information. + /// See the API documentation for more information. /// /// The owner of the repository /// The name of the repository @@ -147,6 +147,27 @@ public IObservable Add(string owner, string name, string user) return _client.Add(owner, name, user).ToObservable(); } + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + public IObservable Add(string owner, string name, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + Ensure.ArgumentNotNull(permission, "permission"); + + return _client.Add(owner, name, user, permission).ToObservable(); + } + /// /// Adds a new collaborator to the repository. /// @@ -163,6 +184,95 @@ public IObservable Add(int repositoryId, string user) return _client.Add(repositoryId, user).ToObservable(); } + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + public IObservable Add(int repositoryId, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + Ensure.ArgumentNotNull(permission, "permission"); + + return _client.Add(repositoryId, user, permission).ToObservable(); + } + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The username of the prospective collaborator + public IObservable Invite(string owner, string name, string user) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + return _client.Invite(owner, name, user).ToObservable(); + } + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The username of the prospective collaborator + /// The permission to set. Only valid on organization-owned repositories. + public IObservable Invite(string owner, string name, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + Ensure.ArgumentNotNull(permission, "psermission"); + + return _client.Invite(owner, name, user, permission).ToObservable(); + } + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// The username of the prospective collaborator + + public IObservable Invite(int repositoryId, string user) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + return _client.Invite(repositoryId, user).ToObservable(); + } + + /// + /// Invites a user as a collaborator to a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// The username of the prospective collaborator + /// The permission to set. Only valid on organization-owned repositories. + public IObservable Invite(int repositoryId, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + Ensure.ArgumentNotNull(permission, "psermission"); + + return _client.Invite(repositoryId, user, permission).ToObservable(); + } + /// /// Deletes a collaborator from the repository. /// @@ -198,4 +308,4 @@ public IObservable Delete(int repositoryId, string user) return _client.Delete(repositoryId, user).ToObservable(); } } -} +} \ No newline at end of file diff --git a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs index c54774af41..86d5e842ba 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs @@ -33,6 +33,7 @@ public ObservableRepositoriesClient(IGitHubClient client) Content = new ObservableRepositoryContentsClient(client); Merging = new ObservableMergingClient(client); Page = new ObservableRepositoryPagesClient(client); + Invitation = new ObservableRepositoryInvitationsClient(client); } /// @@ -853,5 +854,13 @@ public IObservable Compare(string owner, string name, string @bas /// See the Repository Pages API documentation for more information. /// public IObservableRepositoryPagesClient Page { get; private set; } + + /// + /// A client for GitHub's Repository Invitations API. + /// + /// + /// See the Repository Invitations API documentation for more information. + /// + public IObservableRepositoryInvitationsClient Invitation { get; private set; } } } diff --git a/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs b/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs new file mode 100644 index 0000000000..c1c19215db --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableRepositoryInvitationsClient.cs @@ -0,0 +1,98 @@ +using Octokit.Reactive.Internal; +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Threading.Tasks; + +namespace Octokit.Reactive +{ + public class ObservableRepositoryInvitationsClient : IObservableRepositoryInvitationsClient + { + readonly IRepositoryInvitationsClient _client; + readonly IConnection _connection; + + public ObservableRepositoryInvitationsClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _client = client.Repository.Invitation; + _connection = client.Connection; + } + + /// + /// Accept a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation. + public IObservable Accept(int invitationId) + { + return _client.Accept(invitationId).ToObservable(); + } + + /// + /// Decline a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation. + public IObservable Decline(int invitationId) + { + return _client.Decline(invitationId).ToObservable(); + } + + /// + /// Deletes a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The id of the invitation. + public IObservable Delete(int repositoryId, int invitationId) + { + return _client.Delete(repositoryId, invitationId).ToObservable(); + } + + /// + /// Updates a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The id of the invitatio.n + /// The permission to set. + public IObservable Edit(int repositoryId, int invitationId, InvitationUpdate permissions) + { + Ensure.ArgumentNotNull(permissions, "persmissions"); + + return _client.Edit(repositoryId, invitationId, permissions).ToObservable(); + } + + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + public IObservable GetAllForCurrent() + { + return _connection.GetAndFlattenAllPages(ApiUrls.UserInvitations(), null, AcceptHeaders.InvitationsApiPreview, ApiOptions.None); + } + + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + public IObservable GetAllForRepository(int repositoryId) + { + return _connection.GetAndFlattenAllPages(ApiUrls.RepositoryInvitations(repositoryId), null, AcceptHeaders.InvitationsApiPreview, ApiOptions.None); + } + } +} diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj index c6c1d6c718..0964b1ffa7 100644 --- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj @@ -190,6 +190,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj index be8cc86b6d..6a5e5193d4 100644 --- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj +++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj @@ -206,6 +206,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj index 99353b995c..dcef4ea5f3 100644 --- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj @@ -202,6 +202,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index f3ab686b0c..cf8b2786ac 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -100,6 +100,7 @@ + @@ -134,6 +135,7 @@ + diff --git a/Octokit.Tests.Integration/Clients/RepositoryCollaboratorClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryCollaboratorClientTests.cs index d9400995b5..37a6710ab6 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryCollaboratorClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryCollaboratorClientTests.cs @@ -306,4 +306,26 @@ public async Task CheckDeleteMethodWithRepositoryId() } } } + + public class TheInviteMethod + { + [IntegrationTest] + public async Task CanInviteNewCollaborator() + { + 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, "octokat", permission); + + Assert.Equal("octokat", response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + } + } + } } \ No newline at end of file diff --git a/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs new file mode 100644 index 0000000000..7b2ee01591 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs @@ -0,0 +1,191 @@ +using Octokit; +using Octokit.Tests.Integration; +using Octokit.Tests.Integration.Helpers; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +public class RepositoryInvitationsClientTests +{ + const string owner = "octocat"; + const string name = "Hello-World"; + + public class TheGetAllForRepositoryMethod + { + [IntegrationTest] + public async Task CanGetAllInvitations() + { + 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, owner, permission); + + Assert.Equal(owner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + var invitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id); + + Assert.Equal(1, invitations.Count); + Assert.Equal(invitations[0].CreatedAt, response.CreatedAt); + Assert.Equal(invitations[0].Id, response.Id); + Assert.Equal(invitations[0].Invitee.Login, response.Invitee.Login); + Assert.Equal(invitations[0].Inviter.Login, response.Inviter.Login); + Assert.Equal(invitations[0].Permissions, response.Permissions); + Assert.Equal(invitations[0].Repository.Id, response.Repository.Id); + } + } + } + + public class TheGetAllForCurrentMethod + { + [IntegrationTest] + public async Task CanGetAllInvitations() + { + 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, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + var invitations = await github.Repository.Invitation.GetAllForCurrent(); + + Assert.True(invitations.Count >= 1); + Assert.NotNull(invitations.FirstOrDefault(i => i.CreatedAt == response.CreatedAt)); + Assert.NotNull(invitations.FirstOrDefault(i => i.Id == response.Id)); + Assert.NotNull(invitations.FirstOrDefault(i => i.Inviter.Login == response.Inviter.Login)); + Assert.NotNull(invitations.FirstOrDefault(i => i.Invitee.Login == response.Invitee.Login)); + Assert.NotNull(invitations.FirstOrDefault(i => i.Permissions == response.Permissions)); + Assert.NotNull(invitations.FirstOrDefault(i => i.Repository.Id == response.Repository.Id)); + } + } + } + + public class TheAcceptMethod + { + [IntegrationTest] + public async Task CanAcceptInvitation() + { + 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, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + // Accept the invitation + var accepted = await github.Repository.Invitation.Accept(response.Id); + + Assert.True(accepted); + } + } + } + + public class TheDeclineMethod + { + [IntegrationTest] + public async Task CanDeclineInvitation() + { + 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, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + // Decline the invitation + var declined = await github.Repository.Invitation.Decline(response.Id); + + Assert.True(declined); + } + } + } + + public class TheDeleteMethod + { + [IntegrationTest] + public async Task CanDeleteInvitation() + { + 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, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + var delete = await github.Repository.Invitation.Delete(response.Repository.Id, response.Id); + + Assert.True(delete); + } + } + } + + public class TheUpdateMethod + { + [IntegrationTest] + public async Task CanUpdateInvitation() + { + 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, context.RepositoryOwner, permission); + + Assert.Equal(context.RepositoryOwner, response.Invitee.Login); + Assert.Equal(InvitationPermissionType.Write, response.Permissions); + + var updatedInvitation = new InvitationUpdate(InvitationPermissionType.Admin); + + var update = await github.Repository.Invitation.Edit(response.Repository.Id, response.Id, updatedInvitation); + + Assert.Equal(updatedInvitation.Permissions, update.Permissions); + Assert.Equal(response.Id, update.Id); + Assert.Equal(response.Repository.Id, update.Repository.Id); + Assert.Equal(response.Invitee.Login, update.Invitee.Login); + Assert.Equal(response.Inviter.Login, update.Inviter.Login); + } + } + } +} + diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index a2c7d73ad7..4681be2473 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -109,6 +109,7 @@ + diff --git a/Octokit.Tests/Clients/RepoCollaboratorsClientTests.cs b/Octokit.Tests/Clients/RepoCollaboratorsClientTests.cs index 42bb95eb24..3fb20b53ee 100644 --- a/Octokit.Tests/Clients/RepoCollaboratorsClientTests.cs +++ b/Octokit.Tests/Clients/RepoCollaboratorsClientTests.cs @@ -229,6 +229,36 @@ public async Task EnsuresNonNullArguments() } } + public class TheInviteMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepoCollaboratorsClient(connection); + + var permission = new CollaboratorRequest(Permission.Push); + + client.Invite("owner", "test", "user1", permission); + connection.Received().Put(Arg.Is(u => u.ToString() == "repos/owner/test/collaborators/user1"), Arg.Is(permission), Arg.Any(), Arg.Is("application/vnd.github.swamp-thing-preview+json")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepoCollaboratorsClient(Substitute.For()); + var permission = new CollaboratorRequest(Permission.Push); + + await Assert.ThrowsAsync(() => client.Invite(null, "test", "user1", permission)); + await Assert.ThrowsAsync(() => client.Invite("", "test", "user1", permission)); + await Assert.ThrowsAsync(() => client.Invite("owner", null, "user1", permission)); + await Assert.ThrowsAsync(() => client.Invite("owner", "", "user1", permission)); + await Assert.ThrowsAsync(() => client.Invite("owner", "test", "", permission)); + await Assert.ThrowsAsync(() => client.Invite("owner", "test", null, permission)); + await Assert.ThrowsAsync(() => client.Invite("owner", "test", "user1", null)); + } + } + public class TheDeleteMethod { [Fact] @@ -260,8 +290,8 @@ public async Task EnsuresNonNullArguments() await Assert.ThrowsAsync(() => client.Delete("owner", null, "user1")); await Assert.ThrowsAsync(() => client.Delete("owner", "test", null)); await Assert.ThrowsAsync(() => client.Delete(1, null)); - - await Assert.ThrowsAsync(() => client.Delete("", "test", "user1"));; + + await Assert.ThrowsAsync(() => client.Delete("", "test", "user1")); ; await Assert.ThrowsAsync(() => client.Delete("owner", "", "user1")); await Assert.ThrowsAsync(() => client.Delete("owner", "test", "")); await Assert.ThrowsAsync(() => client.Delete(1, "")); diff --git a/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs b/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs new file mode 100644 index 0000000000..374ce74826 --- /dev/null +++ b/Octokit.Tests/Clients/RepositoryInvitationsClientTests.cs @@ -0,0 +1,112 @@ +using NSubstitute; +using Octokit; +using System; +using System.Threading.Tasks; +using Xunit; + +public class RepositoryInvitationsClientTests +{ + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new RepositoryInvitationsClient(null)); + } + } + + public class TheGetAllForRepositoryMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await client.GetAllForRepository(1); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "repositories/1/invitations"), "application/vnd.github.swamp-thing-preview+json"); + } + } + + public class TheGetAllForCurrentMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await client.GetAllForCurrent(); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "user/repository_invitations"), "application/vnd.github.swamp-thing-preview+json"); + } + } + + public class TheAcceptMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await client.Accept(1); + + connection.Connection.Received().Patch(Arg.Is(u => u.ToString() == "user/repository_invitations/1"), "application/vnd.github.swamp-thing-preview+json"); + } + } + + public class TheDeclineMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await client.Decline(1); + + connection.Connection.Received().Delete(Arg.Is(u => u.ToString() == "user/repository_invitations/1"), Arg.Any(), "application/vnd.github.swamp-thing-preview+json"); + } + } + + public class TheDeleteMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + + await client.Delete(1, 2); + + connection.Connection.Received().Delete(Arg.Is(u => u.ToString() == "repositories/1/invitations/2"), Arg.Any(), "application/vnd.github.swamp-thing-preview+json"); + } + } + + public class TheEditMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryInvitationsClient(connection); + var updatedInvitation = new InvitationUpdate(InvitationPermissionType.Read); + + await client.Edit(1, 2, updatedInvitation); + + connection.Received().Patch(Arg.Is(u => u.ToString() == "repositories/1/invitations/2"), Arg.Is(updatedInvitation), "application/vnd.github.swamp-thing-preview+json"); + } + + [Fact] + public async Task EnsureNonNullArguments() + { + var client = new RepositoryInvitationsClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Edit(1, 2, null)); + } + } + +} + diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 319e9c864e..a05b3f8092 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -104,6 +104,7 @@ + @@ -242,6 +243,7 @@ + diff --git a/Octokit.Tests/Reactive/ObservableRepoCollaboratorsClientTests.cs b/Octokit.Tests/Reactive/ObservableRepoCollaboratorsClientTests.cs index f4a51c0746..f036648c86 100644 --- a/Octokit.Tests/Reactive/ObservableRepoCollaboratorsClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableRepoCollaboratorsClientTests.cs @@ -67,7 +67,7 @@ public void RequestsCorrectUrl() _client.GetAll(owner, name); _githubClient.Connection.Received(1) .Get>(Arg.Is(u => u.ToString() == expectedUrl), - Arg.Is>(dictionary => dictionary.Count == 0), + Arg.Is>(dictionary => dictionary.Count == 0), Arg.Any()); } @@ -79,7 +79,7 @@ public void RequestsCorrectUrlWithRepositoryId() _client.GetAll(repositoryId); _githubClient.Connection.Received(1) .Get>(Arg.Is(u => u.ToString() == expectedUrl), - Arg.Is>(dictionary => dictionary.Count == 0), + Arg.Is>(dictionary => dictionary.Count == 0), Arg.Any()); } @@ -331,6 +331,80 @@ public void CallsCreateOnRegularDeploymentsClientWithRepositoryId() } } + public class TheInviteMethod + { + private readonly IGitHubClient _githubClient; + private IObservableRepoCollaboratorsClient _client; + + public TheInviteMethod() + { + _githubClient = Substitute.For(); + } + + private void SetupWithoutNonReactiveClient() + { + _client = new ObservableRepoCollaboratorsClient(_githubClient); + } + + private void SetupWithNonReactiveClient() + { + var collaboratorsClient = new RepoCollaboratorsClient(Substitute.For()); + _githubClient.Repository.Collaborator.Returns(collaboratorsClient); + _client = new ObservableRepoCollaboratorsClient(_githubClient); + } + + [Fact] + public void EnsuresNonNullArguments() + { + SetupWithNonReactiveClient(); + var permission = new CollaboratorRequest(Permission.Push); + + Assert.Throws(() => _client.Invite(null, "repo", "user", permission)); + Assert.Throws(() => _client.Invite("owner", null, "user", permission)); + Assert.Throws(() => _client.Invite("owner", "repo", null, permission)); + Assert.Throws(() => _client.Invite("owner", "repo", "user", null)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + SetupWithNonReactiveClient(); + var permission = new CollaboratorRequest(Permission.Push); + + Assert.Throws(() => _client.Invite("", "repo", "user", permission)); + Assert.Throws(() => _client.Invite("owner", "", "user", permission)); + Assert.Throws(() => _client.Invite("owner", "repo", "", permission)); + } + + [Fact] + public async Task EnsuresNonWhitespaceArguments() + { + SetupWithNonReactiveClient(); + var permission = new CollaboratorRequest(Permission.Push); + + await AssertEx.ThrowsWhenGivenWhitespaceArgument( + async whitespace => await _client.Invite(whitespace, "repo", "user", permission)); + await AssertEx.ThrowsWhenGivenWhitespaceArgument( + async whitespace => await _client.Invite("owner", whitespace, "user", permission)); + await AssertEx.ThrowsWhenGivenWhitespaceArgument( + async whitespace => await _client.Invite("owner", "repo", whitespace, permission)); + } + + [Fact] + public void CallsInviteOnRegularDeploymentsClient() + { + SetupWithoutNonReactiveClient(); + var permission = new CollaboratorRequest(Permission.Push); + + _client.Invite("owner", "repo", "user", permission); + + _githubClient.Repository.Collaborator.Received(1).Invite(Arg.Is("owner"), + Arg.Is("repo"), + Arg.Is("user"), + Arg.Is(permission)); + } + } + public class TheDeleteMethod { private readonly IGitHubClient _githubClient; @@ -411,4 +485,4 @@ public void CallsCreateOnRegularDeploymentsClientWithRepositoryId() } } } -} +} \ No newline at end of file diff --git a/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs new file mode 100644 index 0000000000..2a7d9bfe83 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableRepositoryInvitationsClientTests.cs @@ -0,0 +1,113 @@ +using NSubstitute; +using Octokit.Reactive; +using System; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservableRepositoryInvitationsClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new ObservableRepositoryInvitationsClient(null)); + } + } + + public class TheGetAllForRepositoryMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + client.GetAllForRepository(42); + + gitHub.Received().Repository.Invitation.GetAllForRepository(42); + } + } + + public class TheGetAllForCurrentMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + client.GetAllForCurrent(); + + gitHub.Received().Repository.Invitation.GetAllForCurrent(); + } + } + + public class TheAcceptMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + client.Accept(42); + + gitHub.Received().Repository.Invitation.Accept(42); + } + } + + public class TheDeclineMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + client.Decline(42); + + gitHub.Received().Repository.Invitation.Decline(42); + } + } + + public class TheDeleteMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + client.Delete(42, 43); + + gitHub.Received().Repository.Invitation.Delete(42, 43); + } + } + + public class TheEditMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + var update = new InvitationUpdate(InvitationPermissionType.Write); + + client.Edit(42, 43, update); + + gitHub.Received().Repository.Invitation.Edit(42, 43, update); + } + + [Fact] + public void EnsureNonNullArguments() + { + var gitHub = Substitute.For(); + var client = new ObservableRepositoryInvitationsClient(gitHub); + + Assert.Throws(() => client.Edit(1, 2, null)); + } + } + } +} \ No newline at end of file diff --git a/Octokit.sln b/Octokit.sln index 18068f4c0a..207fab8235 100644 --- a/Octokit.sln +++ b/Octokit.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit", "Octokit\Octokit.csproj", "{08DD4305-7787-4823-A53F-4D0F725A07F3}" EndProject @@ -96,4 +96,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {397C742D-291E-46BD-99A5-57BB6902FA7B} = {CEC9D451-6291-4EDF-971A-D398144FBF96} + EndGlobalSection EndGlobal diff --git a/Octokit/Clients/IRepoCollaboratorsClient.cs b/Octokit/Clients/IRepoCollaboratorsClient.cs index 6ffb832c5a..abc46874dc 100644 --- a/Octokit/Clients/IRepoCollaboratorsClient.cs +++ b/Octokit/Clients/IRepoCollaboratorsClient.cs @@ -92,6 +92,19 @@ public interface IRepoCollaboratorsClient /// Thrown when a general API error occurs. Task Add(string owner, string name, string user); + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + Task Add(string owner, string name, string user, CollaboratorRequest permission); + /// /// Adds a new collaborator to the repository. /// @@ -103,6 +116,66 @@ public interface IRepoCollaboratorsClient /// Thrown when a general API error occurs. Task Add(int repositoryId, string user); + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + Task Add(int repositoryId, string user, CollaboratorRequest permission); + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + /// The name of the user to invite. + /// Thrown when a general API error occurs. + Task Invite(string owner, string name, string user); + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + /// The name of the user to invite. + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + Task Invite(string owner, string name, string user, CollaboratorRequest permission); + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The name of the user to invite. + /// Thrown when a general API error occurs. + Task Invite(int repositoryId, string user); + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The name of the user to invite. + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + Task Invite(int repositoryId, string user, CollaboratorRequest permission); + /// /// Deletes a collaborator from the repository. /// @@ -126,4 +199,4 @@ public interface IRepoCollaboratorsClient /// Thrown when a general API error occurs. Task Delete(int repositoryId, string user); } -} +} \ No newline at end of file diff --git a/Octokit/Clients/IRepositoriesClient.cs b/Octokit/Clients/IRepositoriesClient.cs index 7261c45c7b..540fb95f45 100644 --- a/Octokit/Clients/IRepositoriesClient.cs +++ b/Octokit/Clients/IRepositoriesClient.cs @@ -630,5 +630,13 @@ public interface IRepositoriesClient /// See the Repository Pages API documentation for more information. /// IRepositoryPagesClient Page { get; } + + /// + /// A client for GitHub's Repository Invitations API. + /// + /// + /// See the Repository Invitations API documentation for more information. + /// + IRepositoryInvitationsClient Invitation { get; } } } diff --git a/Octokit/Clients/IRepositoryInvitationsClient.cs b/Octokit/Clients/IRepositoryInvitationsClient.cs new file mode 100644 index 0000000000..ace7a49ccc --- /dev/null +++ b/Octokit/Clients/IRepositoryInvitationsClient.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Invitations on a Repository. + /// + /// + /// See the Invitations API documentation for more details. + /// + public interface IRepositoryInvitationsClient + { + /// + /// Accept a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation + /// Thrown when a general API error occurs. + Task Accept(int invitationId); + + /// + /// Decline a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation + /// Thrown when a general API error occurs. + Task Decline(int invitationId); + + /// + /// Deletes a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// The id of the invitation + /// Thrown when a general API error occurs. + Task Delete(int repositoryId, int invitationId); + + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + /// Thrown when a general API error occurs. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllForCurrent(); + + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Thrown when a general API error occurs. + Task> GetAllForRepository(int repositoryId); + + /// + /// Updates a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// The id of the invitation + /// The permission for the collsborator + /// Thrown when a general API error occurs. + Task Edit(int repositoryId, int invitationId, InvitationUpdate permissions); + } +} diff --git a/Octokit/Clients/RepoCollaboratorsClient.cs b/Octokit/Clients/RepoCollaboratorsClient.cs index 3f241138d9..32ef1686fa 100644 --- a/Octokit/Clients/RepoCollaboratorsClient.cs +++ b/Octokit/Clients/RepoCollaboratorsClient.cs @@ -102,7 +102,7 @@ public async Task IsCollaborator(string owner, string name, string user) Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(user, "user"); - + try { var response = await Connection.Get(ApiUrls.RepoCollaborator(owner, name, user), null, null).ConfigureAwait(false); @@ -153,10 +153,38 @@ public Task Add(string owner, string name, string user) Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(user, "user"); - + return ApiConnection.Put(ApiUrls.RepoCollaborator(owner, name, user)); } + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + public async Task Add(string owner, string name, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + try + { + var response = await Connection.Put(ApiUrls.RepoCollaborator(owner, name, user), permission); + return response.HttpResponse.IsTrue(); + } + catch + { + return false; + } + } + /// /// Adds a new collaborator to the repository. /// @@ -173,6 +201,105 @@ public Task Add(int repositoryId, string user) return ApiConnection.Put(ApiUrls.RepoCollaborator(repositoryId, user)); } + /// + /// Adds a new collaborator to the repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Username of the new collaborator + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + public async Task Add(int repositoryId, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + try + { + var response = await Connection.Put(ApiUrls.RepoCollaborator(repositoryId, user), permission); + return response.HttpResponse.IsTrue(); + } + catch + { + return false; + } + } + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + /// The name of the user to invite. + /// Thrown when a general API error occurs. + public Task Invite(string owner, string name, string user) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + return ApiConnection.Put(ApiUrls.RepoCollaborator(owner, name, user), new object(), null, AcceptHeaders.InvitationsApiPreview); + } + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository. + /// The name of the repository. + /// The name of the user to invite. + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + public Task Invite(string owner, string name, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + Ensure.ArgumentNotNull(permission, "permission"); + + return ApiConnection.Put(ApiUrls.RepoCollaborator(owner, name, user), permission, null, AcceptHeaders.InvitationsApiPreview); + } + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The name of the user to invite. + /// Thrown when a general API error occurs. + public Task Invite(int repositoryId, string user) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + return ApiConnection.Put(ApiUrls.RepoCollaborator(repositoryId, user), new object(), null, AcceptHeaders.InvitationsApiPreview); + } + + /// + /// Invites a new collaborator to the repo + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository. + /// The name of the user to invite. + /// The permission to set. Only valid on organization-owned repositories. + /// Thrown when a general API error occurs. + public Task Invite(int repositoryId, string user, CollaboratorRequest permission) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + Ensure.ArgumentNotNull(permission, "permission"); + + return ApiConnection.Put(ApiUrls.RepoCollaborator(repositoryId, user), permission, null, AcceptHeaders.InvitationsApiPreview); + } + /// /// Deletes a collaborator from the repository. /// @@ -188,7 +315,7 @@ public Task Delete(string owner, string name, string user) Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(user, "user"); - + return ApiConnection.Delete(ApiUrls.RepoCollaborator(owner, name, user)); } @@ -208,4 +335,4 @@ public Task Delete(int repositoryId, string user) return ApiConnection.Delete(ApiUrls.RepoCollaborator(repositoryId, user)); } } -} +} \ No newline at end of file diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index e308d2dd6f..cb838c958f 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -36,6 +36,7 @@ public RepositoriesClient(IApiConnection apiConnection) : base(apiConnection) Merging = new MergingClient(apiConnection); Content = new RepositoryContentsClient(apiConnection); Page = new RepositoryPagesClient(apiConnection); + Invitation = new RepositoryInvitationsClient(apiConnection); } /// @@ -939,5 +940,13 @@ public Task GetBranch(string owner, string name, string branchName) /// See the Repository Pages API documentation for more information. /// public IRepositoryPagesClient Page { get; private set; } + + /// + /// A client for GitHub's Repository Invitations API. + /// + /// + /// See the Repository Invitations API documentation for more information. + /// + public IRepositoryInvitationsClient Invitation { get; private set; } } } diff --git a/Octokit/Clients/RepositoryInvitationsClient.cs b/Octokit/Clients/RepositoryInvitationsClient.cs new file mode 100644 index 0000000000..92a859ad9d --- /dev/null +++ b/Octokit/Clients/RepositoryInvitationsClient.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Octokit +{ + public class RepositoryInvitationsClient : ApiClient, IRepositoryInvitationsClient + { + public RepositoryInvitationsClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Accept a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation + /// Thrown when a general API error occurs. + public async Task Accept(int invitationId) + { + var endpoint = ApiUrls.UserInvitations(invitationId); + + try + { + var httpStatusCode = await Connection.Patch(endpoint, AcceptHeaders.InvitationsApiPreview).ConfigureAwait(false); + return httpStatusCode == HttpStatusCode.NoContent; + } + catch(NotFoundException) + { + return false; + } + } + + /// + /// Decline a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the invitation + /// Thrown when a general API error occurs. + public async Task Decline(int invitationId) + { + var endpoint = ApiUrls.UserInvitations(invitationId); + + try + { + var httpStatusCode = await Connection.Delete(endpoint, new object(), AcceptHeaders.InvitationsApiPreview).ConfigureAwait(false); + return httpStatusCode == HttpStatusCode.NoContent; + } + catch(NotFoundException) + { + return false; + } + } + + /// + /// Deletes a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// The id of the invitation + /// Thrown when a general API error occurs. + public async Task Delete(int repositoryId, int invitationId) + { + var endpoint = ApiUrls.RepositoryInvitations(repositoryId, invitationId); + + try + { + var httpStatusCode = await Connection.Delete(endpoint, new object(), AcceptHeaders.InvitationsApiPreview).ConfigureAwait(false); + return httpStatusCode == HttpStatusCode.NoContent; + } + catch(NotFoundException) + { + return false; + } + } + + /// + /// Gets all invitations for the current user. + /// + /// + /// See the API documentation for more information. + /// + /// Thrown when a general API error occurs. + public Task> GetAllForCurrent() + { + return ApiConnection.GetAll(ApiUrls.UserInvitations(), AcceptHeaders.InvitationsApiPreview); + } + + /// + /// Gets all the invitations on a repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Thrown when a general API error occurs. + public Task> GetAllForRepository(int repositoryId) + { + return ApiConnection.GetAll(ApiUrls.RepositoryInvitations(repositoryId), AcceptHeaders.InvitationsApiPreview); + } + + /// + /// Updates a repository invitation. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// The id of the invitation + /// The permission for the collsborator + /// Thrown when a general API error occurs. + public Task Edit(int repositoryId, int invitationId, InvitationUpdate permissions) + { + Ensure.ArgumentNotNull(permissions, "permissions"); + + return ApiConnection.Patch(ApiUrls.RepositoryInvitations(repositoryId, invitationId), permissions, AcceptHeaders.InvitationsApiPreview); + } + } +} diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index 169fed930c..6082aa3f21 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -35,6 +35,8 @@ public static class AcceptHeaders public const string DeploymentApiPreview = "application/vnd.github.ant-man-preview+json"; + public const string InvitationsApiPreview = "application/vnd.github.swamp-thing-preview+json"; + public const string PagesApiPreview = "application/vnd.github.mister-fantastic-preview+json"; } } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 2192aebd56..b07d732970 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1285,7 +1285,7 @@ public static Uri Blob(string owner, string name) { return Blob(owner, name, ""); } - + /// /// Returns the for a specific blob. /// @@ -3014,5 +3014,45 @@ public static Uri Reactions(int number) { return "reactions/{0}".FormatUri(number); } + + /// + /// Returns the for repository invitations. + /// + /// The id of the repository + /// The for repository invitations. + public static Uri RepositoryInvitations(int repositoryId) + { + return "repositories/{0}/invitations".FormatUri(repositoryId); + } + + /// + /// Returns the for a single repository invitation. + /// + /// The id of the repository + /// The id of the invitation + /// The for repository invitations. + public static Uri RepositoryInvitations(int repositoryId, int invitationId) + { + return "repositories/{0}/invitations/{1}".FormatUri(repositoryId, invitationId); + } + + /// + /// Returns the for invitations for the current user. + /// + /// The for invitations for the current user. + public static Uri UserInvitations() + { + return "user/repository_invitations".FormatUri(); + } + + /// + /// Returns the for a single invitation of the current user. + /// + /// The id of the invitation + /// The for invitations for the current user. + public static Uri UserInvitations(int invitationId) + { + return "user/repository_invitations/{0}".FormatUri(invitationId); + } } } diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 30c9634c8b..988d7f0d32 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -387,6 +387,20 @@ public Task Patch(Uri uri) return Connection.Patch(uri); } + /// + /// Updates the API resource at the specified URI. + /// + /// URI of the API resource to patch + /// Accept header to use for the API request + /// A for the request's execution. + public Task Patch(Uri uri, string accepts) + { + Ensure.ArgumentNotNull(uri, "uri"); + Ensure.ArgumentNotNull(accepts, "accepts"); + + return Connection.Patch(uri, accepts); + } + /// /// Updates the API resource at the specified URI. /// diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 7a8a203f18..c98b76e3ed 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -385,6 +385,21 @@ public async Task Patch(Uri uri) return response.HttpResponse.StatusCode; } + /// + /// Performs an asynchronous HTTP PATCH request. + /// + /// URI endpoint to send request to + /// Specifies accept response media type + /// representing the received HTTP response + public async Task Patch(Uri uri, string accepts) + { + Ensure.ArgumentNotNull(uri, "uri"); + Ensure.ArgumentNotNull(accepts, "accepts"); + + var response = await SendData(uri, new HttpMethod("PATCH"), null, accepts, null, CancellationToken.None).ConfigureAwait(false); + return response.HttpResponse.StatusCode; + } + /// /// Performs an asynchronous HTTP PUT request that expects an empty response. /// diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 0444e3dd36..72c4f742b7 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -259,6 +259,14 @@ public interface IApiConnection /// A for the request's execution. Task Patch(Uri uri); + /// + /// Updates the API resource at the specified URI. + /// + /// URI of the API resource to patch + /// Accept header to use for the API request + /// A for the request's execution. + Task Patch(Uri uri, string accepts); + /// /// Updates the API resource at the specified URI. /// diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 39ddac533c..1cfaa4e9b5 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -63,6 +63,14 @@ public interface IConnection : IApiInfoProvider /// representing the received HTTP response Task Patch(Uri uri); + /// + /// Performs an asynchronous HTTP PATCH request. + /// + /// URI endpoint to send request to + /// Specifies accepted response media types. + /// representing the received HTTP response + Task Patch(Uri uri, string accepts); + /// /// Performs an asynchronous HTTP PATCH request. /// Attempts to map the response body to an object of type diff --git a/Octokit/Models/Request/CollaboratorRequest.cs b/Octokit/Models/Request/CollaboratorRequest.cs new file mode 100644 index 0000000000..a6776d617c --- /dev/null +++ b/Octokit/Models/Request/CollaboratorRequest.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class CollaboratorRequest + { + /// + /// Used to set the permission for a collaborator. + /// + public CollaboratorRequest(Permission permissions) + { + Permission = permissions; + } + + /// + /// The permission to grant the collaborator on this repository. + /// + public Permission Permission { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Permission: {0}", Permission); + } + } + } +} diff --git a/Octokit/Models/Request/InvitationUpdate.cs b/Octokit/Models/Request/InvitationUpdate.cs new file mode 100644 index 0000000000..82c4a1b5e9 --- /dev/null +++ b/Octokit/Models/Request/InvitationUpdate.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; +using System.Globalization; + + +namespace Octokit +{ + /// + /// Used to update a invitation. + /// + /// + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class InvitationUpdate + { + public InvitationUpdate(InvitationPermissionType permission) + { + Permissions = permission; + } + + public InvitationPermissionType Permissions { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Permission: {0}", Permissions); + } + } + } +} diff --git a/Octokit/Models/Response/RepositoryInvitation.cs b/Octokit/Models/Response/RepositoryInvitation.cs new file mode 100644 index 0000000000..8ebb93c5ed --- /dev/null +++ b/Octokit/Models/Response/RepositoryInvitation.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + public enum InvitationPermissionType + { + Read, + Write, + Admin + } + + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RepositoryInvitation + { + public RepositoryInvitation() { } + + public RepositoryInvitation(int id, Repository repository, User invitee, User inviter, InvitationPermissionType permissions, DateTimeOffset createdAt, string url, string htmlUrl) + { + Id = id; + Repository = repository; + Invitee = invitee; + Inviter = inviter; + Permissions = permissions; + CreatedAt = createdAt; + Url = url; + HtmlUrl = htmlUrl; + } + + public int Id { get; protected set; } + + public Repository Repository { get; protected set; } + + public User Invitee { get; protected set; } + + public User Inviter { get; protected set; } + + public InvitationPermissionType Permissions { get; protected set; } + + public DateTimeOffset CreatedAt { get; protected set; } + + public string Url { get; protected set; } + + public string HtmlUrl { get; protected set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "Repository Invitation: Id: {0} Permissions: {1}", Id, Permissions); + } + } + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index b183eb0a5d..dda54a6619 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -479,6 +479,11 @@ + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 1fba6249a5..2c472d67d9 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -490,6 +490,11 @@ + + + + + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 0bdf885b25..16c0e17c21 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -486,6 +486,11 @@ + + + + + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 138d85c764..f28e8a25ac 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -476,6 +476,11 @@ + + + + + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index a19cc40a60..d807f7c109 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -483,6 +483,11 @@ + + + + + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index a27d92848d..4adbffc62a 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -62,6 +62,7 @@ + @@ -101,6 +102,7 @@ + @@ -132,6 +134,8 @@ + + @@ -203,6 +207,7 @@ +