using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using NSubstitute; using Octokit.Caching; using Octokit.Internal; using Xunit; using static Octokit.Internal.TestSetup; namespace Octokit.Tests.Http { public class ConnectionTests { const string exampleUrl = "http://example.com"; static readonly Uri _exampleUri = new Uri(exampleUrl); public class TheGetMethod { [Fact] public async Task SendsProperlyFormattedRequest() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.GetResponse(new Uri("endpoint", UriKind.Relative)); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.ContentType == null && req.Body == null && req.Method == HttpMethod.Get && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task CanMakeMultipleRequestsWithSameConnection() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.GetResponse(new Uri("endpoint", UriKind.Relative)); await connection.GetResponse(new Uri("endpoint", UriKind.Relative)); await connection.GetResponse(new Uri("endpoint", UriKind.Relative)); httpClient.Received(3).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.Method == HttpMethod.Get && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task ParsesApiInfoOnResponse() { var httpClient = Substitute.For(); var headers = new Dictionary { { "X-Accepted-OAuth-Scopes", "user" } }; var response = CreateResponse(HttpStatusCode.OK, headers); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var resp = await connection.GetResponse(new Uri("endpoint", UriKind.Relative)); Assert.NotNull(resp.HttpResponse.ApiInfo); Assert.Equal("user", resp.HttpResponse.ApiInfo.AcceptedOauthScopes.First()); } [Fact] public async Task ThrowsAuthorizationExceptionExceptionForUnauthorizedResponse() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.Unauthorized); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.NotNull(exception); } [Theory] [InlineData("missing", "")] [InlineData("missing", "required; sms")] [InlineData("X-GitHub-OTP", "blah")] [InlineData("X-GitHub-OTP", "foo; sms")] public async Task ThrowsUnauthorizedExceptionExceptionWhenChallengedWithBadHeader( string headerKey, string otpHeaderValue) { var headers = new Dictionary { { headerKey, otpHeaderValue } }; var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.Unauthorized, headers); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal(HttpStatusCode.Unauthorized, exception.StatusCode); } [Theory] [InlineData("X-GitHub-OTP", "required", TwoFactorType.Unknown)] [InlineData("X-GitHub-OTP", "required;", TwoFactorType.Unknown)] [InlineData("X-GitHub-OTP", "required; poo", TwoFactorType.Unknown)] [InlineData("X-GitHub-OTP", "required; app", TwoFactorType.AuthenticatorApp)] [InlineData("X-GitHub-OTP", "required; sms", TwoFactorType.Sms)] [InlineData("x-github-otp", "required; sms", TwoFactorType.Sms)] public async Task ThrowsTwoFactorExceptionExceptionWhenChallenged( string headerKey, string otpHeaderValue, TwoFactorType expectedFactorType) { var headers = new Dictionary { { headerKey, otpHeaderValue } }; var response = CreateResponse(HttpStatusCode.Unauthorized, headers); var httpClient = Substitute.For(); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal(expectedFactorType, exception.TwoFactorType); } [Fact] public async Task ThrowsApiValidationExceptionFor422Response() { var httpClient = Substitute.For(); var response = CreateResponse( (HttpStatusCode)422, @"{""errors"":[{""code"":""custom"",""field"":""key"",""message"":""key is " + @"already in use"",""resource"":""PublicKey""}],""message"":""Validation Failed""}" ); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("Validation Failed", exception.Message); Assert.Equal("key is already in use", exception.ApiError.Errors[0].Message); } [Fact] public async Task ThrowsRateLimitExceededExceptionForForbiddenResponse() { var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"API rate limit exceeded. " + "See http://developer.github.com/v3/#rate-limiting for details.\"}"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("API rate limit exceeded. See http://developer.github.com/v3/#rate-limiting for details.", exception.Message); } [Fact] public async Task ThrowsSecondaryRateLimitExceededExceptionForForbiddenResponse() { var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"You have exceeded a secondary rate limit. Please wait a few minutes before you try again.\"}"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("You have exceeded a secondary rate limit. Please wait a few minutes before you try again.", exception.Message); } [Fact] public async Task ThrowsLoginAttemptsExceededExceptionForForbiddenResponse() { var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"Maximum number of login attempts exceeded\"," + "\"documentation_url\":\"http://developer.github.com/v3\"}"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("Maximum number of login attempts exceeded", exception.Message); Assert.Equal("http://developer.github.com/v3", exception.ApiError.DocumentationUrl); } [Fact] public async Task ThrowsNotFoundExceptionForFileNotFoundResponse() { var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.NotFound, "GONE BYE BYE!"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("GONE BYE BYE!", exception.Message); } [Fact] public async Task ThrowsForbiddenExceptionForUnknownForbiddenResponse() { var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "YOU SHALL NOT PASS!"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("YOU SHALL NOT PASS!", exception.Message); } [Fact] public async Task ThrowsAbuseExceptionForResponseWithAbuseDocumentationLink() { var messageText = "blahblahblah this does not matter because we are testing the URL"; var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"" + messageText + "\"," + "\"documentation_url\":\"https://developer.github.com/v3/#abuse-rate-limits\"}"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); } [Fact] public async Task ThrowsAbuseExceptionForResponseWithAbuseDescription() { var messageText = "You have triggered an abuse detection mechanism. Please wait a few minutes before you try again."; var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"" + messageText + "\"," + "\"documentation_url\":\"https://ThisURLDoesNotMatter.com\"}"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); } [Fact] public async Task AbuseExceptionContainsTheRetryAfterHeaderAmount() { var messageText = "You have triggered an abuse detection mechanism. Please wait a few minutes before you try again."; var httpClient = Substitute.For(); var headerDictionary = new Dictionary { { "Retry-After", "45" } }; var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"" + messageText + "\"," + "\"documentation_url\":\"https://ThisURLDoesNotMatter.com\"}", headerDictionary); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal(45, exception.RetryAfterSeconds); } [Fact] public async Task ThrowsAbuseExceptionWithDefaultMessageForUnsafeAbuseResponse() { string messageText = string.Empty; var httpClient = Substitute.For(); var response = CreateResponse( HttpStatusCode.Forbidden, "{\"message\":\"" + messageText + "\"," + "\"documentation_url\":\"https://developer.github.com/v3/#abuse-rate-limits\"}"); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var exception = await Assert.ThrowsAsync( () => connection.GetResponse(new Uri("endpoint", UriKind.Relative))); Assert.Equal("Request Forbidden - Abuse Detection", exception.Message); } } public class TheGetHtmlMethod { [Fact] public async Task SendsProperlyFormattedRequestWithProperAcceptHeader() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.GetHtml(new Uri("endpoint", UriKind.Relative)); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.ContentType == null && req.Body == null && req.Method == HttpMethod.Get && req.Headers["Accept"] == "application/vnd.github.v3.html" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } } public class TheGetRawMethod { [Fact] public async Task SendsProperlyFormattedRequestWithProperAcceptHeader() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.GetRaw(new Uri("endpoint", UriKind.Relative), new Dictionary()); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.ContentType == null && req.Body == null && req.Method == HttpMethod.Get && req.Headers["Accept"] == "application/vnd.github.v3.raw" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task SendsProperlyFormattedRequestWithProperAcceptHeaderAndTimeout() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.GetRaw(new Uri("endpoint", UriKind.Relative), new Dictionary(), TimeSpan.FromSeconds(1)); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.Timeout == TimeSpan.FromSeconds(1) && req.ContentType == null && req.Body == null && req.Method == HttpMethod.Get && req.Headers["Accept"] == "application/vnd.github.v3.raw" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task ReturnsCorrectContentForNull() { object body = null; var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK, body); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var actual = await connection.GetRaw(new Uri("endpoint", UriKind.Relative), new Dictionary()); Assert.NotNull(actual); Assert.Null(actual.Body); } [Fact] public async Task ReturnsCorrectContentForByteArray() { var body = new byte[] { 1, 2, 3 }; var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK, body); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var actual = await connection.GetRaw(new Uri("endpoint", UriKind.Relative), new Dictionary()); Assert.NotNull(actual); Assert.Equal(body, actual.Body); } [Fact] public async Task ReturnsCorrectContentForStream() { var bytes = new byte[] { 1, 2, 3 }; var body = new MemoryStream(bytes); var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK, body); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var actual = await connection.GetRaw(new Uri("endpoint", UriKind.Relative), new Dictionary()); Assert.NotNull(actual); Assert.Equal(bytes, actual.Body); } } public class ThePatchMethod { [Fact] public async Task RunsConfiguredAppWithAppropriateEnv() { var body = new object(); var expectedData = SimpleJson.SerializeObject(body); var serializer = Substitute.For(); serializer.Serialize(body).Returns(expectedData); var response = CreateResponse(HttpStatusCode.OK); var httpClient = Substitute.For(); httpClient.Send(Args.Request, Args.CancellationToken) .Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, serializer); await connection.Patch(new Uri("endpoint", UriKind.Relative), body); serializer.Received(1).Serialize(body); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && (string)req.Body == expectedData && req.Method == HttpVerb.Patch && req.ContentType == "application/x-www-form-urlencoded" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task RunsConfiguredAppWithAcceptsOverride() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.Patch(new Uri("endpoint", UriKind.Relative), new object(), "custom/accepts"); httpClient.Received(1).Send(Arg.Is(req => req.Headers["Accept"] == "custom/accepts"), Args.CancellationToken); } } public class ThePutMethod { [Fact] public async Task MakesPutRequestWithData() { var body = new object(); var serializer = Substitute.For(); var expectedBody = SimpleJson.SerializeObject(body); var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); serializer.Serialize(body).Returns(expectedBody); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, serializer); await connection.Put(new Uri("endpoint", UriKind.Relative), body); serializer.Received(1).Serialize(body); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && (string)req.Body == expectedBody && req.Method == HttpMethod.Put && req.ContentType == "application/x-www-form-urlencoded" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task MakesPutRequestWithNoData() { var body = RequestBody.Empty; var serializer = Substitute.For(); var expectedBody = SimpleJson.SerializeObject(body); var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); serializer.Serialize(body).Returns(expectedBody); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, serializer); await connection.Put(new Uri("endpoint", UriKind.Relative), body); serializer.Received(1).Serialize(body); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && (string)req.Body == expectedBody && req.Method == HttpMethod.Put && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task MakesPutRequestWithDataAndTwoFactor() { var body = new object(); var serializer = Substitute.For(); var expectedBody = SimpleJson.SerializeObject(body); var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); serializer.Serialize(body).Returns(expectedBody); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, serializer); await connection.Put(new Uri("endpoint", UriKind.Relative), body, "two-factor"); serializer.Received(1).Serialize(body); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && (string)req.Body == expectedBody && req.Method == HttpMethod.Put && req.Headers["X-GitHub-OTP"] == "two-factor" && req.ContentType == "application/x-www-form-urlencoded" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task MakesPutRequestWithNoDataAndTwoFactor() { var body = RequestBody.Empty; var serializer = Substitute.For(); var expectedBody = SimpleJson.SerializeObject(body); var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); serializer.Serialize(body).Returns(expectedBody); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, serializer); await connection.Put(new Uri("endpoint", UriKind.Relative), body, "two-factor"); serializer.Received(1).Serialize(body); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && (string)req.Body == expectedBody && req.Method == HttpMethod.Put && req.Headers["X-GitHub-OTP"] == "two-factor" && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } } public class ThePostMethod { [Fact] public async Task SendsProperlyFormattedPostRequest() { var body = new object(); var serializer = Substitute.For(); var data = SimpleJson.SerializeObject(body); var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); serializer.Serialize(body).Returns(data); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, serializer); await connection.Post(new Uri("endpoint", UriKind.Relative), body, null, null); serializer.Received(1).Serialize(body); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.ContentType == "application/x-www-form-urlencoded" && (string)req.Body == data && req.Method == HttpMethod.Post && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } [Fact] public async Task SendsProperlyFormattedPostRequestWithCorrectHeaders() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var body = new MemoryStream(new byte[] { 48, 49, 50 }); await connection.Post( new Uri("https://other.host.com/path?query=val"), body, null, "application/arbitrary"); httpClient.Received().Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.Body == body && req.Headers["Accept"] == "application/vnd.github.v3+json" && req.ContentType == "application/arbitrary" && req.Method == HttpMethod.Post && req.Endpoint == new Uri("https://other.host.com/path?query=val")), Args.CancellationToken); } [Fact] public async Task SetsAcceptsHeader() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); var body = new MemoryStream(new byte[] { 48, 49, 50 }); await connection.Post( new Uri("https://other.host.com/path?query=val"), body, "application/json", null); httpClient.Received().Send(Arg.Is(req => req.Headers["Accept"] == "application/json" && req.ContentType == "application/x-www-form-urlencoded"), Args.CancellationToken); } } public class TheDeleteMethod { [Fact] public async Task SendsProperlyFormattedDeleteRequest() { var httpClient = Substitute.For(); var response = CreateResponse(HttpStatusCode.OK); httpClient.Send(Args.Request, Args.CancellationToken).Returns(Task.FromResult(response)); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); await connection.Delete(new Uri("endpoint", UriKind.Relative)); httpClient.Received(1).Send(Arg.Is(req => req.BaseAddress == _exampleUri && req.Body == null && req.ContentType == null && req.Method == HttpMethod.Delete && req.Endpoint == new Uri("endpoint", UriKind.Relative)), Args.CancellationToken); } } public class TheConstructor { [Fact] public void EnsuresAbsoluteBaseAddress() { Assert.Throws(() => new Connection(new ProductHeaderValue("TestRunner"), new Uri("foo", UriKind.Relative))); Assert.Throws(() => new Connection(new ProductHeaderValue("TestRunner"), new Uri("foo", UriKind.RelativeOrAbsolute))); } [Fact] public void EnsuresNonNullArguments() { // 1 arg Assert.Throws(() => new Connection(null)); // 2 args Assert.Throws(() => new Connection(null, new Uri("https://example.com"))); Assert.Throws(() => new Connection(new ProductHeaderValue("test"), (Uri)null)); // 3 args Assert.Throws(() => new Connection(null, new Uri("https://example.com"), Substitute.For())); Assert.Throws(() => new Connection(new ProductHeaderValue("foo"), null, Substitute.For())); Assert.Throws(() => new Connection(new ProductHeaderValue("foo"), new Uri("https://example.com"), null)); // 5 Args Assert.Throws(() => new Connection(null , new Uri("https://example.com"), Substitute.For(), Substitute.For(), Substitute.For())); Assert.Throws(() => new Connection(new ProductHeaderValue("foo"), new Uri("https://example.com"), Substitute.For(), Substitute.For(), null)); Assert.Throws(() => new Connection(new ProductHeaderValue("foo"), new Uri("https://example.com"), Substitute.For(), null, Substitute.For())); Assert.Throws(() => new Connection(new ProductHeaderValue("foo"), new Uri("https://example.com"), null, Substitute.For(), Substitute.For())); Assert.Throws(() => new Connection(new ProductHeaderValue("foo"), null, Substitute.For(), Substitute.For(), Substitute.For())); } [Fact] public void CreatesConnectionWithBaseAddress() { var connection = new Connection(new ProductHeaderValue("OctokitTests"), new Uri("https://github.com/")); Assert.Equal(new Uri("https://github.com/"), connection.BaseAddress); Assert.StartsWith("OctokitTests (", connection.UserAgent); Assert.Contains("Octokit.net", connection.UserAgent); } } 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 = Substitute.For(); expectedResponse.StatusCode.Returns(HttpStatusCode.OK); expectedResponse.ApiInfo.Returns(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.Single(result.OauthScopes); Assert.Equal(4, result.AcceptedOauthScopes.Count); Assert.Equal("5634b0b187fd2e91e3126a75006cc4fa", result.Etag); Assert.Equal(100, result.RateLimit.Limit); } } public class TheResponseCacheProperty { [Fact] public void WhenSetWrapsExistingHttpClientWithCachingHttpClient() { var responseCache = Substitute.For(); var httpClient = Substitute.For(); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); connection.ResponseCache = responseCache; Assert.IsType(connection._httpClient); var cachingHttpClient = (CachingHttpClient) connection._httpClient; Assert.Equal(httpClient, cachingHttpClient._httpClient); Assert.Equal(responseCache, cachingHttpClient._responseCache); } [Fact] public void WhenResetWrapsUnderlyingHttpClientWithCachingHttpClient() { var responseCache = Substitute.For(); var httpClient = Substitute.For(); var connection = new Connection(new ProductHeaderValue("OctokitTests"), _exampleUri, Substitute.For(), httpClient, Substitute.For()); connection.ResponseCache = Substitute.For(); connection.ResponseCache = responseCache; Assert.IsType(connection._httpClient); var cachingHttpClient = (CachingHttpClient) connection._httpClient; Assert.Equal(httpClient, cachingHttpClient._httpClient); Assert.Equal(responseCache, cachingHttpClient._responseCache); } } } }