From b62357fe46a9e5f45de8d85faa15e76bca9c1a89 Mon Sep 17 00:00:00 2001 From: Nigel Sampson Date: Thu, 30 Jan 2014 11:26:19 +1300 Subject: [PATCH] Watched Client and Unit Tests --- Octokit.Tests/Clients/WatchedClientTests.cs | 132 ++++++++++++++++++++ Octokit.Tests/Octokit.Tests.csproj | 1 + Octokit/Clients/ActivitiesClient.cs | 2 + Octokit/Clients/IActivitiesClient.cs | 1 + Octokit/Clients/IWatchedClient.cs | 66 ++++++++++ Octokit/Clients/WatchedClient.cs | 126 +++++++++++++++++++ Octokit/Helpers/ApiUrls.cs | 39 ++++++ Octokit/Models/Request/NewSubscription.cs | 17 +++ Octokit/Models/Response/Subscription.cs | 37 ++++++ Octokit/Octokit-Mono.csproj | 4 + Octokit/Octokit-netcore45.csproj | 4 + Octokit/Octokit.csproj | 4 + 12 files changed, 433 insertions(+) create mode 100644 Octokit.Tests/Clients/WatchedClientTests.cs create mode 100644 Octokit/Clients/IWatchedClient.cs create mode 100644 Octokit/Clients/WatchedClient.cs create mode 100644 Octokit/Models/Request/NewSubscription.cs create mode 100644 Octokit/Models/Response/Subscription.cs diff --git a/Octokit.Tests/Clients/WatchedClientTests.cs b/Octokit.Tests/Clients/WatchedClientTests.cs new file mode 100644 index 00000000..f1103898 --- /dev/null +++ b/Octokit.Tests/Clients/WatchedClientTests.cs @@ -0,0 +1,132 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using NSubstitute; +using Octokit.Internal; +using Xunit; +using Xunit.Extensions; + +namespace Octokit.Tests.Clients +{ + public class WatchedClientTests + { + public class TheGetAllForCurrentMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var endpoint = new Uri("user/subscriptions", UriKind.Relative); + var connection = Substitute.For(); + var client = new WatchedClient(connection); + + client.GetAllForCurrent(); + + connection.Received().GetAll(endpoint); + } + } + + public class TheGetAllForUserMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var endpoint = new Uri("users/banana/subscriptions", UriKind.Relative); + var connection = Substitute.For(); + var client = new WatchedClient(connection); + + client.GetAllForUser("banana"); + + connection.Received().GetAll(endpoint); + } + } + + public class TheGetAllWatchersForRepoMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var endpoint = new Uri("repos/fight/club/subscribers", UriKind.Relative); + var connection = Substitute.For(); + var client = new WatchedClient(connection); + + client.GetAllWatchers("fight", "club"); + + connection.Received().GetAll(endpoint); + } + } + + public class TheCheckWatchedMethod + { + [Fact] + public async Task ReturnsTrueOnValidResult() + { + var endpoint = new Uri("repos/fight/club/subscription", UriKind.Relative); + + var connection = Substitute.For(); + connection.Get(endpoint).Returns(Task.FromResult(new Subscription())); + + var client = new WatchedClient(connection); + + var watched = await client.CheckWatched("fight", "club"); + + Assert.True(watched); + } + + [Fact] + public async Task ReturnsFalseOnNotFoundException() + { + var endpoint = new Uri("repos/fight/club/subscription", UriKind.Relative); + + var connection = Substitute.For(); + var response = new ApiResponse { StatusCode = HttpStatusCode.NotFound }; + connection.Get(endpoint).Returns(x => { throw new NotFoundException(response); }); + + var client = new WatchedClient(connection); + + var watched = await client.CheckWatched("fight", "club"); + + Assert.False(watched); + } + } + + public class TheWatchRepoMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var endpoint = new Uri("repos/fight/club/subscription", UriKind.Relative); + var connection = Substitute.For(); + var client = new WatchedClient(connection); + + var newSubscription = new NewSubscription(); + client.WatchRepo("fight", "club", newSubscription); + + connection.Received().Put(endpoint, newSubscription); + } + } + + public class TheUnwatchRepoMethod + { + [Theory] + [InlineData(HttpStatusCode.NoContent, true)] + [InlineData(HttpStatusCode.NotFound, false)] + [InlineData(HttpStatusCode.OK, false)] + public async Task ReturnsCorrectResultBasedOnStatus(HttpStatusCode status, bool expected) + { + var response = Task.Factory.StartNew(() => status); + + var connection = Substitute.For(); + connection.DeleteAsync(Arg.Is(u => u.ToString() == "repos/yes/no/subscription")) + .Returns(response); + + var apiConnection = Substitute.For(); + apiConnection.Connection.Returns(connection); + + var client = new WatchedClient(apiConnection); + var result = await client.UnwatchRepo("yes", "no"); + + Assert.Equal(expected, result); + } + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index faaeb8dc..e0e8b473 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -86,6 +86,7 @@ + diff --git a/Octokit/Clients/ActivitiesClient.cs b/Octokit/Clients/ActivitiesClient.cs index 36748ec8..a3dd024a 100644 --- a/Octokit/Clients/ActivitiesClient.cs +++ b/Octokit/Clients/ActivitiesClient.cs @@ -17,9 +17,11 @@ { Events = new EventsClient(apiConnection); Starring = new StarredClient(apiConnection); + Watching = new WatchedClient(apiConnection); } public IEventsClient Events { get; private set; } public IStarredClient Starring { get; private set; } + public IWatchedClient Watching { get; private set; } } } diff --git a/Octokit/Clients/IActivitiesClient.cs b/Octokit/Clients/IActivitiesClient.cs index c2bd6b70..b9f5f0b6 100644 --- a/Octokit/Clients/IActivitiesClient.cs +++ b/Octokit/Clients/IActivitiesClient.cs @@ -10,5 +10,6 @@ { IEventsClient Events { get; } IStarredClient Starring { get; } + IWatchedClient Watching { get; } } } \ No newline at end of file diff --git a/Octokit/Clients/IWatchedClient.cs b/Octokit/Clients/IWatchedClient.cs new file mode 100644 index 00000000..7ccd64f4 --- /dev/null +++ b/Octokit/Clients/IWatchedClient.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace Octokit +{ + public interface IWatchedClient + { + /// + /// 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. + Task> GetAllWatchers(string owner, string name); + + /// + /// Retrieves all of the watched (ies) for the current user. + /// + /// Thrown if the client is not authenticated. + /// + /// A of (ies) watched by the current authenticated user. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + Task> GetAllForCurrent(); + + /// + /// Retrieves all of the (ies) watched by the specified user. + /// + /// The login of the user + /// Thrown if the client is not authenticated. + /// + /// A (ies) watched by the specified user. + /// + Task> 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 + Task CheckWatched(string owner, string name); + + /// + /// Watches 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 watching + Task WatchRepo(string owner, string name, NewSubscription newSubscription); + + /// + /// Unwatches 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")] + Task UnwatchRepo(string owner, string name); + } +} \ No newline at end of file diff --git a/Octokit/Clients/WatchedClient.cs b/Octokit/Clients/WatchedClient.cs new file mode 100644 index 00000000..45fcfc89 --- /dev/null +++ b/Octokit/Clients/WatchedClient.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace Octokit +{ + public class WatchedClient : ApiClient, IWatchedClient + { + /// + /// Instantiates a new GitHub Activity Watching API client. + /// + /// An API connection + public WatchedClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// 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 Task> GetAllWatchers(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return ApiConnection.GetAll(ApiUrls.Watchers(owner, name)); + } + + /// + /// Retrieves all of the watched (ies) for the current user. + /// + /// Thrown if the client is not authenticated. + /// + /// A of (ies) watched by the current authenticated user. + /// + public Task> GetAllForCurrent() + { + return ApiConnection.GetAll(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 (ies) watched by the specified user. + /// + public Task> GetAllForUser(string user) + { + Ensure.ArgumentNotNullOrEmptyString(user, "user"); + + return ApiConnection.GetAll(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 async Task CheckWatched(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + try + { + var subscription = await ApiConnection.Get(ApiUrls.Watched(owner, name)) + .ConfigureAwait(false); + + return subscription != null; + } + catch (NotFoundException) + { + return false; + } + } + + /// + /// Watches 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 watching + public Task WatchRepo(string owner, string name, NewSubscription newSubscription) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(newSubscription, "newSubscription"); + + return ApiConnection.Put(ApiUrls.Watched(owner, name), newSubscription); + } + + /// + /// Unwatches 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 async Task UnwatchRepo(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + try + { + var statusCode = await Connection.DeleteAsync(ApiUrls.Watched(owner, name)) + .ConfigureAwait(false); + + return statusCode == HttpStatusCode.NoContent; + } + catch (NotFoundException) + { + return false; + } + } + } +} diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 34f983fd..acede773 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -11,6 +11,7 @@ namespace Octokit static readonly Uri _currentUserOrganizationsUrl = new Uri("user/orgs", UriKind.Relative); static readonly Uri _currentUserSshKeys = new Uri("user/keys", UriKind.Relative); static readonly Uri _currentUserStars = new Uri("user/starred", UriKind.Relative); + static readonly Uri _currentUserWatched = new Uri("user/subscriptions", UriKind.Relative); static readonly Uri _currentUserEmailsEndpoint = new Uri("user/emails", UriKind.Relative); static readonly Uri _currentUserAuthorizationsEndpoint = new Uri("authorizations", UriKind.Relative); static readonly Uri _currentUserNotificationsEndpoint = new Uri("notifications", UriKind.Relative); @@ -438,6 +439,44 @@ namespace Octokit return "repos/{0}/{1}/statuses/{2}".FormatUri(owner, name, reference); } + /// + /// Returns the that lists the watched repositories for the authenticated user. + /// + /// The owner of the repository + /// The name of the repository + public static Uri Watchers(string owner, string name) + { + return "repos/{0}/{1}/subscribers".FormatUri(owner, name); + } + + /// + /// Returns the that lists the watched repositories for the authenticated user. + /// + public static Uri Watched() + { + return _currentUserWatched; + } + + /// + /// Returns the that lists the watched repositories for the specified user. + /// + /// The user that has the watches + public static Uri WatchedByUser(string user) + { + return "users/{0}/subscriptions".FormatUri(user); + } + + /// + /// Returns the that shows whether the repo is starred by the current user. + /// + /// The owner of the repository + /// The name of the repository + /// + public static Uri Watched(string owner, string name) + { + return "repos/{0}/{1}/subscription".FormatUri(owner, name); + } + /// /// Returns the that lists the starred repositories for the authenticated user. /// diff --git a/Octokit/Models/Request/NewSubscription.cs b/Octokit/Models/Request/NewSubscription.cs new file mode 100644 index 00000000..1e138575 --- /dev/null +++ b/Octokit/Models/Request/NewSubscription.cs @@ -0,0 +1,17 @@ +using System; + +namespace Octokit +{ + public class NewSubscription + { + /// + /// Determines if notifications should be received from this repository. + /// + public bool Subscribed { get; set; } + + /// + /// Determines if all notifications should be blocked from this repository. + /// + public bool Ignored { get; set; } + } +} diff --git a/Octokit/Models/Response/Subscription.cs b/Octokit/Models/Response/Subscription.cs new file mode 100644 index 00000000..e48fab07 --- /dev/null +++ b/Octokit/Models/Response/Subscription.cs @@ -0,0 +1,37 @@ +using System; + +namespace Octokit +{ + public class Subscription + { + /// + /// Determines if notifications should be received from this repository. + /// + public bool Subscribed { get; set; } + + /// + /// Determines if all notifications should be blocked from this repository. + /// + public bool Ignored { get; set; } + + /// + /// Url of the label + /// + public string Reason { get; set; } + + /// + /// The for when this was created. + /// + public DateTimeOffset CreatedAt { get; set; } + + /// + /// The API URL for this . + /// + public Uri Url { get; set; } + + /// + /// The API URL for this . + /// + public Uri RepositoryUrl { get; set; } + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 522b4bfb..f370e799 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -68,6 +68,7 @@ + @@ -75,6 +76,7 @@ + @@ -89,6 +91,7 @@ + @@ -126,6 +129,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index bb077574..9ad02d67 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -90,6 +90,7 @@ + @@ -104,6 +105,7 @@ + @@ -170,6 +172,7 @@ + @@ -222,6 +225,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index febcad07..f1f7e631 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -54,12 +54,15 @@ Properties\SolutionInfo.cs + + Code + @@ -94,6 +97,7 @@ +