Watched Client and Unit Tests

This commit is contained in:
Nigel Sampson
2014-01-30 11:26:19 +13:00
parent 9d59fd936a
commit b62357fe46
12 changed files with 433 additions and 0 deletions
+132
View File
@@ -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<IApiConnection>();
var client = new WatchedClient(connection);
client.GetAllForCurrent();
connection.Received().GetAll<Repository>(endpoint);
}
}
public class TheGetAllForUserMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var endpoint = new Uri("users/banana/subscriptions", UriKind.Relative);
var connection = Substitute.For<IApiConnection>();
var client = new WatchedClient(connection);
client.GetAllForUser("banana");
connection.Received().GetAll<Repository>(endpoint);
}
}
public class TheGetAllWatchersForRepoMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var endpoint = new Uri("repos/fight/club/subscribers", UriKind.Relative);
var connection = Substitute.For<IApiConnection>();
var client = new WatchedClient(connection);
client.GetAllWatchers("fight", "club");
connection.Received().GetAll<User>(endpoint);
}
}
public class TheCheckWatchedMethod
{
[Fact]
public async Task ReturnsTrueOnValidResult()
{
var endpoint = new Uri("repos/fight/club/subscription", UriKind.Relative);
var connection = Substitute.For<IApiConnection>();
connection.Get<Subscription>(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<IApiConnection>();
var response = new ApiResponse<Subscription> { StatusCode = HttpStatusCode.NotFound };
connection.Get<Subscription>(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<IApiConnection>();
var client = new WatchedClient(connection);
var newSubscription = new NewSubscription();
client.WatchRepo("fight", "club", newSubscription);
connection.Received().Put<Subscription>(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<HttpStatusCode>(() => status);
var connection = Substitute.For<IConnection>();
connection.DeleteAsync(Arg.Is<Uri>(u => u.ToString() == "repos/yes/no/subscription"))
.Returns(response);
var apiConnection = Substitute.For<IApiConnection>();
apiConnection.Connection.Returns(connection);
var client = new WatchedClient(apiConnection);
var result = await client.UnwatchRepo("yes", "no");
Assert.Equal(expected, result);
}
}
}
}
+1
View File
@@ -86,6 +86,7 @@
<Compile Include="Clients\ReleasesClientTests.cs" />
<Compile Include="Clients\SshKeysClientTests.cs" />
<Compile Include="Clients\TreesClientTests.cs" />
<Compile Include="Clients\WatchedClientTests.cs" />
<Compile Include="Exceptions\ApiExceptionTests.cs" />
<Compile Include="Exceptions\ApiValidationExceptionTests.cs" />
<Compile Include="Exceptions\TwoFactorChallengeFailedException.cs" />
+2
View File
@@ -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; }
}
}
+1
View File
@@ -10,5 +10,6 @@
{
IEventsClient Events { get; }
IStarredClient Starring { get; }
IWatchedClient Watching { get; }
}
}
+66
View File
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Octokit
{
public interface IWatchedClient
{
/// <summary>
/// Retrieves all of the watchers for the passed repository.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>A <see cref="IReadOnlyPagedCollection{User}"/> of <see cref="User"/>s watching the passed repository.</returns>
Task<IReadOnlyList<User>> GetAllWatchers(string owner, string name);
/// <summary>
/// Retrieves all of the watched <see cref="Repository"/>(ies) for the current user.
/// </summary>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>
/// A <see cref="IReadOnlyPagedCollection{Repository}"/> of <see cref="Repository"/>(ies) watched by the current authenticated user.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
Task<IReadOnlyList<Repository>> GetAllForCurrent();
/// <summary>
/// Retrieves all of the <see cref="Repository"/>(ies) watched by the specified user.
/// </summary>
/// <param name="user">The login of the user</param>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>
/// A <see cref="IReadOnlyPagedCollection{Repository}"/>(ies) watched by the specified user.
/// </returns>
Task<IReadOnlyList<Repository>> GetAllForUser(string user);
/// <summary>
/// Check if a repository is watched by the current authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>A <c>bool</c> representing the success of the operation</returns>
Task<bool> CheckWatched(string owner, string name);
/// <summary>
/// Watches a repository for the authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository to star</param>
/// <param name="name">The name of the repository to star</param>
/// <param name="newSubscription">A <see cref="NewSubscription"/> instance describing the new subscription to create</param>
/// <returns>A <c>bool</c> representing the success of watching</returns>
Task<Subscription> WatchRepo(string owner, string name, NewSubscription newSubscription);
/// <summary>
/// Unwatches a repository for the authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository to unstar</param>
/// <param name="name">The name of the repository to unstar</param>
/// <returns>A <c>bool</c> representing the success of the operation</returns>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="Unwatch",
Justification = "Unwatch is consistent with the GitHub website")]
Task<bool> UnwatchRepo(string owner, string name);
}
}
+126
View File
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
namespace Octokit
{
public class WatchedClient : ApiClient, IWatchedClient
{
/// <summary>
/// Instantiates a new GitHub Activity Watching API client.
/// </summary>
/// <param name="apiConnection">An API connection</param>
public WatchedClient(IApiConnection apiConnection)
: base(apiConnection)
{
}
/// <summary>
/// Retrieves all of the watchers for the passed repository.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>A <see cref="IReadOnlyPagedCollection{User}"/> of <see cref="User"/>s watching the passed repository.</returns>
public Task<IReadOnlyList<User>> GetAllWatchers(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
return ApiConnection.GetAll<User>(ApiUrls.Watchers(owner, name));
}
/// <summary>
/// Retrieves all of the watched <see cref="Repository"/>(ies) for the current user.
/// </summary>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>
/// A <see cref="IReadOnlyPagedCollection{Repository}"/> of <see cref="Repository"/>(ies) watched by the current authenticated user.
/// </returns>
public Task<IReadOnlyList<Repository>> GetAllForCurrent()
{
return ApiConnection.GetAll<Repository>(ApiUrls.Watched());
}
/// <summary>
/// Retrieves all of the <see cref="Repository"/>(ies) watched by the specified user.
/// </summary>
/// <param name="user">The login of the user</param>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>
/// A <see cref="IReadOnlyPagedCollection{Repository}"/>(ies) watched by the specified user.
/// </returns>
public Task<IReadOnlyList<Repository>> GetAllForUser(string user)
{
Ensure.ArgumentNotNullOrEmptyString(user, "user");
return ApiConnection.GetAll<Repository>(ApiUrls.WatchedByUser(user));
}
/// <summary>
/// Check if a repository is watched by the current authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <exception cref="AuthorizationException">Thrown if the client is not authenticated.</exception>
/// <returns>A <c>bool</c> representing the success of the operation</returns>
public async Task<bool> CheckWatched(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
try
{
var subscription = await ApiConnection.Get<Subscription>(ApiUrls.Watched(owner, name))
.ConfigureAwait(false);
return subscription != null;
}
catch (NotFoundException)
{
return false;
}
}
/// <summary>
/// Watches a repository for the authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository to star</param>
/// <param name="name">The name of the repository to star</param>
/// <param name="newSubscription">A <see cref="NewSubscription"/> instance describing the new subscription to create</param>
/// <returns>A <c>bool</c> representing the success of watching</returns>
public Task<Subscription> WatchRepo(string owner, string name, NewSubscription newSubscription)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(newSubscription, "newSubscription");
return ApiConnection.Put<Subscription>(ApiUrls.Watched(owner, name), newSubscription);
}
/// <summary>
/// Unwatches a repository for the authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository to unstar</param>
/// <param name="name">The name of the repository to unstar</param>
/// <returns>A <c>bool</c> representing the success of the operation</returns>
public async Task<bool> 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;
}
}
}
}
+39
View File
@@ -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);
}
/// <summary>
/// Returns the <see cref="Uri"/> that lists the watched repositories for the authenticated user.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
public static Uri Watchers(string owner, string name)
{
return "repos/{0}/{1}/subscribers".FormatUri(owner, name);
}
/// <summary>
/// Returns the <see cref="Uri"/> that lists the watched repositories for the authenticated user.
/// </summary>
public static Uri Watched()
{
return _currentUserWatched;
}
/// <summary>
/// Returns the <see cref="Uri"/> that lists the watched repositories for the specified user.
/// </summary>
/// <param name="user">The user that has the watches</param>
public static Uri WatchedByUser(string user)
{
return "users/{0}/subscriptions".FormatUri(user);
}
/// <summary>
/// Returns the <see cref="Uri"/> that shows whether the repo is starred by the current user.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
public static Uri Watched(string owner, string name)
{
return "repos/{0}/{1}/subscription".FormatUri(owner, name);
}
/// <summary>
/// Returns the <see cref="Uri"/> that lists the starred repositories for the authenticated user.
/// </summary>
+17
View File
@@ -0,0 +1,17 @@
using System;
namespace Octokit
{
public class NewSubscription
{
/// <summary>
/// Determines if notifications should be received from this repository.
/// </summary>
public bool Subscribed { get; set; }
/// <summary>
/// Determines if all notifications should be blocked from this repository.
/// </summary>
public bool Ignored { get; set; }
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
namespace Octokit
{
public class Subscription
{
/// <summary>
/// Determines if notifications should be received from this repository.
/// </summary>
public bool Subscribed { get; set; }
/// <summary>
/// Determines if all notifications should be blocked from this repository.
/// </summary>
public bool Ignored { get; set; }
/// <summary>
/// Url of the label
/// </summary>
public string Reason { get; set; }
/// <summary>
/// The <see cref="DateTimeOffset"/> for when this <see cref="Subscription"/> was created.
/// </summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// The API URL for this <see cref="Subscription"/>.
/// </summary>
public Uri Url { get; set; }
/// <summary>
/// The API URL for this <see cref="Repository"/>.
/// </summary>
public Uri RepositoryUrl { get; set; }
}
}
+4
View File
@@ -68,6 +68,7 @@
<Compile Include="Clients\ITagsClient.cs" />
<Compile Include="Clients\ITreesClient.cs" />
<Compile Include="Clients\ITeamsClient.cs" />
<Compile Include="Clients\IWatchedClient.cs" />
<Compile Include="Clients\MilestonesClient.cs" />
<Compile Include="Clients\OrganizationMembersClient.cs" />
<Compile Include="Clients\ReferencesClient.cs" />
@@ -75,6 +76,7 @@
<Compile Include="Clients\TagsClient.cs" />
<Compile Include="Clients\TreesClient.cs" />
<Compile Include="Clients\TeamsClient.cs" />
<Compile Include="Clients\WatchedClient.cs" />
<Compile Include="Exceptions\NotFoundException.cs" />
<Compile Include="Clients\IAssigneesClient.cs" />
<Compile Include="Clients\IIssuesClient.cs" />
@@ -89,6 +91,7 @@
<Compile Include="Models\Request\NewLabel.cs" />
<Compile Include="Models\Request\NewMilestone.cs" />
<Compile Include="Models\Request\NewReference.cs" />
<Compile Include="Models\Request\NewSubscription.cs" />
<Compile Include="Models\Request\NewTag.cs" />
<Compile Include="Models\Request\NewTree.cs" />
<Compile Include="Models\Request\NewTreeItem.cs" />
@@ -126,6 +129,7 @@
<Compile Include="Models\Request\MilestoneRequest.cs" />
<Compile Include="Models\Response\Reference.cs" />
<Compile Include="Models\Response\Signature.cs" />
<Compile Include="Models\Response\Subscription.cs" />
<Compile Include="Models\Response\TagObject.cs" />
<Compile Include="Models\Response\TreeItem.cs" />
<Compile Include="Models\Response\TreeResponse.cs" />
+4
View File
@@ -90,6 +90,7 @@
<Compile Include="Clients\ITreesClient.cs" />
<Compile Include="Clients\ITeamsClient.cs" />
<Compile Include="Clients\IUsersClient.cs" />
<Compile Include="Clients\IWatchedClient.cs" />
<Compile Include="Clients\MilestonesClient.cs" />
<Compile Include="Clients\MiscellaneousClient.cs" />
<Compile Include="Clients\NotificationsClient.cs" />
@@ -104,6 +105,7 @@
<Compile Include="Clients\TreesClient.cs" />
<Compile Include="Clients\TeamsClient.cs" />
<Compile Include="Clients\UsersClient.cs" />
<Compile Include="Clients\WatchedClient.cs" />
<Compile Include="Exceptions\ApiException.cs" />
<Compile Include="Exceptions\ApiValidationException.cs" />
<Compile Include="Exceptions\AuthorizationException.cs" />
@@ -170,6 +172,7 @@
<Compile Include="Models\Request\NewMilestone.cs" />
<Compile Include="Models\Request\NewReference.cs" />
<Compile Include="Models\Request\NewRepository.cs" />
<Compile Include="Models\Request\NewSubscription.cs" />
<Compile Include="Models\Request\NewTag.cs" />
<Compile Include="Models\Request\NewTree.cs" />
<Compile Include="Models\Request\NewTreeItem.cs" />
@@ -222,6 +225,7 @@
<Compile Include="Models\Response\Signature.cs" />
<Compile Include="Models\Response\SshKey.cs" />
<Compile Include="Models\Response\SshKeyInfo.cs" />
<Compile Include="Models\Response\Subscription.cs" />
<Compile Include="Models\Response\TagObject.cs" />
<Compile Include="Models\Response\TreeItem.cs" />
<Compile Include="Models\Response\TreeResponse.cs" />
+4
View File
@@ -54,12 +54,15 @@
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Clients\ActivitiesClient.cs" />
<Compile Include="Clients\IWatchedClient.cs" />
<Compile Include="Clients\SearchClient.cs" />
<Compile Include="Clients\ISearchClient.cs" />
<Compile Include="Clients\WatchedClient.cs" />
<Compile Include="Models\Request\BaseSearchRequest.cs" />
<Compile Include="Helpers\EnumExtensions.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Models\Request\NewSubscription.cs" />
<Compile Include="Models\Request\SearchCodeRequest.cs" />
<Compile Include="Models\Request\SearchIssuesRequest.cs" />
<Compile Include="Models\Request\SearchQualifierOperator.cs" />
@@ -94,6 +97,7 @@
<Compile Include="Models\Response\Branch.cs" />
<Compile Include="Models\Response\GistComment.cs" />
<Compile Include="Models\Response\Reference.cs" />
<Compile Include="Models\Response\Subscription.cs" />
<Compile Include="Models\Response\TreeItem.cs" />
<Compile Include="Models\Response\TreeResponse.cs" />
<Compile Include="Models\Response\Activity.cs" />