diff --git a/Octokit.Reactive/Clients/IObservableUserEmailsClient.cs b/Octokit.Reactive/Clients/IObservableUserEmailsClient.cs new file mode 100644 index 0000000000..95f67d9e95 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableUserEmailsClient.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics.CodeAnalysis; + + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's User Emails API. + /// + /// + /// See the User Emails API documentation for more information. + /// + public interface IObservableUserEmailsClient + { + /// + /// Gets all email addresses for the authenticated user. + /// + /// + /// http://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user + /// + /// The es for the authenticated user. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAll(); + + /// + /// Adds email addresses for the authenticated user. + /// + /// + /// http://developer.github.com/v3/users/emails/#add-email-addresses + /// + /// The email addresses to add. + /// Returns the added es. + IObservable Add(params string[] emailAddresses); + } +} diff --git a/Octokit.Reactive/Clients/IObservableUsersClient.cs b/Octokit.Reactive/Clients/IObservableUsersClient.cs index ef319da01d..465739a2ad 100644 --- a/Octokit.Reactive/Clients/IObservableUsersClient.cs +++ b/Octokit.Reactive/Clients/IObservableUsersClient.cs @@ -27,12 +27,5 @@ public interface IObservableUsersClient /// Thrown if the client is not authenticated. /// A IObservable Update(UserUpdate user); - - /// - /// Returns emails for the current user. - /// - /// - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - IObservable GetEmails(); } } diff --git a/Octokit.Reactive/Clients/ObservableUserEmailsClient.cs b/Octokit.Reactive/Clients/ObservableUserEmailsClient.cs new file mode 100644 index 0000000000..c50410aa29 --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableUserEmailsClient.cs @@ -0,0 +1,31 @@ +using Octokit.Reactive.Internal; +using System; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; + +namespace Octokit.Reactive +{ + public class ObservableUserEmailsClient : IObservableUserEmailsClient + { + readonly IUserEmailsClient _client; + readonly IConnection _connection; + + public ObservableUserEmailsClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _client = client.User.Email; + _connection = client.Connection; + } + + public IObservable GetAll() + { + return _connection.GetAndFlattenAllPages(ApiUrls.Emails()); + } + + public IObservable Add(params string[] emailAddresses) + { + return _client.Add(emailAddresses).ToObservable().SelectMany(a => a); + } + } +} diff --git a/Octokit.Reactive/Clients/ObservableUsersClient.cs b/Octokit.Reactive/Clients/ObservableUsersClient.cs index 5588e7376c..1648d8cc1d 100644 --- a/Octokit.Reactive/Clients/ObservableUsersClient.cs +++ b/Octokit.Reactive/Clients/ObservableUsersClient.cs @@ -1,20 +1,17 @@ using System; using System.Reactive.Threading.Tasks; -using Octokit.Reactive.Internal; namespace Octokit.Reactive { public class ObservableUsersClient : IObservableUsersClient { readonly IUsersClient _client; - readonly IConnection _connection; public ObservableUsersClient(IGitHubClient client) { Ensure.ArgumentNotNull(client, "client"); _client = client.User; - _connection = client.Connection; } /// @@ -50,14 +47,5 @@ public IObservable Update(UserUpdate user) return _client.Update(user).ToObservable(); } - - /// - /// Returns emails for the current user. - /// - /// - public IObservable GetEmails() - { - return _connection.GetAndFlattenAllPages(ApiUrls.Emails()); - } } } diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj index 2f21b5e66a..c3dfc8ca03 100644 --- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj @@ -122,6 +122,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj index cd6ab187ea..82cdcebb68 100644 --- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj +++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj @@ -131,6 +131,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj index 0f441e351f..d3e3504590 100644 --- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj @@ -126,6 +126,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index 6c5c7e71fc..f66a07f170 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -73,6 +73,7 @@ Properties\SolutionInfo.cs + @@ -122,6 +123,7 @@ + diff --git a/Octokit.Tests.Integration/Clients/UserEmailsClientTests.cs b/Octokit.Tests.Integration/Clients/UserEmailsClientTests.cs new file mode 100644 index 0000000000..347c1c9e36 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/UserEmailsClientTests.cs @@ -0,0 +1,21 @@ +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Integration.Clients +{ + public class UserEmailsClientTests + { + [IntegrationTest] + public async Task CanGetEmail() + { + var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) + { + Credentials = Helper.Credentials + }; + + var emails = await github.User.Email.GetAll(); + Assert.NotEmpty(emails); + } + } +} diff --git a/Octokit.Tests.Integration/Clients/UsersClientTests.cs b/Octokit.Tests.Integration/Clients/UsersClientTests.cs index 23de66ecfa..c4e1bfd288 100644 --- a/Octokit.Tests.Integration/Clients/UsersClientTests.cs +++ b/Octokit.Tests.Integration/Clients/UsersClientTests.cs @@ -99,22 +99,4 @@ public async Task FailsWhenAuthenticatedWithBadCredentials() Assert.Equal(HttpStatusCode.Unauthorized, e.StatusCode); } } - - public class TheGetEmailsMethod - { - [IntegrationTest] - public async Task RetrievesEmailsForUser() - { - var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) - { - Credentials = Helper.Credentials - }; - - var emails = await github.User.GetEmails(); - - Assert.NotEmpty(emails); - var email = emails.First(); - Assert.True(email.Primary); - } - } } diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 9587edf948..89dad30065 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -69,6 +69,7 @@ + @@ -81,6 +82,7 @@ + diff --git a/Octokit.Tests.Integration/Reactive/ObservableUserEmailsClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableUserEmailsClientTests.cs new file mode 100644 index 0000000000..fcb527b7c2 --- /dev/null +++ b/Octokit.Tests.Integration/Reactive/ObservableUserEmailsClientTests.cs @@ -0,0 +1,24 @@ +using Octokit.Reactive; +using System.Net.Http.Headers; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Integration +{ + public class ObservableUserEmailsClientTests + { + [IntegrationTest] + public async Task CanGetEmail() + { + var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) + { + Credentials = Helper.Credentials + }; + var client = new ObservableUserEmailsClient(github); + + var email = await client.GetAll(); + Assert.NotNull(email); + } + } +} diff --git a/Octokit.Tests/Clients/UserEmailsClientTests.cs b/Octokit.Tests/Clients/UserEmailsClientTests.cs new file mode 100644 index 0000000000..e699bc8a47 --- /dev/null +++ b/Octokit.Tests/Clients/UserEmailsClientTests.cs @@ -0,0 +1,66 @@ +using NSubstitute; +using Octokit.Tests.Helpers; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Clients +{ + public class UserEmailsClientTests + { + public class TheGetAllMethod + { + [Fact] + public void GetsCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserEmailsClient(connection); + + client.GetAll(); + + connection.Received(1) + .GetAll(Arg.Is(u => u.ToString() == "user/emails")); + } + } + + public class TheAddMethod + { + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new UserEmailsClient(connection); + + client.Add("octocat@github.com"); + + connection.Received(1) + .Post>(Arg.Is(u => u.ToString() == "user/emails"), Arg.Any()); + } + + [Fact] + public void EnsuresNonNullArgument() + { + var client = new UserEmailsClient(Substitute.For()); + Assert.Throws(() => client.Add(null)); + } + + [Fact] + public void EnsuresNoNullEmails() + { + var client = new UserEmailsClient(Substitute.For()); + Assert.Throws(() => client.Add("octokit@github.com", null)); + } + } + + public class TheCtor + { + [Fact] + public void EnsuresArguments() + { + Assert.Throws( + () => new UserEmailsClient(null)); + } + } + } +} diff --git a/Octokit.Tests/Clients/UsersClientTests.cs b/Octokit.Tests/Clients/UsersClientTests.cs index dcdc85de0f..783c49eb49 100644 --- a/Octokit.Tests/Clients/UsersClientTests.cs +++ b/Octokit.Tests/Clients/UsersClientTests.cs @@ -90,20 +90,5 @@ public async Task EnsuresArgumentsNotNull() await AssertEx.Throws(() => userEndpoint.Update(null)); } } - - public class TheGetEmailsMethod - { - [Fact] - public void SendsUpdateToCorrectUrl() - { - var endpoint = new Uri("user/emails", UriKind.Relative); - var client = Substitute.For(); - var usersClient = new UsersClient(client); - - usersClient.GetEmails(); - - client.Received().GetAll(endpoint, null); - } - } } } diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 28ad4e8c0c..93e91ef64a 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -86,6 +86,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/Octokit.Tests/Reactive/ObservableUserEmailsClientTests.cs b/Octokit.Tests/Reactive/ObservableUserEmailsClientTests.cs new file mode 100644 index 0000000000..49ea1e8f81 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableUserEmailsClientTests.cs @@ -0,0 +1,81 @@ +using NSubstitute; +using Octokit.Reactive; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Octokit.Tests +{ + public class ObservableUserEmailsClientTests + { + private static ObservableUserEmailsClient CreateFixtureWithNonReactiveClient() + { + var nonreactiveClient = new UserEmailsClient(Substitute.For()); + var github = Substitute.For(); + github.User.Email.Returns(nonreactiveClient); + return new ObservableUserEmailsClient(github); + } + + public class TheGetAllMethod + { + [Fact] + public void GetsCorrectUrl() + { + var expectedUri = new Uri("user/emails", UriKind.Relative); + var github = Substitute.For(); + var client = new ObservableUserEmailsClient(github); + + client.GetAll(); + + github.Connection.Received(1).GetAsync>(expectedUri); + } + } + + public class TheAddMethod + { + public IGitHubClient GitHubClient; + + public ObservableUserEmailsClient Client; + + [Fact] + public void CallsAddOnClient() + { + var github = Substitute.For(); + var client = new ObservableUserEmailsClient(github); + string email = "octo@github.com"; + + client.Add(email); + + github.User.Email.Received(1).Add(Arg.Is(email)); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = CreateFixtureWithNonReactiveClient(); + + Assert.Throws(() => client.Add(null)); + Assert.Throws(() => client.Add("octo@github.com", null)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + var client = CreateFixtureWithNonReactiveClient(); + + Assert.Throws(() => client.Add("")); + Assert.Throws(() => client.Add("octo@github.com", "")); + } + } + + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws( + () => new ObservableUserEmailsClient(null)); + } + } + } +} diff --git a/Octokit/Clients/IUserEmailsClient.cs b/Octokit/Clients/IUserEmailsClient.cs new file mode 100644 index 0000000000..9af896e31f --- /dev/null +++ b/Octokit/Clients/IUserEmailsClient.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's User Emails API. + /// + /// + /// See the User Emails API documentation for more information. + /// + public interface IUserEmailsClient + { + /// + /// Gets all email addresses for the authenticated user. + /// + /// + /// http://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user + /// + /// The es for the authenticated user. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAll(); + + /// + /// Adds email addresses for the authenticated user. + /// + /// + /// http://developer.github.com/v3/users/emails/#add-email-addresses + /// + /// The email addresses to add. + /// Returns the added es. + Task> Add(params string[] emailAddresses); + } +} diff --git a/Octokit/Clients/IUsersClient.cs b/Octokit/Clients/IUsersClient.cs index eddc0039ae..74762df1c6 100644 --- a/Octokit/Clients/IUsersClient.cs +++ b/Octokit/Clients/IUsersClient.cs @@ -12,6 +12,8 @@ namespace Octokit /// public interface IUsersClient { + IUserEmailsClient Email { get; } + /// /// Returns the user specified by the login. /// @@ -35,13 +37,6 @@ public interface IUsersClient /// A Task Update(UserUpdate user); - /// - /// Returns emails for the current user. - /// - /// - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - Task> GetEmails(); - /// /// A client for GitHub's User Followers API /// diff --git a/Octokit/Clients/UserEmailsClient.cs b/Octokit/Clients/UserEmailsClient.cs new file mode 100644 index 0000000000..a5a23629b5 --- /dev/null +++ b/Octokit/Clients/UserEmailsClient.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's User Emails API. + /// + /// + /// See the User Emails API documentation for more information. + /// + public class UserEmailsClient : ApiClient, IUserEmailsClient + { + /// + /// Instantiates a new GitHub User Emails API client. + /// + /// An API connection + public UserEmailsClient(IApiConnection apiConnection) + : base(apiConnection) + { } + + /// + /// Gets all email addresses for the authenticated user. + /// + /// + /// http://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user + /// + /// The es for the authenticated user. + public Task> GetAll() + { + return ApiConnection.GetAll(ApiUrls.Emails()); + } + + /// + /// Adds email addresses for the authenticated user. + /// + /// + /// http://developer.github.com/v3/users/emails/#add-email-addresses + /// + /// The email addresses to add. + /// Returns the added es. + public Task> Add(params string[] emailAddresses) + { + Ensure.ArgumentNotNull(emailAddresses, "emailAddresses"); + if (emailAddresses.Any(String.IsNullOrWhiteSpace)) + throw new ArgumentException("Cannot contain null, empty or whitespace values", "emailAddresses"); + + return ApiConnection.Post>(ApiUrls.Emails(), emailAddresses); + } + } +} \ No newline at end of file diff --git a/Octokit/Clients/UsersClient.cs b/Octokit/Clients/UsersClient.cs index c68ece71f5..55cdd384e8 100644 --- a/Octokit/Clients/UsersClient.cs +++ b/Octokit/Clients/UsersClient.cs @@ -23,9 +23,12 @@ public class UsersClient : ApiClient, IUsersClient /// An API connection public UsersClient(IApiConnection apiConnection) : base(apiConnection) { + Email = new UserEmailsClient(apiConnection); Followers = new FollowersClient(apiConnection); } + public IUserEmailsClient Email { get; private set; } + /// /// Returns the user specified by the login. /// @@ -61,15 +64,6 @@ public Task Update(UserUpdate user) return ApiConnection.Patch(_userEndpoint, user); } - /// - /// Returns emails for the current user. - /// - /// - public Task> GetEmails() - { - return ApiConnection.GetAll(ApiUrls.Emails(), null); - } - /// /// A client for GitHub's User Followers API /// diff --git a/Octokit/Models/Response/EmailAddress.cs b/Octokit/Models/Response/EmailAddress.cs index 50e88d9dae..fa4e0b31ad 100644 --- a/Octokit/Models/Response/EmailAddress.cs +++ b/Octokit/Models/Response/EmailAddress.cs @@ -1,11 +1,40 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + namespace Octokit { + /// + /// A users email + /// + [DebuggerDisplay("DebuggerDisplay,nq")] public class EmailAddress { + /// + /// The email address + /// public string Email { get; set; } + /// + /// true if the email is verified; otherwise false + /// public bool Verified { get; set; } + /// + /// true if this is the users primary email; otherwise false + /// public bool Primary { get; set; } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", + Justification="Used by DebuggerDisplayAttribute")] + private string DebuggerDisplay + { + get + { + return String.Format(CultureInfo.InvariantCulture, + "EmailAddress: Email: {0}; Primary: {1}, Verified: {2}", Email, Primary, Verified); + } + } } } \ No newline at end of file diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 44aff67cb9..920e4a8dc3 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -252,6 +252,8 @@ + + diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index e2adfa432a..3f8356fcf6 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -258,6 +258,8 @@ + + diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 3728fac7fe..75d85a12f2 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -253,6 +253,8 @@ + + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index e59d2c0bf7..01b1ba2f1d 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -253,6 +253,8 @@ + + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 43c8793826..fcb0616040 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -54,6 +54,8 @@ Properties\SolutionInfo.cs + +