From e1d618dcaa2d57a5535c878c103ee772566f70eb Mon Sep 17 00:00:00 2001 From: Haacked Date: Tue, 22 Oct 2013 17:24:35 -0700 Subject: [PATCH] Implement AssigneesClient Implement client to list and check available assignees for a repository --- Octokit.Reactive/IAssigneesClient.cs | 24 +++++ Octokit.Reactive/Octokit.Reactive.csproj | 1 + .../AssigneesClientTests.cs | 50 ++++++++++ .../Octokit.Tests.Integration.csproj | 1 + Octokit.Tests/Clients/AssigneesClientTests.cs | 97 +++++++++++++++++++ Octokit.Tests/Octokit.Tests.csproj | 1 + Octokit.Tests/OctokitRT.Tests.csproj | 1 + Octokit/Clients/ApiClient.cs | 7 ++ Octokit/Clients/AssigneesClient.cs | 55 +++++++++++ Octokit/Clients/IssuesClient.cs | 3 + Octokit/GitHubClient.cs | 17 ++-- Octokit/Helpers/ApiUrls.cs | 24 ++++- Octokit/Http/ApiConnection.cs | 2 +- Octokit/Http/IApiConnection.cs | 5 + Octokit/IAssigneesClient.cs | 25 +++++ Octokit/IIssuesClient.cs | 2 + Octokit/Octokit.csproj | 2 + Octokit/OctokitRT.csproj | 2 + 18 files changed, 309 insertions(+), 10 deletions(-) create mode 100644 Octokit.Reactive/IAssigneesClient.cs create mode 100644 Octokit.Tests.Integration/AssigneesClientTests.cs create mode 100644 Octokit.Tests/Clients/AssigneesClientTests.cs create mode 100644 Octokit/Clients/AssigneesClient.cs create mode 100644 Octokit/IAssigneesClient.cs diff --git a/Octokit.Reactive/IAssigneesClient.cs b/Octokit.Reactive/IAssigneesClient.cs new file mode 100644 index 00000000..14108139 --- /dev/null +++ b/Octokit.Reactive/IAssigneesClient.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Octokit +{ + public interface IAssigneesClient + { + /// + /// Gets all the available assignees (owner + collaborators) to which issues may be assigned. + /// + /// The owner of the repository + /// The name of the repository + /// + Task> GetForRepository(string owner, string name); + + /// + /// Checks to see if a user is an assignee for a repository. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the prospective assignee + /// + Task CheckAssignee(string owner, string name, string assignee); + } +} diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index 8a5f6aad..50f5a0a2 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -93,6 +93,7 @@ + diff --git a/Octokit.Tests.Integration/AssigneesClientTests.cs b/Octokit.Tests.Integration/AssigneesClientTests.cs new file mode 100644 index 00000000..768b2745 --- /dev/null +++ b/Octokit.Tests.Integration/AssigneesClientTests.cs @@ -0,0 +1,50 @@ +using System.Linq; +using System.Threading.Tasks; +using Octokit; +using Octokit.Tests.Integration; +using Xunit; + +public class AssigneesClientTests +{ + readonly IGitHubClient _gitHubClient; + readonly Repository _repository; + readonly string _owner; + + public AssigneesClientTests() + { + _gitHubClient = new GitHubClient("Test Runner User Agent") + { + Credentials = Helper.Credentials + }; + var repoName = Helper.MakeNameWithTimestamp("public-repo"); + + _repository = _gitHubClient.Repository.Create(new NewRepository { Name = repoName }).Result; + _owner = _repository.Owner.Login; + } + + [IntegrationTest] + public async Task CanCheckAssignees() + { + var isAssigned = await + _gitHubClient.Issue.Assignee.CheckAssignee(_owner, _repository.Name, "FakeHaacked"); + Assert.False(isAssigned); + + // Repository owner is always an assignee + isAssigned = await + _gitHubClient.Issue.Assignee.CheckAssignee(_owner, _repository.Name, _owner); + Assert.True(isAssigned); + } + + [IntegrationTest] + public async Task CanListAssignees() + { + // Repository owner is always an assignee + var assignees = await _gitHubClient.Issue.Assignee.GetForRepository(_owner, _repository.Name); + Assert.True(assignees.Any(u => u.Login == Helper.Credentials.Login)); + } + + public void Dispose() + { + Helper.DeleteRepo(_repository); + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 3095ffc8..6e331027 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -55,6 +55,7 @@ + diff --git a/Octokit.Tests/Clients/AssigneesClientTests.cs b/Octokit.Tests/Clients/AssigneesClientTests.cs new file mode 100644 index 00000000..254da851 --- /dev/null +++ b/Octokit.Tests/Clients/AssigneesClientTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using NSubstitute; +using Octokit; +using Octokit.Internal; +using Octokit.Tests.Helpers; +using Xunit; +using Xunit.Extensions; + +public class AssignessClientTests +{ + public class TheGetForRepositoryMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new AssigneesClient(connection); + + client.GetForRepository("fake", "repo"); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "/repos/fake/repo/assignees")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new AssigneesClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.GetForRepository(null, "name")); + await AssertEx.Throws(async () => await client.GetForRepository(null, "")); + await AssertEx.Throws(async () => await client.GetForRepository("owner", null)); + await AssertEx.Throws(async () => await client.GetForRepository("", null)); + } + } + + public class TheCheckAssigneeMethod + { + [Theory] + [InlineData(HttpStatusCode.NoContent, true)] + [InlineData(HttpStatusCode.NotFound, false)] + public async Task RequestsCorrectValueForStatusCode(HttpStatusCode status, bool expected) + { + var response = Task.Factory.StartNew>(() => + new ApiResponse { StatusCode = status }); + var connection = Substitute.For(); + connection.GetAsync(Arg.Is(u => u.ToString() == "/repos/foo/bar/assignees/cody"), + null, null).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new AssigneesClient(apiConnection); + + var result = await client.CheckAssignee("foo", "bar", "cody"); + + Assert.Equal(expected, result); + } + + [Fact] + public async Task ThrowsExceptionForInvalidStatusCode() + { + var response = Task.Factory.StartNew>(() => + new ApiResponse { StatusCode = HttpStatusCode.Conflict }); + var connection = Substitute.For(); + connection.GetAsync(Arg.Is(u => u.ToString() == "/repos/foo/bar/assignees/cody"), + null, null).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new AssigneesClient(apiConnection); + + AssertEx.Throws(async () => await client.CheckAssignee("foo", "bar", "cody")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new AssigneesClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.CheckAssignee(null, "name", "tweety")); + await AssertEx.Throws(async () => await client.CheckAssignee(null, "", "tweety")); + await AssertEx.Throws(async () => await client.CheckAssignee("owner", null, "tweety")); + await AssertEx.Throws(async () => await client.CheckAssignee("", null, "tweety")); + await AssertEx.Throws(async () => await client.CheckAssignee("owner", "name", null)); + await AssertEx.Throws(async () => await client.CheckAssignee("owner", "name", "")); + } + } + + public class TheCtor + { + [Fact] + public void EnsuresArgument() + { + Assert.Throws(() => new AssigneesClient(null)); + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index f0071c31..d71b72bc 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -60,6 +60,7 @@ + diff --git a/Octokit.Tests/OctokitRT.Tests.csproj b/Octokit.Tests/OctokitRT.Tests.csproj index 1217bdaf..d9352ac2 100644 --- a/Octokit.Tests/OctokitRT.Tests.csproj +++ b/Octokit.Tests/OctokitRT.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/Octokit/Clients/ApiClient.cs b/Octokit/Clients/ApiClient.cs index f774eb01..49a5d417 100644 --- a/Octokit/Clients/ApiClient.cs +++ b/Octokit/Clients/ApiClient.cs @@ -14,6 +14,7 @@ Ensure.ArgumentNotNull(apiConnection, "apiConnection"); ApiConnection = apiConnection; + Connection = apiConnection.Connection; } /// @@ -23,5 +24,11 @@ /// The API client's connection /// protected IApiConnection ApiConnection {get; private set;} + + /// + /// Returns the underlying used by the . This is useful + /// for requests that need to access the HTTP response and not just the response model. + /// + protected IConnection Connection { get; private set; } } } diff --git a/Octokit/Clients/AssigneesClient.cs b/Octokit/Clients/AssigneesClient.cs new file mode 100644 index 00000000..b0dea000 --- /dev/null +++ b/Octokit/Clients/AssigneesClient.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Octokit +{ + public class AssigneesClient : ApiClient, IAssigneesClient + { + public AssigneesClient(IApiConnection apiConnection) : base(apiConnection) + { + } + + /// + /// Gets all the available assignees (owner + collaborators) to which issues may be assigned. + /// + /// The owner of the repository + /// The name of the repository + /// + public async Task> GetForRepository(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return await ApiConnection.GetAll(ApiUrls.Assignees(owner, name)); + } + + /// + /// Checks to see if a user is an assignee for a repository. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the prospective assignee + /// + public async Task CheckAssignee(string owner, string name, string assignee) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(assignee, "assignee"); + + try + { + var response = await Connection.GetAsync(ApiUrls.CheckAssignee(owner, name, assignee), null, null); + if (response.StatusCode != HttpStatusCode.NotFound && response.StatusCode != HttpStatusCode.NoContent) + { + throw new ApiException("Invalid Status Code returned. Expected a 204 or a 404", response.StatusCode); + } + return response.StatusCode == HttpStatusCode.NoContent; + } + catch (NotFoundException) + { + return false; + } + } + } +} diff --git a/Octokit/Clients/IssuesClient.cs b/Octokit/Clients/IssuesClient.cs index fae73023..7f3cc63d 100644 --- a/Octokit/Clients/IssuesClient.cs +++ b/Octokit/Clients/IssuesClient.cs @@ -7,8 +7,11 @@ namespace Octokit { public IssuesClient(IApiConnection apiConnection) : base(apiConnection) { + Assignee = new AssigneesClient(apiConnection); } + public IAssigneesClient Assignee { get; private set; } + /// /// Gets a single Issue by number./// /// diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs index b08be3e7..f12deefc 100644 --- a/Octokit/GitHubClient.cs +++ b/Octokit/GitHubClient.cs @@ -34,15 +34,16 @@ namespace Octokit Ensure.ArgumentNotNull(connection, "connection"); Connection = connection; - Authorization = new AuthorizationsClient(new ApiConnection(connection)); - Issue = new IssuesClient(new ApiConnection(connection)); + var apiConnection = new ApiConnection(connection); + Authorization = new AuthorizationsClient(apiConnection); + Issue = new IssuesClient(apiConnection); Miscellaneous = new MiscellaneousClient(connection); - Notification = new NotificationsClient(new ApiConnection(connection)); - Organization = new OrganizationsClient(new ApiConnection(connection)); - Repository = new RepositoriesClient(new ApiConnection(connection)); - Release = new ReleasesClient(new ApiConnection(connection)); - User = new UsersClient(new ApiConnection(connection)); - SshKey = new SshKeysClient(new ApiConnection(connection)); + Notification = new NotificationsClient(apiConnection); + Organization = new OrganizationsClient(apiConnection); + Repository = new RepositoriesClient(apiConnection); + Release = new ReleasesClient(apiConnection); + User = new UsersClient(apiConnection); + SshKey = new SshKeysClient(apiConnection); } /// diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 4e943694..482cebe9 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -188,6 +188,28 @@ namespace Octokit return "/repos/{0}/{1}/issues/{2}".FormatUri(owner, name, number); } - + /// + /// Returns the that returns all of the assignees to which issues may be assigned. + /// + /// The owner of the repository + /// The name of the repository + /// + public static Uri Assignees(string owner, string name) + { + return "/repos/{0}/{1}/assignees".FormatUri(owner, name); + } + + /// + /// Returns the that returns a 204 if the login belongs to an assignee of the repository. + /// Otherwire returns a 404. + /// + /// The owner of the repository + /// The name of the repository + /// The login for the user + /// + public static Uri CheckAssignee(string owner, string name, string login) + { + return "/repos/{0}/{1}/assignees/{2}".FormatUri(owner, name, login); + } } } diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 44505790..d3b381dd 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -38,7 +38,7 @@ namespace Octokit /// /// Gets the connection for making HTTP requests. /// - protected IConnection Connection { get; private set; } + public IConnection Connection { get; private set; } /// /// Gets the API resource at the specified URI. diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index ff17b7ac..3c6ef682 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -11,6 +11,11 @@ namespace Octokit /// public interface IApiConnection { + /// + /// The underlying connection. + /// + IConnection Connection { get; } + /// /// Gets the API resource at the specified URI. /// diff --git a/Octokit/IAssigneesClient.cs b/Octokit/IAssigneesClient.cs new file mode 100644 index 00000000..24de3c03 --- /dev/null +++ b/Octokit/IAssigneesClient.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Octokit +{ + public interface IAssigneesClient + { + /// + /// Gets all the available assignees (owner + collaborators) to which issues may be assigned. + /// + /// The owner of the repository + /// The name of the repository + /// + Task> GetForRepository(string owner, string name); + + /// + /// Checks to see if a user is an assignee for a repository. + /// + /// The owner of the repository + /// The name of the repository + /// Username of the prospective assignee + /// + Task CheckAssignee(string owner, string name, string assignee); + } +} diff --git a/Octokit/IIssuesClient.cs b/Octokit/IIssuesClient.cs index 6307cfb2..9841931a 100644 --- a/Octokit/IIssuesClient.cs +++ b/Octokit/IIssuesClient.cs @@ -6,6 +6,8 @@ namespace Octokit { public interface IIssuesClient { + IAssigneesClient Assignee { get; } + /// /// Gets a single Issue by number. /// diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index ab75a36a..9382c47f 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -81,8 +81,10 @@ Properties\SolutionInfo.cs + + diff --git a/Octokit/OctokitRT.csproj b/Octokit/OctokitRT.csproj index 1c4806e2..d7fb2e80 100644 --- a/Octokit/OctokitRT.csproj +++ b/Octokit/OctokitRT.csproj @@ -110,6 +110,7 @@ + @@ -146,6 +147,7 @@ +