diff --git a/Octokit.Reactive/Clients/IObservableActivitiesClient.cs b/Octokit.Reactive/Clients/IObservableActivitiesClient.cs index b3698f33..7be9ecd5 100644 --- a/Octokit.Reactive/Clients/IObservableActivitiesClient.cs +++ b/Octokit.Reactive/Clients/IObservableActivitiesClient.cs @@ -3,5 +3,7 @@ public interface IObservableActivitiesClient { IObservableEventsClient Events { get; } + IObservableWatchedClient Watched { get; } + IObservableStarredClient Starred { get; } } } \ No newline at end of file diff --git a/Octokit.Reactive/Clients/IObservableFollowersClient.cs b/Octokit.Reactive/Clients/IObservableFollowersClient.cs new file mode 100644 index 00000000..f959b833 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableFollowersClient.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reactive; +using System.Text; +using System.Threading.Tasks; + +namespace Octokit.Reactive +{ + public interface IObservableFollowersClient + { + /// + /// List the authenticated user’s followers + /// + /// + /// See the API documentation for more information. + /// + /// A of s that follow the authenticated user. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllForCurrent(); + + /// + /// List a user’s followers + /// + /// The login name for the user + /// + /// See the API documentation for more information. + /// + /// A of s that follow the passed user. + IObservable GetAll(string login); + + /// + /// List who the authenticated user is following + /// + /// + /// See the API documentation for more information. + /// + /// A of s that the authenticated user follows. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetFollowingForCurrent(); + + /// + /// List who a user is following + /// + /// The login name of the user + /// + /// See the API documentation for more information. + /// + /// A of s that the passed user follows. + IObservable GetFollowing(string login); + + /// + /// Check if the authenticated user follows another user + /// + /// The login name of the other user + /// + /// See the API documentation for more information. + /// + /// A bool representing the success of the operation. + IObservable IsFollowingForCurrent(string following); + + /// + /// Check if one user follows another user + /// + /// The login name of the user + /// The login name of the other user + /// + /// See the API documentation for more information. + /// + /// A bool representing the success of the operation. + IObservable IsFollowing(string login, string following); + + /// + /// Follow a user + /// + /// The login name of the user to follow + /// + /// See the API documentation for more information. + /// + /// A bool representing the success of the operation. + IObservable Follow(string login); + + /// + /// Unfollow a user + /// + /// The login name of the user to unfollow + /// + /// See the API documentation for more information. + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unfollow", + Justification = "Unfollow is consistent with the GitHub website")] + IObservable Unfollow(string login); + } +} diff --git a/Octokit.Reactive/Clients/IObservableMiscellaneousClient.cs b/Octokit.Reactive/Clients/IObservableMiscellaneousClient.cs index ecf58209..e06e3033 100644 --- a/Octokit.Reactive/Clients/IObservableMiscellaneousClient.cs +++ b/Octokit.Reactive/Clients/IObservableMiscellaneousClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Octokit.Reactive @@ -8,7 +7,7 @@ namespace Octokit.Reactive { [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Makes a network request")] - IObservable> GetEmojis(); + IObservable GetEmojis(); IObservable RenderRawMarkdown(string markdown); } diff --git a/Octokit.Reactive/Clients/IObservableWatchedClient.cs b/Octokit.Reactive/Clients/IObservableWatchedClient.cs new file mode 100644 index 00000000..7a2c9684 --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableWatchedClient.cs @@ -0,0 +1,61 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Octokit.Reactive +{ + public interface IObservableWatchedClient + { + /// + /// Retrieves all of the watchers for the passed repository + /// + /// The owner of the repository + /// The name of the repository + /// Thrown if the client is not authenticated + /// A of s watching the passed repository + IObservable GetAllWatchers(string owner, string name); + + /// + /// Retrieves all of the watched (ies) for the current user + /// + /// Thrown if the client is not authenticated + /// A of + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + IObservable GetAllForCurrent(); + + /// + /// Retrieves all of the (ies) watched by the specified user + /// + /// The login of the user + /// Thrown if the client is not authenticated + /// A watched by the specified user + IObservable GetAllForUser(string user); + + /// + /// Check if a repository is watched by the current authenticated user + /// + /// The owner of the repository + /// The name of the repository + /// Thrown if the client is not authenticated + /// A bool representing the success of the operation + IObservable CheckStarred(string owner, string name); + + /// + /// Stars a repository for the authenticated user. + /// + /// The owner of the repository to star + /// The name of the repository to star + /// A instance describing the new subscription to create + /// A bool representing the success of starring + IObservable WatchRepo(string owner, string name, NewSubscription newSubscription); + + /// + /// Unstars a repository for the authenticated user. + /// + /// The owner of the repository to unstar + /// The name of the repository to unstar + /// A bool representing the success of the operation + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="Unwatch", + Justification = "Unwatch is consistent with the GitHub website")] + IObservable UnwatchRepo(string owner, string name); + } +} \ No newline at end of file diff --git a/Octokit.Reactive/Clients/ObservableActivitiesClient.cs b/Octokit.Reactive/Clients/ObservableActivitiesClient.cs index c2150c59..4391c2d3 100644 --- a/Octokit.Reactive/Clients/ObservableActivitiesClient.cs +++ b/Octokit.Reactive/Clients/ObservableActivitiesClient.cs @@ -7,7 +7,13 @@ Ensure.ArgumentNotNull(client, "client"); Events = new ObservableEventsClient(client); + Watched = new ObservableWatchedClient(client); + Starred = new ObservableStarredClient(client); } public IObservableEventsClient Events { get; private set; } + + public IObservableWatchedClient Watched { get; private set; } + + public IObservableStarredClient Starred { get; private set; } } } \ No newline at end of file diff --git a/Octokit.Reactive/Clients/ObservableFollowersClient.cs b/Octokit.Reactive/Clients/ObservableFollowersClient.cs new file mode 100644 index 00000000..e9dadc2e --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableFollowersClient.cs @@ -0,0 +1,141 @@ +using System; +using System.Reactive; +using System.Reactive.Threading.Tasks; +using Octokit.Reactive.Internal; + +namespace Octokit.Reactive +{ + public class ObservableFollowersClient : IObservableFollowersClient + { + readonly IFollowersClient _client; + readonly IConnection _connection; + + /// + /// Initializes a new User Followers API client. + /// + /// An used to make the requests + public ObservableFollowersClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _client = client.User.Followers; + _connection = client.Connection; + } + + /// + /// List the authenticated user’s followers + /// + /// + /// See the API documentation for more information. + /// + /// A of s that follow the authenticated user. + public IObservable GetAllForCurrent() + { + return _connection.GetAndFlattenAllPages(ApiUrls.Followers()); + } + + /// + /// List a user’s followers + /// + /// The login name for the user + /// + /// See the API documentation for more information. + /// + /// A of s that follow the passed user. + public IObservable GetAll(string login) + { + Ensure.ArgumentNotNullOrEmptyString(login, "login"); + + return _connection.GetAndFlattenAllPages(ApiUrls.Followers(login)); + } + + /// + /// List who the authenticated user is following + /// + /// + /// See the API documentation for more information. + /// + /// A of s that the authenticated user follows. + public IObservable GetFollowingForCurrent() + { + return _connection.GetAndFlattenAllPages(ApiUrls.Following()); + } + + /// + /// List who a user is following + /// + /// The login name of the user + /// + /// See the API documentation for more information. + /// + /// A of s that the passed user follows. + public IObservable GetFollowing(string login) + { + Ensure.ArgumentNotNullOrEmptyString(login, "login"); + + return _connection.GetAndFlattenAllPages(ApiUrls.Following(login)); + } + + /// + /// Check if the authenticated user follows another user + /// + /// The login name of the other user + /// + /// See the API documentation for more information. + /// + /// A bool representing the success of the operation. + public IObservable IsFollowingForCurrent(string following) + { + Ensure.ArgumentNotNullOrEmptyString(following, "following"); + + return _client.IsFollowingForCurrent(following).ToObservable(); + } + + /// + /// Check if one user follows another user + /// + /// The login name of the user + /// The login name of the other user + /// + /// See the API documentation for more information. + /// + /// A bool representing the success of the operation. + public IObservable IsFollowing(string login, string following) + { + Ensure.ArgumentNotNullOrEmptyString(login, "login"); + Ensure.ArgumentNotNullOrEmptyString(following, "following"); + + return _client.IsFollowing(login, following).ToObservable(); + } + + /// + /// Follow a user + /// + /// The login name of the user to follow + /// + /// See the API documentation for more information. + /// + /// A bool representing the success of the operation. + public IObservable Follow(string login) + { + Ensure.ArgumentNotNullOrEmptyString(login, "login"); + + return _client.Follow(login).ToObservable(); + } + + /// + /// Unfollow a user + /// + /// The login name of the user to unfollow + /// + /// See the API documentation for more information. + /// + /// + public IObservable Unfollow(string login) + { + Ensure.ArgumentNotNullOrEmptyString(login, "login"); + + return _client.Unfollow(login).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/Clients/ObservableMiscellaneousClient.cs b/Octokit.Reactive/Clients/ObservableMiscellaneousClient.cs index 702e7ee1..33ce4891 100644 --- a/Octokit.Reactive/Clients/ObservableMiscellaneousClient.cs +++ b/Octokit.Reactive/Clients/ObservableMiscellaneousClient.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Reactive.Linq; using System.Reactive.Threading.Tasks; namespace Octokit.Reactive @@ -15,9 +15,9 @@ namespace Octokit.Reactive _client = client; } - public IObservable> GetEmojis() + public IObservable GetEmojis() { - return _client.GetEmojis().ToObservable(); + return _client.GetEmojis().ToObservable().SelectMany(e => e); } public IObservable RenderRawMarkdown(string markdown) diff --git a/Octokit.Reactive/Clients/ObservableSearchClient.cs b/Octokit.Reactive/Clients/ObservableSearchClient.cs index c417285e..71b1c038 100644 --- a/Octokit.Reactive/Clients/ObservableSearchClient.cs +++ b/Octokit.Reactive/Clients/ObservableSearchClient.cs @@ -61,7 +61,7 @@ namespace Octokit.Reactive public IObservable SearchCode(SearchCodeRequest request) { Ensure.ArgumentNotNull(request, "request"); - return _connection.GetAndFlattenAllPages(ApiUrls.SearchCode(), request.ToParametersDictionary()); + return _connection.GetAndFlattenAllPages(ApiUrls.SearchCode(), request.Parameters); } } } \ No newline at end of file diff --git a/Octokit.Reactive/Clients/ObservableStarredClient.cs b/Octokit.Reactive/Clients/ObservableStarredClient.cs index 7439c111..d94747c6 100644 --- a/Octokit.Reactive/Clients/ObservableStarredClient.cs +++ b/Octokit.Reactive/Clients/ObservableStarredClient.cs @@ -5,7 +5,7 @@ using Octokit.Reactive.Internal; namespace Octokit.Reactive { - public class ObservableStarredClient + public class ObservableStarredClient : IObservableStarredClient { private IStarredClient _client; private IConnection _connection; diff --git a/Octokit.Reactive/Clients/ObservableTreesClient.cs b/Octokit.Reactive/Clients/ObservableTreesClient.cs index 2e3ba031..47d24578 100644 --- a/Octokit.Reactive/Clients/ObservableTreesClient.cs +++ b/Octokit.Reactive/Clients/ObservableTreesClient.cs @@ -12,7 +12,7 @@ namespace Octokit.Reactive { Ensure.ArgumentNotNull(client, "client"); - _client = client.Tree; + _client = client.GitDatabase.Tree; } /// diff --git a/Octokit.Reactive/Clients/ObservableWatchedClient.cs b/Octokit.Reactive/Clients/ObservableWatchedClient.cs new file mode 100644 index 00000000..5a97c83d --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableWatchedClient.cs @@ -0,0 +1,103 @@ +using System; +using System.Reactive.Threading.Tasks; + +using Octokit.Reactive.Internal; + +namespace Octokit.Reactive +{ + public class ObservableWatchedClient : IObservableWatchedClient + { + private IWatchedClient _client; + private IConnection _connection; + + public ObservableWatchedClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _client = client.Activity.Watching; + _connection = client.Connection; + } + + /// + /// Retrieves all of the watchers for the passed repository + /// + /// The owner of the repository + /// The name of the repository + /// Thrown if the client is not authenticated + /// A of s watching the passed repository + public IObservable GetAllWatchers(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _connection.GetAndFlattenAllPages(ApiUrls.Watchers(owner, name)); + } + + /// + /// Retrieves all of the watched (ies) for the current user + /// + /// Thrown if the client is not authenticated + /// A of + public IObservable GetAllForCurrent() + { + return _connection.GetAndFlattenAllPages(ApiUrls.Watched()); + } + + /// + /// Retrieves all of the (ies) watched by the specified user + /// + /// The login of the user + /// Thrown if the client is not authenticated + /// A watched by the specified user + public IObservable GetAllForUser(string user) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + return _connection.GetAndFlattenAllPages(ApiUrls.WatchedByUser(user)); + } + + /// + /// Check if a repository is watched by the current authenticated user + /// + /// The owner of the repository + /// The name of the repository + /// Thrown if the client is not authenticated + /// A bool representing the success of the operation + public IObservable CheckStarred(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.CheckWatched(owner, name).ToObservable(); + } + + /// + /// Stars a repository for the authenticated user. + /// + /// The owner of the repository to star + /// The name of the repository to star + /// A instance describing the new subscription to create + /// A bool representing the success of starring + public IObservable WatchRepo(string owner, string name, NewSubscription newSubscription) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.WatchRepo(owner, name, newSubscription).ToObservable(); + } + + /// + /// Unstars a repository for the authenticated user. + /// + /// The owner of the repository to unstar + /// The name of the repository to unstar + /// A bool representing the success of the operation + public IObservable UnwatchRepo(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return _client.UnwatchRepo(owner, name).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj index aea65088..0f9a5321 100644 --- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj @@ -122,6 +122,10 @@ + + + + diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj index 6dfc2a78..5c093854 100644 --- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj +++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj @@ -131,6 +131,10 @@ + + + + diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj index ef06fde1..52331941 100644 --- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj @@ -126,6 +126,10 @@ + + + + diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index 876530db..6bc21ae3 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -77,6 +77,8 @@ + + @@ -123,9 +125,11 @@ + + diff --git a/Octokit.Tests.Integration/Clients/FollowersClientTests.cs b/Octokit.Tests.Integration/Clients/FollowersClientTests.cs new file mode 100644 index 00000000..4d7f1e0f --- /dev/null +++ b/Octokit.Tests.Integration/Clients/FollowersClientTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Octokit; +using Octokit.Tests.Integration; +using Xunit; + +public class FollowersClientTests : IDisposable +{ + readonly GitHubClient _github; + readonly User _currentUser; + + public FollowersClientTests() + { + _github = new GitHubClient(new ProductHeaderValue("OctokitTests")) + { + Credentials = Helper.Credentials + }; + _currentUser = _github.User.Current().Result; + } + + [IntegrationTest] + public async Task ReturnsUsersTheCurrentUserIsFollowing() + { + await _github.User.Followers.Follow("alfhenrik"); + + var following = await _github.User.Followers.GetFollowingForCurrent(); + + Assert.NotNull(following); + Assert.True(following.Any(f => f.Login == "alfhenrik")); + } + + [IntegrationTest] + public async Task ReturnsUsersTheUserIsFollowing() + { + var following = await _github.User.Followers.GetFollowing("alfhenrik"); + + Assert.NotNull(following); + Assert.NotEmpty(following); + } + + [IntegrationTest] + public async Task ReturnsUsersFollowingTheUser() + { + await _github.User.Followers.Follow("alfhenrik"); + + var followers = await _github.User.Followers.GetAll("alfhenrik"); + + Assert.NotEmpty(followers); + Assert.True(followers.Any(f => f.Login == _currentUser.Login)); + } + + [IntegrationTest] + public async Task ChecksIfIsFollowingUserWhenFollowingUser() + { + await _github.User.Followers.Follow("alfhenrik"); + + var isFollowing = await _github.User.Followers.IsFollowingForCurrent("alfhenrik"); + + Assert.True(isFollowing); + } + + [IntegrationTest] + public async Task ChecksIfIsFollowingUserWhenNotFollowingUser() + { + var isFollowing = await _github.User.Followers.IsFollowingForCurrent("alfhenrik"); + + Assert.False(isFollowing); + } + + [IntegrationTest] + public async Task FollowUserNotBeingFollowedByTheUser() + { + var result = await _github.User.Followers.Follow("alfhenrik"); + var following = await _github.User.Followers.GetFollowingForCurrent(); + + Assert.True(result); + Assert.NotEmpty(following); + Assert.True(following.Any(f => f.Login == "alfhenrik")); + } + + [IntegrationTest] + public async Task UnfollowUserBeingFollowedByTheUser() + { + await _github.User.Followers.Follow("alfhenrik"); + var followers = await _github.User.Followers.GetAll("alfhenrik"); + Assert.True(followers.Any(f => f.Login == _currentUser.Login)); + + await _github.User.Followers.Unfollow("alfhenrik"); + followers = await _github.User.Followers.GetAll("alfhenrik"); + Assert.False(followers.Any(f => f.Login == _currentUser.Login)); + } + + [IntegrationTest] + public async Task UnfollowUserNotBeingFollowedTheUser() + { + var followers = await _github.User.Followers.GetAll("alfhenrik"); + Assert.False(followers.Any(f => f.Login == _currentUser.Login)); + + await _github.User.Followers.Unfollow("alfhenrik"); + } + + public void Dispose() + { + _github.User.Followers.Unfollow("alfhenrik"); + } +} diff --git a/Octokit.Tests.Integration/Clients/UsersClientTests.cs b/Octokit.Tests.Integration/Clients/UsersClientTests.cs index 69d7c8ac..23de66ec 100644 --- a/Octokit.Tests.Integration/Clients/UsersClientTests.cs +++ b/Octokit.Tests.Integration/Clients/UsersClientTests.cs @@ -102,6 +102,7 @@ public class UsersClientTests public class TheGetEmailsMethod { + [IntegrationTest] public async Task RetrievesEmailsForUser() { var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) @@ -111,10 +112,9 @@ public class UsersClientTests var emails = await github.User.GetEmails(); - Assert.Equal(1, emails.Count()); - Assert.Equal("test-octowin@example.com", emails.First().Email); - Assert.True(emails.First().Primary); - Assert.False(emails.First().Verified); + 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 5cc50133..ed3e147d 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -71,6 +71,7 @@ + diff --git a/Octokit.Tests/Clients/FollowersClientTests.cs b/Octokit.Tests/Clients/FollowersClientTests.cs new file mode 100644 index 00000000..9c24b839 --- /dev/null +++ b/Octokit.Tests/Clients/FollowersClientTests.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using NSubstitute; +using Octokit.Internal; +using Octokit.Tests; +using Octokit.Tests.Helpers; +using Xunit; +using Xunit.Extensions; + +namespace Octokit.Tests.Clients +{ + /// + /// Client tests mostly just need to make sure they call the IApiConnection with the correct + /// relative Uri. No need to fake up the response. All *those* tests are in ApiConnectionTests.cs. + /// + public class FollowersClientTests + { + public class TheConstructor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new FollowersClient(null)); + } + } + + public class TheGetAllForCurrentMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + client.GetAllForCurrent(); + + connection.Received().GetAll( + Arg.Is(u => u.ToString() == "user/followers")); + } + } + + public class TheGetAllMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + client.GetAll("alfhenrik"); + + connection.Received().GetAll( + Arg.Is(u => u.ToString() == "users/alfhenrik/followers")); + } + + [Fact] + public void EnsureNonNullArguments() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + AssertEx.Throws(async () => await client.GetAll(null)); + AssertEx.Throws(async () => await client.GetAll("")); + } + } + + public class TheGetFollowingForCurrentMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + client.GetFollowingForCurrent(); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "user/following")); + } + } + + public class TheGetFollowingMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + client.GetFollowing("alfhenrik"); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == "users/alfhenrik/following")); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + AssertEx.Throws(async () => await client.GetFollowing(null)); + AssertEx.Throws(async () => await client.GetFollowing("")); + } + } + + public class TheIsFollowingForCurrentMethod + { + [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() == "user/following/alfhenrik"), + null, null).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new FollowersClient(apiConnection); + + var result = await client.IsFollowingForCurrent("alfhenrik"); + + 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() == "user/following/alfhenrik"), + null, null).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new FollowersClient(apiConnection); + + AssertEx.Throws(async () => await client.IsFollowingForCurrent("alfhenrik")); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + AssertEx.Throws(async () => await client.IsFollowingForCurrent(null)); + AssertEx.Throws(async () => await client.IsFollowingForCurrent("")); + } + } + + public class TheIsFollowingMethod + { + [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() == "users/alfhenrik/following/alfhenrik-test"), + null, null).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new FollowersClient(apiConnection); + + var result = await client.IsFollowing("alfhenrik", "alfhenrik-test"); + + 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() == "users/alfhenrik/following/alfhenrik-test"), + null, null).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new FollowersClient(apiConnection); + + AssertEx.Throws(async () => await client.IsFollowing("alfhenrik", "alfhenrik-test")); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + AssertEx.Throws(async () => await client.IsFollowing(null, "alfhenrik-test")); + AssertEx.Throws(async () => await client.IsFollowing("alfhenrik", null)); + AssertEx.Throws(async () => await client.IsFollowing("", "alfhenrik-text")); + AssertEx.Throws(async () => await client.IsFollowing("alfhenrik", "")); + } + + } + + public class TheFollowMethod + { + [Theory] + [InlineData(HttpStatusCode.NoContent, true)] + public async Task RequestsCorrectValueForStatusCode(HttpStatusCode status, bool expected) + { + var response = Task.Factory.StartNew>(() => + new ApiResponse { StatusCode = status }); + var connection = Substitute.For(); + connection.PutAsync(Arg.Is(u => u.ToString() == "user/following/alfhenrik"), + Args.Object).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new FollowersClient(apiConnection); + + var result = await client.Follow("alfhenrik"); + + Assert.Equal(expected, result); + } + + [Fact] + public async Task ThrowsExceptionForInvalidStatusCode() + { + var response = Task.Factory.StartNew>(() => + new ApiResponse { StatusCode = HttpStatusCode.Conflict }); + var connection = Substitute.For(); + connection.PutAsync(Arg.Is(u => u.ToString() == "user/following/alfhenrik"), + new { }).Returns(response); + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + var client = new FollowersClient(apiConnection); + + AssertEx.Throws(async () => await client.Follow("alfhenrik")); + } + + [Fact] + public async Task EnsureNonNullArguments() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + await AssertEx.Throws(async () => await client.Follow(null)); + await AssertEx.Throws(async () => await client.Follow("")); + } + } + + public class TheUnfollowMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + client.Unfollow("alfhenrik"); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "user/following/alfhenrik")); + } + + [Fact] + public async Task EnsureNonNullArguments() + { + var connection = Substitute.For(); + var client = new FollowersClient(connection); + + await AssertEx.Throws(async () => await client.Unfollow(null)); + await AssertEx.Throws(async () => await client.Unfollow("")); + } + } + } +} diff --git a/Octokit.Tests/Clients/IssueCommentsClientTests.cs b/Octokit.Tests/Clients/IssueCommentsClientTests.cs index 06a9ba63..e3091e90 100644 --- a/Octokit.Tests/Clients/IssueCommentsClientTests.cs +++ b/Octokit.Tests/Clients/IssueCommentsClientTests.cs @@ -143,6 +143,32 @@ public class IssueCommentsClientTests } } + public class TheDeleteMethod + { + [Fact] + public void DeletesCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssueCommentsClient(connection); + + client.Delete("fake", "repo", 42); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/comments/42")); + } + + [Fact] + public async Task EnsuresArgumentsNotNullOrEmpty() + { + var connection = Substitute.For(); + var client = new IssueCommentsClient(connection); + + await AssertEx.Throws(async () => await client.Delete(null, "name", 42)); + await AssertEx.Throws(async () => await client.Delete("", "name", 42)); + await AssertEx.Throws(async () => await client.Delete("owner", null, 42)); + await AssertEx.Throws(async () => await client.Delete("owner", "", 42)); + } + } + public class TheCtor { [Fact] diff --git a/Octokit.Tests/Clients/IssuesLabelsClientTests.cs b/Octokit.Tests/Clients/IssuesLabelsClientTests.cs index 11e65314..0ceef6de 100644 --- a/Octokit.Tests/Clients/IssuesLabelsClientTests.cs +++ b/Octokit.Tests/Clients/IssuesLabelsClientTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using NSubstitute; using Octokit.Tests.Helpers; using Xunit; +using System.Collections.Generic; namespace Octokit.Tests.Clients { @@ -79,5 +80,127 @@ namespace Octokit.Tests.Clients await AssertEx.Throws(async () => await client.Get("owner", null, "label")); } } + + public class TheAddToIssueMethod + { + readonly string[] labels = { "foo", "bar" }; + + [Fact] + public void PostsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesLabelsClient(connection); + + client.AddToIssue("fake", "repo", 42, labels); + + connection.Received().Post>(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/labels"), Arg.Any()); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new IssuesLabelsClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.AddToIssue(null, "name", 42, labels)); + await AssertEx.Throws(async () => await client.AddToIssue("owner", null, 42, labels)); + await AssertEx.Throws(async () => await client.AddToIssue("owner", "name", 42, null)); + } + } + + public class TheRemoveFromIssueMethod + { + [Fact] + public void DeleteCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesLabelsClient(connection); + + client.RemoveFromIssue("fake", "repo", 42, "label"); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/labels/label")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new IssuesLabelsClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.RemoveFromIssue(null, "name", 42, "label")); + await AssertEx.Throws(async () => await client.RemoveFromIssue("owner", null, 42, "label")); + await AssertEx.Throws(async () => await client.RemoveFromIssue("owner", "name", 42, null)); + } + } + + public class TheReplaceForIssueMethod + { + readonly string[] labels = { "foo", "bar" }; + + [Fact] + public void PutsToCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesLabelsClient(connection); + + client.ReplaceAllForIssue("fake", "repo", 42, labels); + + connection.Received().Put>(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/labels"), Arg.Any()); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new IssuesLabelsClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.ReplaceAllForIssue(null, "name", 42, labels)); + await AssertEx.Throws(async () => await client.ReplaceAllForIssue("owner", null, 42, labels)); + await AssertEx.Throws(async () => await client.ReplaceAllForIssue("owner", "name", 42, null)); + } + } + + public class TheRemoveAllFromIssueMethod + { + [Fact] + public void DeletesCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesLabelsClient(connection); + + client.RemoveAllFromIssue("fake", "repo", 42); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/labels")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new IssuesLabelsClient(Substitute.For()); + + await AssertEx.Throws(async () => await client.RemoveAllFromIssue(null, "name", 42)); + await AssertEx.Throws(async () => await client.RemoveAllFromIssue("owner", null, 42)); + } + } + + public class TheGetForMilestoneMethod + { + [Fact] + public void GetsCorrectUrl() + { + var connection = Substitute.For(); + var client = new IssuesLabelsClient(connection); + + client.GetForMilestone("fake", "repo", 42); + + connection.Received().GetAll