diff --git a/Octokit.Reactive/IObservableGitHubClient.cs b/Octokit.Reactive/IObservableGitHubClient.cs
index e6374a0c5e..be317491fb 100644
--- a/Octokit.Reactive/IObservableGitHubClient.cs
+++ b/Octokit.Reactive/IObservableGitHubClient.cs
@@ -1,6 +1,6 @@
namespace Octokit.Reactive
{
- public interface IObservableGitHubClient
+ public interface IObservableGitHubClient : IApiInfoProvider
{
IConnection Connection { get; }
diff --git a/Octokit.Reactive/ObservableGitHubClient.cs b/Octokit.Reactive/ObservableGitHubClient.cs
index dc5c3d0326..4970eebc9c 100644
--- a/Octokit.Reactive/ObservableGitHubClient.cs
+++ b/Octokit.Reactive/ObservableGitHubClient.cs
@@ -68,5 +68,14 @@ public IConnection Connection
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 GetLastApiInfo()
+ {
+ return _gitHubClient.Connection.GetLastApiInfo();
+ }
}
}
diff --git a/Octokit.Tests.Integration/Clients/GitHubClientTests.cs b/Octokit.Tests.Integration/Clients/GitHubClientTests.cs
new file mode 100644
index 0000000000..aa7c6fcebb
--- /dev/null
+++ b/Octokit.Tests.Integration/Clients/GitHubClientTests.cs
@@ -0,0 +1,83 @@
+using Octokit;
+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 CanRetrieveLastApiInfoWithEtag()
+ {
+ // To check for etag, I'm using a new repository
+ // As per suggestion here -> https://github.com/octokit/octokit.net/pull/855#issuecomment-126966532
+ var github = Helper.GetAuthenticatedClient();
+ var repoName = Helper.MakeNameWithTimestamp("public-repo");
+
+ var createdRepository = await github.Repository.Create(new NewRepository(repoName));
+
+ try
+ {
+ var result = github.GetLastApiInfo();
+
+ Assert.True(result.Links.Count == 0);
+ Assert.True(result.AcceptedOauthScopes.Count > -1);
+ Assert.True(result.OauthScopes.Count > -1);
+ Assert.False(String.IsNullOrEmpty(result.Etag));
+ Assert.True(result.RateLimit.Limit > 0);
+ Assert.True(result.RateLimit.Remaining > -1);
+ Assert.NotNull(result.RateLimit.Reset);
+ }
+ finally
+ {
+ Helper.DeleteRepo(createdRepository);
+ }
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveLastApiInfoWithLinks()
+ {
+ // To check for links, I'm doing a list of all contributors to the octokit.net project
+ // Adapted from suggestion here -> https://github.com/octokit/octokit.net/pull/855#issuecomment-126966532
+ var github = Helper.GetAuthenticatedClient();
+
+ await github.Repository.GetAllContributors("octokit", "octokit.net");
+
+ var result = github.GetLastApiInfo();
+
+ Assert.True(result.Links.Count > 0);
+ Assert.True(result.AcceptedOauthScopes.Count > -1);
+ Assert.True(result.OauthScopes.Count > -1);
+ Assert.False(String.IsNullOrEmpty(result.Etag));
+ Assert.True(result.RateLimit.Limit > 0);
+ Assert.True(result.RateLimit.Remaining > -1);
+ Assert.NotNull(result.RateLimit.Reset);
+ }
+
+ [PersonalAccessTokenTest]
+ public async Task CanRetrieveLastApiInfoAcceptedOauth()
+ {
+ // To check for OAuth & AcceptedOAuth I'm getting the octokit user
+ // Adapted from suggestion here -> https://github.com/octokit/octokit.net/pull/855#issuecomment-126966532
+ var github = Helper.GetAuthenticatedClient();
+
+ await github.User.Get("octokit");
+
+ var result = github.GetLastApiInfo();
+
+ 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/Helper.cs b/Octokit.Tests.Integration/Helper.cs
index 6959652097..8e623528d8 100644
--- a/Octokit.Tests.Integration/Helper.cs
+++ b/Octokit.Tests.Integration/Helper.cs
@@ -12,7 +12,7 @@ public static class Helper
var githubUsername = Environment.GetEnvironmentVariable("OCTOKIT_GITHUBUSERNAME");
UserName = githubUsername;
Organization = Environment.GetEnvironmentVariable("OCTOKIT_GITHUBORGANIZATION");
-
+
var githubToken = Environment.GetEnvironmentVariable("OCTOKIT_OAUTHTOKEN");
if (githubToken != null)
@@ -52,6 +52,14 @@ static Helper()
public static Credentials ApplicationCredentials { get { return _oauthApplicationCredentials.Value; } }
+ public static bool IsUsingToken
+ {
+ get
+ {
+ return !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("OCTOKIT_OAUTHTOKEN"));
+ }
+ }
+
public static bool IsPaidAccount
{
get
diff --git a/Octokit.Tests.Integration/Helpers/PersonalAccessTokenTestAttribute.cs b/Octokit.Tests.Integration/Helpers/PersonalAccessTokenTestAttribute.cs
new file mode 100644
index 0000000000..0fd107ec66
--- /dev/null
+++ b/Octokit.Tests.Integration/Helpers/PersonalAccessTokenTestAttribute.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Octokit.Tests.Integration
+{
+ public class PersonalAccessTokenTestDiscoverer : IXunitTestCaseDiscoverer
+ {
+ readonly IMessageSink diagnosticMessageSink;
+
+ public PersonalAccessTokenTestDiscoverer(IMessageSink diagnosticMessageSink)
+ {
+ this.diagnosticMessageSink = diagnosticMessageSink;
+ }
+
+ public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
+ {
+ return Helper.IsUsingToken
+ ? new[] { new XunitTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) }
+ : Enumerable.Empty();
+ }
+ }
+
+ [XunitTestCaseDiscoverer("Octokit.Tests.Integration.PersonalAccessTokenTestDiscoverer", "Octokit.Tests.Integration")]
+ public class PersonalAccessTokenTestAttribute : FactAttribute
+ {
+ }
+}
diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
index 87dd6b7a9d..f6154d2829 100644
--- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
+++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
@@ -75,6 +75,7 @@
+
@@ -101,6 +102,7 @@
+
diff --git a/Octokit.Tests/GitHubClientTests.cs b/Octokit.Tests/GitHubClientTests.cs
index 1b0fc24293..d3f8cb797d 100644
--- a/Octokit.Tests/GitHubClientTests.cs
+++ b/Octokit.Tests/GitHubClientTests.cs
@@ -5,6 +5,7 @@
using Octokit.Internal;
using Xunit;
using Xunit.Extensions;
+using System.Collections.Generic;
namespace Octokit.Tests
{
@@ -104,5 +105,71 @@ public void IsRetrievedFromCredentialStore()
Assert.Equal("bar", client.Credentials.Password);
}
}
+
+ public class TheLastApiInfoProperty
+ {
+ [Fact]
+ public async Task ReturnsNullIfNew()
+ {
+ var connection = Substitute.For();
+ connection.GetLastApiInfo().Returns((ApiInfo)null);
+ var client = new GitHubClient(connection);
+
+ var result = client.GetLastApiInfo();
+
+ Assert.Null(result);
+
+ var temp = connection.Received(1).GetLastApiInfo();
+ }
+
+ [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.GetLastApiInfo().Returns(apiInfo);
+ var client = new GitHubClient(connection);
+
+ var result = client.GetLastApiInfo();
+
+ Assert.NotNull(result);
+
+ var temp = connection.Received(1).GetLastApiInfo();
+ }
+ }
+
}
}
diff --git a/Octokit.Tests/Http/ApiInfoTests.cs b/Octokit.Tests/Http/ApiInfoTests.cs
new file mode 100644
index 0000000000..5971c58f31
--- /dev/null
+++ b/Octokit.Tests/Http/ApiInfoTests.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Octokit.Tests.Http
+{
+ public class ApiInfoTests
+ {
+ public class TheMethods
+ {
+ [Fact]
+ public void CanClone()
+ {
+ var original = 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 clone = original.Clone();
+
+ // Note the use of Assert.NotSame tests for value types - this should continue to test should the underlying
+ // model are changed to Object types
+ Assert.NotSame(original, clone);
+
+ Assert.Equal(original.Etag, clone.Etag);
+ Assert.NotSame(original.Etag, clone.Etag);
+
+ Assert.Equal(original.AcceptedOauthScopes.Count, clone.AcceptedOauthScopes.Count);
+ Assert.NotSame(original.AcceptedOauthScopes, clone.AcceptedOauthScopes);
+ for (int i = 0; i < original.AcceptedOauthScopes.Count; i++)
+ {
+ Assert.Equal(original.AcceptedOauthScopes[i], clone.AcceptedOauthScopes[i]);
+ Assert.NotSame(original.AcceptedOauthScopes[i], clone.AcceptedOauthScopes[i]);
+ }
+
+ Assert.Equal(original.Links.Count, clone.Links.Count);
+ Assert.NotSame(original.Links, clone.Links);
+ for (int i = 0; i < original.Links.Count; i++)
+ {
+ Assert.Equal(original.Links.Keys.ToArray()[i], clone.Links.Keys.ToArray()[i]);
+ Assert.NotSame(original.Links.Keys.ToArray()[i], clone.Links.Keys.ToArray()[i]);
+ Assert.Equal(original.Links.Values.ToArray()[i].ToString(), clone.Links.Values.ToArray()[i].ToString());
+ Assert.NotSame(original.Links.Values.ToArray()[i], clone.Links.Values.ToArray()[i]);
+ }
+
+ Assert.Equal(original.OauthScopes.Count, clone.OauthScopes.Count);
+ Assert.NotSame(original.OauthScopes, clone.OauthScopes);
+ for (int i = 0; i < original.OauthScopes.Count; i++)
+ {
+ Assert.Equal(original.OauthScopes[i], clone.OauthScopes[i]);
+ Assert.NotSame(original.OauthScopes[i], clone.OauthScopes[i]);
+ }
+
+ Assert.NotSame(original.RateLimit, clone.RateLimit);
+ Assert.Equal(original.RateLimit.Limit, clone.RateLimit.Limit);
+ Assert.NotSame(original.RateLimit.Limit, clone.RateLimit.Limit);
+ Assert.Equal(original.RateLimit.Remaining, clone.RateLimit.Remaining);
+ Assert.NotSame(original.RateLimit.Remaining, clone.RateLimit.Remaining);
+ Assert.Equal(original.RateLimit.ResetAsUtcEpochSeconds, clone.RateLimit.ResetAsUtcEpochSeconds);
+ Assert.NotSame(original.RateLimit.ResetAsUtcEpochSeconds, clone.RateLimit.ResetAsUtcEpochSeconds);
+ Assert.Equal(original.RateLimit.Reset, clone.RateLimit.Reset);
+ Assert.NotSame(original.RateLimit.Reset, clone.RateLimit.Reset);
+ }
+ }
+
+ }
+}
diff --git a/Octokit.Tests/Http/ConnectionTests.cs b/Octokit.Tests/Http/ConnectionTests.cs
index eebc34d821..05c4eae1ca 100644
--- a/Octokit.Tests/Http/ConnectionTests.cs
+++ b/Octokit.Tests/Http/ConnectionTests.cs
@@ -4,8 +4,10 @@
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
using NSubstitute;
+using NSubstitute.Core.Arguments;
using Octokit.Internal;
using Octokit.Tests.Helpers;
using Xunit;
@@ -615,5 +617,91 @@ public void CreatesConnectionWithBaseAddress()
Assert.True(connection.UserAgent.StartsWith("OctokitTests ("));
}
}
+
+ public class TheLastAPiInfoProperty
+ {
+ [Fact]
+ public async Task ReturnsNullIfNew()
+ {
+ var httpClient = Substitute.For();
+ var connection = new Connection(new ProductHeaderValue("OctokitTests"),
+ _exampleUri,
+ Substitute.For(),
+ httpClient,
+ Substitute.For());
+
+ var result = connection.GetLastApiInfo();
+
+ Assert.Null(result);
+ }
+
+ [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();
+
+ // We really only care about the ApiInfo property...
+ var expectedResponse = new Response(HttpStatusCode.OK, null, new Dictionary(), "application/json")
+ {
+ ApiInfo = apiInfo
+ };
+
+ httpClient.Send(Arg.Any(), Arg.Any())
+ .Returns(Task.FromResult(expectedResponse));
+
+ var connection = new Connection(new ProductHeaderValue("OctokitTests"),
+ _exampleUri,
+ Substitute.For(),
+ httpClient,
+ Substitute.For());
+
+ connection.Get(new Uri("https://example.com"), TimeSpan.MaxValue);
+
+ var result = connection.GetLastApiInfo();
+
+ // 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);
+ }
+ }
}
}
diff --git a/Octokit.Tests/Http/RateLimitTests.cs b/Octokit.Tests/Http/RateLimitTests.cs
index 3b0db94424..9473d05e63 100644
--- a/Octokit.Tests/Http/RateLimitTests.cs
+++ b/Octokit.Tests/Http/RateLimitTests.cs
@@ -109,6 +109,30 @@ public void EnsuresHeadersNotNull()
{
Assert.Throws(() => new RateLimit(null));
}
+
+ }
+
+ public class TheMethods
+ {
+ [Fact]
+ public void CanClone()
+ {
+ var original = new RateLimit(100, 42, 1372700873);
+
+ var clone = original.Clone();
+
+ // Note the use of Assert.NotSame tests for value types - this should continue to test should the underlying
+ // model are changed to Object types
+ Assert.NotSame(original, clone);
+ Assert.Equal(original.Limit, clone.Limit);
+ Assert.NotSame(original.Limit, clone.Limit);
+ Assert.Equal(original.Remaining, clone.Remaining);
+ Assert.NotSame(original.Remaining, clone.Remaining);
+ Assert.Equal(original.ResetAsUtcEpochSeconds, clone.ResetAsUtcEpochSeconds);
+ Assert.NotSame(original.ResetAsUtcEpochSeconds, clone.ResetAsUtcEpochSeconds);
+ Assert.Equal(original.Reset, clone.Reset);
+ Assert.NotSame(original.Reset, clone.Reset);
+ }
}
}
}
\ No newline at end of file
diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj
index c9ae7fde8c..3c5335c573 100644
--- a/Octokit.Tests/Octokit.Tests.csproj
+++ b/Octokit.Tests/Octokit.Tests.csproj
@@ -130,6 +130,7 @@
+
Code
diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs
index d88a625720..6115f282a0 100644
--- a/Octokit/GitHubClient.cs
+++ b/Octokit/GitHubClient.cs
@@ -100,6 +100,15 @@ public GitHubClient(IConnection connection)
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 GetLastApiInfo()
+ {
+ return Connection.GetLastApiInfo();
+ }
+
///
/// Convenience property for getting and setting credentials.
///
diff --git a/Octokit/Helpers/CollectionExtensions.cs b/Octokit/Helpers/CollectionExtensions.cs
index 15cc101bef..3481918c1d 100644
--- a/Octokit/Helpers/CollectionExtensions.cs
+++ b/Octokit/Helpers/CollectionExtensions.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Linq;
+using System.Collections.Generic;
namespace Octokit
{
@@ -11,5 +13,37 @@ public static TValue SafeGet(this IReadOnlyDictionary Clone(this IReadOnlyList input)
+ {
+ List output = null;
+ if (input == null)
+ return output;
+
+ output = new List();
+
+ foreach (var item in input)
+ {
+ output.Add(new String(item.ToCharArray()));
+ }
+
+ return output;
+ }
+
+ public static IDictionary Clone(this IReadOnlyDictionary input)
+ {
+ Dictionary output = null;
+ if (input == null)
+ return output;
+
+ output = new Dictionary();
+
+ foreach (var item in input)
+ {
+ output.Add(new String(item.Key.ToCharArray()), new Uri(item.Value.ToString()));
+ }
+
+ return output;
+ }
}
}
diff --git a/Octokit/Http/ApiInfo.cs b/Octokit/Http/ApiInfo.cs
index 54550cb76e..9a47f70de8 100644
--- a/Octokit/Http/ApiInfo.cs
+++ b/Octokit/Http/ApiInfo.cs
@@ -51,5 +51,25 @@ public ApiInfo(IDictionary links,
/// Information about the API rate limit
///
public RateLimit RateLimit { get; private set; }
+
+ ///
+ /// Allows you to clone ApiInfo
+ ///
+ /// A clone of
+ public ApiInfo Clone()
+ {
+ // Seem to have to do this to pass a whole bunch of tests (for example Octokit.Tests.Clients.EventsClientTests.DeserializesCommitCommentEventCorrectly)
+ // I believe this has something to do with the Mocking framework.
+ if (this.Links == null || this.OauthScopes == null || this.RateLimit == null || this.Etag == null)
+ return null;
+
+ return new ApiInfo(this.Links.Clone(),
+ this.OauthScopes.Clone(),
+ this.AcceptedOauthScopes.Clone(),
+ new String(this.Etag.ToCharArray()),
+ this.RateLimit.Clone());
+ }
+
+
}
}
diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs
index 846923d3d8..5b086ca348 100644
--- a/Octokit/Http/Connection.cs
+++ b/Octokit/Http/Connection.cs
@@ -136,6 +136,20 @@ public Connection(
_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 GetLastApiInfo()
+ {
+ // We've choosen to not wrap the _lastApiInfo in a lock. Originally the code was returning a reference - so there was a danger of
+ // on thread writing to the object while another was reading. Now we are cloning the ApiInfo on request - thus removing the need (or overhead)
+ // of putting locks in place.
+ // See https://github.com/octokit/octokit.net/pull/855#discussion_r36774884
+ return _lastApiInfo == null ? null : _lastApiInfo.Clone();
+ }
+ private ApiInfo _lastApiInfo;
+
public Task> Get(Uri uri, IDictionary parameters, string accepts)
{
Ensure.ArgumentNotNull(uri, "uri");
@@ -524,6 +538,11 @@ async Task RunRequest(IRequest request, CancellationToken cancellatio
request.Headers.Add("User-Agent", UserAgent);
await _authenticator.Apply(request).ConfigureAwait(false);
var response = await _httpClient.Send(request, cancellationToken).ConfigureAwait(false);
+ if (response != null)
+ {
+ // Use the clone method to avoid keeping hold of the original (just in case it effect the lifetime of the whole response
+ _lastApiInfo = response.ApiInfo.Clone();
+ }
HandleErrors(response);
return response;
}
diff --git a/Octokit/Http/IApiInfoProvider.cs b/Octokit/Http/IApiInfoProvider.cs
new file mode 100644
index 0000000000..c0b5dfb987
--- /dev/null
+++ b/Octokit/Http/IApiInfoProvider.cs
@@ -0,0 +1,15 @@
+namespace Octokit
+{
+ ///
+ /// Provides a property for the Last recorded API infomation
+ ///
+ public interface IApiInfoProvider
+ {
+ ///
+ /// 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
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+ ApiInfo GetLastApiInfo();
+ }
+}
diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs
index 7deec1ccb8..bde7885afb 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 : IApiInfoProvider
{
///
/// Performs an asynchronous HTTP GET request that expects a containing HTML.
diff --git a/Octokit/Http/RateLimit.cs b/Octokit/Http/RateLimit.cs
index 6d30371164..6a3950eecc 100644
--- a/Octokit/Http/RateLimit.cs
+++ b/Octokit/Http/RateLimit.cs
@@ -99,5 +99,19 @@ internal string DebuggerDisplay
}
}
+ ///
+ /// Allows you to clone RateLimit
+ ///
+ /// A clone of
+ public RateLimit Clone()
+ {
+ return new RateLimit
+ {
+ Limit = this.Limit,
+ Remaining = this.Remaining,
+ ResetAsUtcEpochSeconds = this.ResetAsUtcEpochSeconds
+ };
+ }
+
}
}
diff --git a/Octokit/Http/Response.cs b/Octokit/Http/Response.cs
index 89b2750b3d..c143cdddc7 100644
--- a/Octokit/Http/Response.cs
+++ b/Octokit/Http/Response.cs
@@ -43,7 +43,7 @@ public Response(HttpStatusCode statusCode, object body, IDictionary
/// Information about the API response parsed from the response headers.
///
- public ApiInfo ApiInfo { get; private set; }
+ public ApiInfo ApiInfo { get; internal set; } // This setter is internal for use in tests.
///
/// The response status code.
///
diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs
index a828c83e42..7db4a5554c 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 : IApiInfoProvider
{
///
/// Provides a client connection to make rest requests to HTTP endpoints.
diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj
index 8b5eedaca9..77417e73f4 100644
--- a/Octokit/Octokit-Mono.csproj
+++ b/Octokit/Octokit-Mono.csproj
@@ -400,6 +400,7 @@
+
\ No newline at end of file
diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj
index a34a7b1ef9..3ef8050e70 100644
--- a/Octokit/Octokit-MonoAndroid.csproj
+++ b/Octokit/Octokit-MonoAndroid.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -416,6 +416,7 @@
+
\ No newline at end of file
diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj
index 3f4b6f9cfa..51b7b67624 100644
--- a/Octokit/Octokit-Monotouch.csproj
+++ b/Octokit/Octokit-Monotouch.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -409,6 +409,7 @@
+
diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj
index 36e840bd46..7e822d0785 100644
--- a/Octokit/Octokit-Portable.csproj
+++ b/Octokit/Octokit-Portable.csproj
@@ -399,6 +399,7 @@
+
diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj
index 17d8bae84e..862c55a477 100644
--- a/Octokit/Octokit-netcore45.csproj
+++ b/Octokit/Octokit-netcore45.csproj
@@ -403,6 +403,7 @@
+
diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj
index 760b5bfe67..b5c513c6fe 100644
--- a/Octokit/Octokit.csproj
+++ b/Octokit/Octokit.csproj
@@ -84,6 +84,7 @@
+