Merge pull request #160 from brlinton/observable-issues

Observable Issues Client
This commit is contained in:
Phil Haack
2013-11-02 21:19:15 -07:00
8 changed files with 799 additions and 1 deletions
+1
View File
@@ -69,3 +69,4 @@ nunit-*.xml
*.userprefs
packaging/
tools/FAKE.Core
*.ncrunch*
@@ -0,0 +1,140 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Octokit.Reactive
{
public interface IObservableIssuesClient
{
IObservableAssigneesClient Assignee { get; }
/// <summary>
/// Client for managing milestones.
/// </summary>
IObservableMilestonesClient Milestone { get; }
/// <summary>
/// Gets a single Issue by number.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#get-a-single-issue
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get",
Justification = "Method makes a network request")]
IObservable<Issue> Get(string owner, string name, int number);
/// <summary>
/// Gets all open issues assigned to the authenticated user across all the authenticated users visible
/// repositories including owned repositories, member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
IObservable<Issue> GetAllForCurrent();
/// <summary>
/// Gets all issues across all the authenticated users visible repositories including owned repositories,
/// member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
IObservable<Issue> GetAllForCurrent(IssueRequest request);
/// <summary>
/// Gets all open issues assigned to the authenticated user across owned and member repositories for the
/// authenticated user.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
IObservable<Issue> GetAllForOwnedAndMemberRepositories();
/// <summary>
/// Gets all issues across owned and member repositories for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
IObservable<Issue> GetAllForOwnedAndMemberRepositories(IssueRequest request);
/// <summary>
/// Gets all open issues assigned to the authenticated user for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <returns></returns>
IObservable<Issue> GetAllForOrganization(string organization);
/// <summary>
/// Gets all issues for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
IObservable<Issue> GetAllForOrganization(string organization, IssueRequest request);
/// <summary>
/// Gets all open issues assigned to the authenticated user for the repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
IObservable<Issue> GetForRepository(string owner, string name);
/// <summary>
/// Gets issues for a repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
IObservable<Issue> GetForRepository(string owner, string name, RepositoryIssueRequest request);
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="newIssue">A <see cref="NewIssue"/> instance describing the new issue to create</param>
/// <returns></returns>
IObservable<Issue> Create(string owner, string name, NewIssue newIssue);
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <param name="issueUpdate">An <see cref="IssueUpdate"/> instance describing the changes to make to the issue
/// </param>
/// <returns></returns>
IObservable<Issue> Update(string owner, string name, int number, IssueUpdate issueUpdate);
}
}
@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Reactive.Threading.Tasks;
using Octokit.Reactive.Clients;
using Octokit.Reactive.Internal;
namespace Octokit.Reactive
{
public class ObservableIssuesClient : IObservableIssuesClient
{
readonly IIssuesClient _client;
readonly IConnection _connection;
public IObservableAssigneesClient Assignee { get; private set; }
public IObservableMilestonesClient Milestone { get; private set; }
public ObservableIssuesClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, "client");
_client = client.Issue;
_connection = client.Connection;
Assignee = new ObservableAssigneesClient(client);
Milestone = new ObservableMilestonesClient(client);
}
/// <summary>
/// Gets a single Issue by number./// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#get-a-single-issue
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public IObservable<Issue> Get(string owner, string name, int number)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
return _client.Get(owner, name, number).ToObservable();
}
/// <summary>
/// Gets all open issues assigned to the authenticated user across all the authenticated users visible
/// repositories including owned repositories, member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
public IObservable<Issue> GetAllForCurrent()
{
return GetAllForCurrent(new IssueRequest());
}
/// <summary>
/// Gets all issues across all the authenticated users visible repositories including owned repositories,
/// member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public IObservable<Issue> GetAllForCurrent(IssueRequest request)
{
Ensure.ArgumentNotNull(request, "request");
return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.Issues(), request.ToParametersDictionary());
}
/// <summary>
/// Gets all open issues assigned to the authenticated user across owned and member repositories for the
/// authenticated user.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
public IObservable<Issue> GetAllForOwnedAndMemberRepositories()
{
return GetAllForOwnedAndMemberRepositories(new IssueRequest());
}
/// <summary>
/// Gets all issues across owned and member repositories for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public IObservable<Issue> GetAllForOwnedAndMemberRepositories(IssueRequest request)
{
Ensure.ArgumentNotNull(request, "request");
return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.IssuesForOwnedAndMember(), request.ToParametersDictionary());
}
/// <summary>
/// Gets all open issues assigned to the authenticated user for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <returns></returns>
public IObservable<Issue> GetAllForOrganization(string organization)
{
return GetAllForOrganization(organization, new IssueRequest());
}
/// <summary>
/// Gets all issues for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public IObservable<Issue> GetAllForOrganization(string organization, IssueRequest request)
{
Ensure.ArgumentNotNullOrEmptyString(organization, "organization");
Ensure.ArgumentNotNull(request, "request");
return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.Issues(organization), request.ToParametersDictionary());
}
/// <summary>
/// Gets all open issues assigned to the authenticated user for the repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
public IObservable<Issue> GetForRepository(string owner, string name)
{
return GetForRepository(owner, name, new RepositoryIssueRequest());
}
/// <summary>
/// Gets issues for a repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public IObservable<Issue> GetForRepository(string owner, string name, RepositoryIssueRequest request)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(request, "request");
return _connection.GetAndFlattenAllPages<Issue>(ApiUrls.Issues(owner, name), request.ToParametersDictionary());
}
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="newIssue">A <see cref="NewIssue"/> instance describing the new issue to create</param>
/// <returns></returns>
public IObservable<Issue> Create(string owner, string name, NewIssue newIssue)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(newIssue, "newIssue");
return _client.Create(owner, name, newIssue).ToObservable();
}
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <param name="issueUpdate">An <see cref="IssueUpdate"/> instance describing the changes to make to the issue
/// </param>
/// <returns></returns>
public IObservable<Issue> Update(string owner, string name, int number, IssueUpdate issueUpdate)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(issueUpdate, "issueUpdate");
return _client.Update(owner, name, number, issueUpdate).ToObservable();
}
}
}
+3 -1
View File
@@ -71,7 +71,9 @@
<Compile Include="..\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Clients\IObservableIssuesClient.cs" />
<Compile Include="Clients\IObservableMilestonesClient.cs" />
<Compile Include="Clients\ObservableIssuesClient.cs" />
<Compile Include="Clients\ObservableMilestonesClient.cs" />
<Compile Include="Clients\ObservableAssigneesClient.cs" />
<Compile Include="Clients\IObservableCommitStatusClient.cs" />
@@ -123,4 +125,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
@@ -63,6 +63,7 @@
<Compile Include="IntegrationTestAttribute.cs" />
<Compile Include="IssuesClientTests.cs" />
<Compile Include="MiscellaneousClientTests.cs" />
<Compile Include="Reactive\ObservableIssuesClientTests.cs" />
<Compile Include="Reactive\ObservableMilestonesClientTests.cs" />
<Compile Include="Reactive\ObservableRepositoriesClientTests.cs" />
<Compile Include="ReleasesClientTests.cs" />
@@ -0,0 +1,85 @@
using Octokit.Reactive;
using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Octokit.Tests.Integration
{
public class ObservableIssuesClientTests : IDisposable
{
readonly ObservableIssuesClient client;
readonly string repoName;
readonly Repository createdRepository;
public ObservableIssuesClientTests()
{
var github = new GitHubClient(new ProductHeaderValue("OctokitTests"))
{
Credentials = Helper.Credentials
};
client = new ObservableIssuesClient(github);
repoName = Helper.MakeNameWithTimestamp("public-repo");
var result = github.Repository.Create(new NewRepository { Name = repoName }).Result;
createdRepository = result;
}
[IntegrationTest]
public async Task ReturnsSpecifiedIssue()
{
var observable = client.Get("libgit2", "libgit2sharp", 1);
var issue = await observable;
Assert.Equal(1, issue.Number);
Assert.Equal("Change License ", issue.Title);
}
[IntegrationTest]
public void ReturnsAllIssuesForARepository()
{
var issues = client.GetForRepository("libgit2", "libgit2sharp").ToList().Wait();
Assert.NotEmpty(issues);
}
[IntegrationTest]
public async void ReturnsAllIssuesForCurrentUser()
{
var newIssue = new NewIssue("Integration test issue");
var createResult = await client.Create(createdRepository.Owner.Name, repoName, newIssue);
var issues = client.GetAllForCurrent().ToList().Wait();
Assert.NotEmpty(issues);
}
[IntegrationTest]
public async void ReturnsAllIssuesForOwnedAndMemberRepositories()
{
var newIssue = new NewIssue("Integration test issue");
var createResult = await client.Create(createdRepository.Owner.Name, repoName, newIssue);
var result = client.GetAllForOwnedAndMemberRepositories().ToList().Wait();
Assert.NotEmpty(result);
}
[IntegrationTest]
public async void CanCreateAndUpdateIssues()
{
var newIssue = new NewIssue("Integration test issue");
var createResult = await client.Create(createdRepository.Owner.Name, repoName, newIssue);
var updateResult = await client.Update(createdRepository.Owner.Name, repoName, createResult.Number, new IssueUpdate { Title = "Modified integration test issue" });
Assert.Equal("Modified integration test issue", updateResult.Title);
}
public void Dispose()
{
Helper.DeleteRepo(createdRepository);
}
}
}
+1
View File
@@ -106,6 +106,7 @@
<Compile Include="Helpers\StringExtensionsTests.cs" />
<Compile Include="Clients\RepositoriesClientTests.cs" />
<Compile Include="Reactive\AuthorizationExtensionsTests.cs" />
<Compile Include="Reactive\ObservableIssuesClientTests.cs" />
<Compile Include="Reactive\ObservableMilestonesClientTests.cs" />
<Compile Include="Reactive\ObservableRepositoriesClientTests.cs" />
<Compile Include="SimpleJsonSerializerTests.cs" />
@@ -0,0 +1,364 @@
using NSubstitute;
using Octokit.Internal;
using Octokit.Reactive;
using Octokit.Tests.Helpers;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Octokit.Tests.Reactive
{
public class ObservableIssuesClientTests
{
public class TheGetMethod
{
[Fact]
public void GetsFromClientIssueIssue()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);
client.Get("fake", "repo", 42);
gitHubClient.Issue.Received().Get("fake", "repo", 42);
}
[Fact]
public async Task EnsuresNonNullArguments()
{
var client = new ObservableIssuesClient(Substitute.For<IGitHubClient>());
await AssertEx.Throws<ArgumentNullException>(async () => await client.Get(null, "name", 1));
await AssertEx.Throws<ArgumentNullException>(async () => await client.Get("owner", null, 1));
await AssertEx.Throws<ArgumentException>(async () => await client.Get(null, "", 1));
await AssertEx.Throws<ArgumentException>(async () => await client.Get("", null, 1));
}
}
public class TheGetForRepositoryMethod
{
[Fact]
public void ReturnsEveryPageOfIssues()
{
var firstPageUrl = new Uri("repos/fake/repo/issues", UriKind.Relative);
var secondPageUrl = new Uri("https://example.com/page/2");
var firstPageLinks = new Dictionary<string, Uri> { { "next", secondPageUrl } };
var firstPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 1},
new Issue {Number = 2},
new Issue {Number = 3},
},
ApiInfo = CreateApiInfo(firstPageLinks)
};
var thirdPageUrl = new Uri("https://example.com/page/3");
var secondPageLinks = new Dictionary<string, Uri> { { "next", thirdPageUrl } };
var secondPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 4},
new Issue {Number = 5},
new Issue {Number = 6},
},
ApiInfo = CreateApiInfo(secondPageLinks)
};
var lastPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 7},
},
ApiInfo = CreateApiInfo(new Dictionary<string, Uri>())
};
var gitHubClient = Substitute.For<IGitHubClient>();
gitHubClient.Connection.GetAsync<List<Issue>>(Arg.Is(firstPageUrl),
Arg.Is<Dictionary<string, string>>(d => d.Count == 4
&& d["direction"] == "desc"
&& d["state"] == "open"
&& d["sort"] == "created"
&& d["filter"] == "assigned"), Arg.Any<string>())
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => firstPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(secondPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => secondPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(thirdPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => lastPageResponse));
var client = new ObservableIssuesClient(gitHubClient);
var results = client.GetForRepository("fake", "repo").ToArray().Wait();
Assert.Equal(7, results.Length);
Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number);
Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number);
Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number);
}
}
public class TheGetAllForOwnedAndMemberRepositoriesMethod
{
[Fact]
public void ReturnsEveryPageOfIssues()
{
var firstPageUrl = new Uri("user/issues", UriKind.Relative);
var secondPageUrl = new Uri("https://example.com/page/2");
var firstPageLinks = new Dictionary<string, Uri> { { "next", secondPageUrl } };
var firstPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 1},
new Issue {Number = 2},
new Issue {Number = 3},
},
ApiInfo = CreateApiInfo(firstPageLinks)
};
var thirdPageUrl = new Uri("https://example.com/page/3");
var secondPageLinks = new Dictionary<string, Uri> { { "next", thirdPageUrl } };
var secondPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 4},
new Issue {Number = 5},
new Issue {Number = 6},
},
ApiInfo = CreateApiInfo(secondPageLinks)
};
var lastPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 7},
},
ApiInfo = CreateApiInfo(new Dictionary<string, Uri>())
};
var gitHubClient = Substitute.For<IGitHubClient>();
gitHubClient.Connection.GetAsync<List<Issue>>(Arg.Is(firstPageUrl),
Arg.Is<Dictionary<string, string>>(d => d.Count == 4
&& d["direction"] == "desc"
&& d["state"] == "open"
&& d["sort"] == "created"
&& d["filter"] == "assigned"), Arg.Any<string>())
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => firstPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(secondPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => secondPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(thirdPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => lastPageResponse));
var client = new ObservableIssuesClient(gitHubClient);
var results = client.GetAllForOwnedAndMemberRepositories().ToArray().Wait();
Assert.Equal(7, results.Length);
Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number);
Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number);
Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number);
}
}
public class TheGetAllForOrganizationMethod
{
[Fact]
public void ReturnsEveryPageOfIssues()
{
var firstPageUrl = new Uri("orgs/test/issues", UriKind.Relative);
var secondPageUrl = new Uri("https://example.com/page/2");
var firstPageLinks = new Dictionary<string, Uri> { { "next", secondPageUrl } };
var firstPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 1},
new Issue {Number = 2},
new Issue {Number = 3},
},
ApiInfo = CreateApiInfo(firstPageLinks)
};
var thirdPageUrl = new Uri("https://example.com/page/3");
var secondPageLinks = new Dictionary<string, Uri> { { "next", thirdPageUrl } };
var secondPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 4},
new Issue {Number = 5},
new Issue {Number = 6},
},
ApiInfo = CreateApiInfo(secondPageLinks)
};
var lastPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 7},
},
ApiInfo = CreateApiInfo(new Dictionary<string, Uri>())
};
var gitHubClient = Substitute.For<IGitHubClient>();
gitHubClient.Connection.GetAsync<List<Issue>>(Arg.Is(firstPageUrl),
Arg.Is<Dictionary<string, string>>(d => d.Count == 4
&& d["direction"] == "desc"
&& d["state"] == "open"
&& d["sort"] == "created"
&& d["filter"] == "assigned"), Arg.Any<string>())
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => firstPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(secondPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => secondPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(thirdPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => lastPageResponse));
var client = new ObservableIssuesClient(gitHubClient);
var results = client.GetAllForOrganization("test").ToArray().Wait();
Assert.Equal(7, results.Length);
Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number);
Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number);
Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number);
}
}
public class TheGetAllForCurrentMethod
{
[Fact]
public void ReturnsEveryPageOfIssues()
{
var firstPageUrl = new Uri("issues", UriKind.Relative);
var secondPageUrl = new Uri("https://example.com/page/2");
var firstPageLinks = new Dictionary<string, Uri> { { "next", secondPageUrl } };
var firstPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 1},
new Issue {Number = 2},
new Issue {Number = 3},
},
ApiInfo = CreateApiInfo(firstPageLinks)
};
var thirdPageUrl = new Uri("https://example.com/page/3");
var secondPageLinks = new Dictionary<string, Uri> { { "next", thirdPageUrl } };
var secondPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 4},
new Issue {Number = 5},
new Issue {Number = 6},
},
ApiInfo = CreateApiInfo(secondPageLinks)
};
var lastPageResponse = new ApiResponse<List<Issue>>
{
BodyAsObject = new List<Issue>
{
new Issue {Number = 7},
},
ApiInfo = CreateApiInfo(new Dictionary<string, Uri>())
};
var gitHubClient = Substitute.For<IGitHubClient>();
gitHubClient.Connection.GetAsync<List<Issue>>(Arg.Is(firstPageUrl),
Arg.Is<Dictionary<string, string>>(d => d.Count == 4
&& d["direction"] == "desc"
&& d["state"] == "open"
&& d["sort"] == "created"
&& d["filter"] == "assigned"), Arg.Any<string>())
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => firstPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(secondPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => secondPageResponse));
gitHubClient.Connection.GetAsync<List<Issue>>(thirdPageUrl, null, null)
.Returns(Task.Factory.StartNew<IResponse<List<Issue>>>(() => lastPageResponse));
var client = new ObservableIssuesClient(gitHubClient);
var results = client.GetAllForCurrent().ToArray().Wait();
Assert.Equal(7, results.Length);
Assert.Equal(firstPageResponse.BodyAsObject[0].Number, results[0].Number);
Assert.Equal(secondPageResponse.BodyAsObject[1].Number, results[4].Number);
Assert.Equal(lastPageResponse.BodyAsObject[0].Number, results[6].Number);
}
}
public class TheCreateMethod
{
[Fact]
public void CreatesFromClientIssueIssue()
{
var newIssue = new NewIssue("some title");
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);
client.Create("fake", "repo", newIssue);
gitHubClient.Issue.Received().Create("fake", "repo", newIssue);
}
[Fact]
public async Task EnsuresArgumentsNotNull()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create(null, "name", new NewIssue("title")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("", "name", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", null, new NewIssue("x")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("owner", "", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", "name", null));
}
}
public class TheUpdateMethod
{
[Fact]
public void UpdatesClientIssueIssue()
{
var IssueUpdate = new IssueUpdate();
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);
client.Update("fake", "repo", 42, IssueUpdate);
gitHubClient.Issue.Received().Update("fake", "repo", 42, IssueUpdate);
}
[Fact]
public async Task EnsuresArgumentsNotNull()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableIssuesClient(gitHubClient);
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create(null, "name", new NewIssue("title")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("", "name", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", null, new NewIssue("x")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("owner", "", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", "name", null));
}
}
public class TheCtor
{
[Fact]
public void EnsuresArgument()
{
Assert.Throws<ArgumentNullException>(() => new IssuesClient(null));
}
}
static ApiInfo CreateApiInfo(IDictionary<string, Uri> links)
{
return new ApiInfo(links, new List<string>(), new List<string>(), "etag", new RateLimit(new Dictionary<string, string>()));
}
}
}