From 4f826bc65ba7d52fd0dd1e46ebfe9c19cd6011ed Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Tue, 7 Nov 2023 00:47:30 +0000 Subject: [PATCH] [fix] Fix handling for Streams in IConnection for raw content (#2791) * Fix handling for Streams Fix `NullReferenceException` if raw content was handled as a string rather than a stream by `HttpClientAdapter.BuildResponse()`. Resolves #2789. * Add Connection.GetRaw tests Add tests for `Connection.GetRaw()` and for #2789. --------- Co-authored-by: Keegan Campbell --- Octokit.Tests/Http/ConnectionTests.cs | 110 ++++++++++++++++++++++++++ Octokit/Http/Connection.cs | 9 ++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/Octokit.Tests/Http/ConnectionTests.cs b/Octokit.Tests/Http/ConnectionTests.cs index c915386fae..1f6b9b362d 100644 --- a/Octokit.Tests/Http/ConnectionTests.cs +++ b/Octokit.Tests/Http/ConnectionTests.cs @@ -416,6 +416,116 @@ public async Task SendsProperlyFormattedRequestWithProperAcceptHeader() } } + 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] diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 3225bf88db..11a67d4502 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -714,8 +714,13 @@ async Task> GetRaw(IRequest request) { request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType); var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false); - - return new ApiResponse(response, await StreamToByteArray(response.Body as Stream)); + + if (response.Body is Stream stream) + { + return new ApiResponse(response, await StreamToByteArray(stream)); + } + + return new ApiResponse(response, response.Body as byte[]); } async Task> GetRawStream(IRequest request)