diff --git a/Octokit.Reactive/IObservableGitHubClient.cs b/Octokit.Reactive/IObservableGitHubClient.cs index e6374a0c..1dc1bb3c 100644 --- a/Octokit.Reactive/IObservableGitHubClient.cs +++ b/Octokit.Reactive/IObservableGitHubClient.cs @@ -1,6 +1,6 @@ namespace Octokit.Reactive { - public interface IObservableGitHubClient + public interface IObservableGitHubClient : IApiInfo { IConnection Connection { get; } diff --git a/Octokit.Reactive/ObservableGitHubClient.cs b/Octokit.Reactive/ObservableGitHubClient.cs index dc5c3d03..0ddd519d 100644 --- a/Octokit.Reactive/ObservableGitHubClient.cs +++ b/Octokit.Reactive/ObservableGitHubClient.cs @@ -68,5 +68,11 @@ namespace Octokit.Reactive public IObservableNotificationsClient Notification { get; private set; } public IObservableGitDatabaseClient GitDatabase { get; private set; } public IObservableSearchClient Search { get; private set; } + + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo LastApiInfo { get { return _gitHubClient.Connection.LastApiInfo; } } } } diff --git a/Octokit.Tests.Integration/Clients/GitHubClientTests.cs b/Octokit.Tests.Integration/Clients/GitHubClientTests.cs new file mode 100644 index 00000000..14ca8894 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/GitHubClientTests.cs @@ -0,0 +1,32 @@ +using Octokit.Tests.Integration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +public class GitHubClientTests +{ + public class TheLastApiInfoProperty + { + [IntegrationTest] + public async Task CanRetrieveLastApiInfo() + { + var github = Helper.GetAuthenticatedClient(); + + // Doesn't matter which API gets called + await github.Miscellaneous.GetRateLimits(); + + var result = github.LastApiInfo; + + //Assert.True(result.Links.Count > 0); + //Assert.True(result.AcceptedOauthScopes.Count > 0); + //Assert.True(result.OauthScopes.Count > 0); + //Assert.False(String.IsNullOrEmpty(result.Etag)); + Assert.True(result.RateLimit.Limit > 0); + Assert.True(result.RateLimit.Remaining > -1); + Assert.NotNull(result.RateLimit.Reset); + } + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 87dd6b7a..45b1fb1e 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -75,6 +75,7 @@ + diff --git a/Octokit.Tests/GitHubClientTests.cs b/Octokit.Tests/GitHubClientTests.cs index 1b0fc242..b02b4ac7 100644 --- a/Octokit.Tests/GitHubClientTests.cs +++ b/Octokit.Tests/GitHubClientTests.cs @@ -5,6 +5,7 @@ using NSubstitute; using Octokit.Internal; using Xunit; using Xunit.Extensions; +using System.Collections.Generic; namespace Octokit.Tests { @@ -104,5 +105,71 @@ namespace Octokit.Tests Assert.Equal("bar", client.Credentials.Password); } } + + public class TheLastApiInfoProperty + { + [Fact] + public async Task ReturnsNullIfNew() + { + var connection = Substitute.For(); + connection.LastApiInfo.Returns((ApiInfo)null); + var client = new GitHubClient(connection); + + var result = client.LastApiInfo; + + Assert.Null(result); + + var temp = connection.Received(1).LastApiInfo; + } + + [Fact] + public async Task ReturnsObjectIfNotNew() + { + var apiInfo = new ApiInfo( + new Dictionary + { + { + "next", + new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5") + }, + { + "last", + new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5") + }, + { + "first", + new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5") + }, + { + "prev", + new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5") + } + }, + new List + { + "user", + }, + new List + { + "user", + "public_repo", + "repo", + "gist" + }, + "5634b0b187fd2e91e3126a75006cc4fa", + new RateLimit(100, 75, 1372700873) + ); + var connection = Substitute.For(); + connection.LastApiInfo.Returns(apiInfo); + var client = new GitHubClient(connection); + + var result = client.LastApiInfo; + + Assert.NotNull(result); + + var temp = connection.Received(1).LastApiInfo; + } + } + } } diff --git a/Octokit.Tests/Http/ConnectionTests.cs b/Octokit.Tests/Http/ConnectionTests.cs index 02f1601e..ad335e44 100644 --- a/Octokit.Tests/Http/ConnectionTests.cs +++ b/Octokit.Tests/Http/ConnectionTests.cs @@ -564,5 +564,85 @@ namespace Octokit.Tests.Http Assert.True(connection.UserAgent.StartsWith("OctokitTests (")); } } + + public class TheLastAPiInfoProperty + { + [Fact] + public async Task ReturnsNullIfNew() + { + var httpClient = Substitute.For(); + httpClient.LastApiInfo.Returns((ApiInfo)null); + var connection = new Connection(new ProductHeaderValue("OctokitTests"), + _exampleUri, + Substitute.For(), + httpClient, + Substitute.For()); + + var result = connection.LastApiInfo; + + Assert.Null(result); + + var temp = httpClient.Received(1).LastApiInfo; + } + + [Fact] + public async Task ReturnsObjectIfNotNew() + { + var apiInfo = new ApiInfo( + new Dictionary + { + { + "next", + new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5") + }, + { + "last", + new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5") + }, + { + "first", + new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5") + }, + { + "prev", + new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5") + } + }, + new List + { + "user", + }, + new List + { + "user", + "public_repo", + "repo", + "gist" + }, + "5634b0b187fd2e91e3126a75006cc4fa", + new RateLimit(100, 75, 1372700873) + ); + + var httpClient = Substitute.For(); + httpClient.LastApiInfo.Returns(apiInfo); + var connection = new Connection(new ProductHeaderValue("OctokitTests"), + _exampleUri, + Substitute.For(), + httpClient, + Substitute.For()); + + var result = connection.LastApiInfo; + + // No point checking all of the values as they are tested elsewhere + // Just provde that the ApiInfo is populated + Assert.Equal(4, result.Links.Count); + Assert.Equal(1, result.OauthScopes.Count); + Assert.Equal(4, result.AcceptedOauthScopes.Count); + Assert.Equal("5634b0b187fd2e91e3126a75006cc4fa", result.Etag); + Assert.Equal(100, result.RateLimit.Limit); + + var temp = httpClient.Received(1).LastApiInfo; + } + } } } diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs index d88a6257..b79640af 100644 --- a/Octokit/GitHubClient.cs +++ b/Octokit/GitHubClient.cs @@ -100,6 +100,12 @@ namespace Octokit Deployment = new DeploymentsClient(apiConnection); } + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo LastApiInfo { get { return Connection.LastApiInfo; } } + /// /// Convenience property for getting and setting credentials. /// diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 846923d3..532c9e71 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -136,6 +136,12 @@ namespace Octokit _jsonPipeline = new JsonHttpPipeline(); } + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo LastApiInfo { get { return _httpClient.LastApiInfo; } } + public Task> Get(Uri uri, IDictionary parameters, string accepts) { Ensure.ArgumentNotNull(uri, "uri"); diff --git a/Octokit/Http/HttpClientAdapter.cs b/Octokit/Http/HttpClientAdapter.cs index 59dd87c0..bf33df39 100644 --- a/Octokit/Http/HttpClientAdapter.cs +++ b/Octokit/Http/HttpClientAdapter.cs @@ -28,6 +28,12 @@ namespace Octokit.Internal _http = new HttpClient(new RedirectHandler { InnerHandler = getHandler() }); } + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo LastApiInfo { get; private set; } + /// /// Sends the specified request and returns a response. /// @@ -45,7 +51,11 @@ namespace Octokit.Internal // Make the request var responseMessage = await _http.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationTokenForRequest) .ConfigureAwait(false); - return await BuildResponse(responseMessage).ConfigureAwait(false); + var response = await BuildResponse(responseMessage).ConfigureAwait(false); + + LastApiInfo = response.ApiInfo; + + return response; } } diff --git a/Octokit/Http/IApiInfo.cs b/Octokit/Http/IApiInfo.cs new file mode 100644 index 00000000..04a2cf89 --- /dev/null +++ b/Octokit/Http/IApiInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// Provides a property for the Last recorded API infomation + /// + public interface IApiInfo + { + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + ApiInfo LastApiInfo { get; } + } +} diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 7deec1cc..dd756b10 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -10,7 +10,7 @@ namespace Octokit /// /// A connection for making HTTP requests against URI endpoints. /// - public interface IConnection + public interface IConnection : IApiInfo { /// /// Performs an asynchronous HTTP GET request that expects a containing HTML. diff --git a/Octokit/Http/IHttpClient.cs b/Octokit/Http/IHttpClient.cs index f554d750..db9de47a 100644 --- a/Octokit/Http/IHttpClient.cs +++ b/Octokit/Http/IHttpClient.cs @@ -10,7 +10,7 @@ namespace Octokit.Internal /// /// Most folks won't ever need to swap this out. But if you're trying to run this on Windows Phone, you might. /// - public interface IHttpClient : IDisposable + public interface IHttpClient : IDisposable, IApiInfo { /// /// Sends the specified request and returns a response. diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs index a828c83e..1ae593cc 100644 --- a/Octokit/IGitHubClient.cs +++ b/Octokit/IGitHubClient.cs @@ -5,7 +5,7 @@ namespace Octokit /// /// A Client for the GitHub API v3. You can read more about the api here: http://developer.github.com. /// - public interface IGitHubClient + public interface IGitHubClient : IApiInfo { /// /// Provides a client connection to make rest requests to HTTP endpoints. diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 64daa005..4db1ff4c 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -399,6 +399,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 9002587a..66103c57 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -1,4 +1,4 @@ - + Debug @@ -415,6 +415,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 607ec595..bde1203d 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -1,4 +1,4 @@ - + Debug @@ -408,6 +408,7 @@ + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 74f211df..4080a485 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -398,6 +398,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 944bb681..65c2b6f0 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -402,6 +402,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 60a9607f..943d7bca 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -84,6 +84,7 @@ +