diff --git a/Activout.RestClient.Newtonsoft.Json.Test/MovieReviews/IMovieReviewService.cs b/Activout.RestClient.Newtonsoft.Json.Test/MovieReviews/IMovieReviewService.cs index 6fb1c6f..07b147b 100644 --- a/Activout.RestClient.Newtonsoft.Json.Test/MovieReviews/IMovieReviewService.cs +++ b/Activout.RestClient.Newtonsoft.Json.Test/MovieReviews/IMovieReviewService.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -25,13 +23,6 @@ public interface IMovieReviewService [Get("/{movieId}/reviews/{reviewId}")] Review GetReview(string movieId, string reviewId); - [Delete("/{movieId}/reviews/{reviewId}")] - void DeleteReview(string movieId, string reviewId); - - [Get("/fail")] - [ErrorResponse(typeof(byte[]))] - void Fail(); - [Post] [Path("/{movieId}/reviews")] Task SubmitReview([PathParam("movieId")] string movieId, Review review); @@ -44,65 +35,15 @@ public interface IMovieReviewService [Path("/{movieId}/reviews/{reviewId}")] Review PartialUpdateReview(string movieId, [PathParam] string reviewId, Review review); - [Post("/import.csv")] - [ContentType("text/csv")] - Task Import(string csv); - [Get] Task> QueryMoviesByDate( [QueryParam] DateTime begin, [QueryParam] DateTime end); - HttpContent GetHttpContent(); - - HttpResponseMessage GetHttpResponseMessage(); - [Path("/object")] JObject GetJObject(); [Path("/array")] Task GetJArray(); - - [Post("/form")] - Task FormPost([FormParam] string value); - - [Path("/headers")] - Task SendFooHeader([HeaderParam("X-Foo")] string foo); - - [Path("/bytes")] - Task GetByteArray(); - - [Path("/byte-object")] - Task GetByteArrayObject(); - - [Path("/string")] - [Accept("text/plain")] - Task GetString(); - - [Path("/string-object")] - [Accept("text/plain")] - Task GetStringObject(); - } - - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] - public class StringObject - { - public string Value { get; } - - public StringObject(string value) - { - Value = value; - } - } - - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] - public class ByteArrayObject - { - public byte[] Bytes { get; } - - public ByteArrayObject(byte[] bytes) - { - Bytes = bytes; - } } } \ No newline at end of file diff --git a/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs b/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs index ec19802..3dd1a10 100644 --- a/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs +++ b/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs @@ -2,14 +2,12 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using Activout.RestClient.Helpers.Implementation; using Activout.RestClient.Newtonsoft.Json.Test.MovieReviews; using Microsoft.Extensions.Logging; -using Moq; using Newtonsoft.Json; using RichardSzalay.MockHttp; using Xunit; @@ -149,105 +147,6 @@ public void TestErrorSync() Assert.Equal("Sorry, that page does not exist", error.Errors[0].Message); } - - [Fact] - public async Task TestTimeoutAsync() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies") - .Respond(async () => - { - await Task.Delay(1000); - return null; - }); - - var httpClient = _mockHttp.ToHttpClient(); - httpClient.Timeout = TimeSpan.FromMilliseconds(1); - var reviewSvc = _restClientFactory.CreateBuilder() - .With(httpClient) - .BaseUri(new Uri(BaseUri)) - .Build(); - - // act - await Assert.ThrowsAsync(() => reviewSvc.GetAllMovies()); - - // assert - } - - [Fact] - public async Task TestCancellationAsync() - { - // arrange - _mockHttp.When($"{BaseUri}/movies") - .Respond(_ => null); - - var reviewSvc = CreateMovieReviewService(); - var cancellationTokenSource = new CancellationTokenSource(); - - // act - cancellationTokenSource.Cancel(); - await Assert.ThrowsAsync(() => - reviewSvc.GetAllMoviesCancellable(cancellationTokenSource.Token)); - - // assert - } - - [Fact] - public async Task TestNoCancellationAsync() - { - // arrange - _mockHttp.When($"{BaseUri}/movies") - .Respond("application/json", "[]"); - - var reviewSvc = CreateMovieReviewService(); - - // act - var movies = await reviewSvc.GetAllMoviesCancellable(default); - - // assert - Assert.Empty(movies); - } - - [Fact] - public void TestErrorEmptyNoContentType() - { - // arrange - _mockHttp - .When(HttpMethod.Get, $"{BaseUri}/movies/fail") - .Respond(HttpStatusCode.BadRequest, request => new ByteArrayContent(new byte[0])); - - var reviewSvc = CreateMovieReviewService(); - - // act - var aggregateException = Assert.Throws(() => reviewSvc.Fail()); - - // assert - var exception = (RestClientException)aggregateException.GetBaseException(); - Assert.Equal(HttpStatusCode.BadRequest, exception.StatusCode); - - Assert.NotNull(exception.ErrorResponse); - Assert.IsType(exception.ErrorResponse); - Assert.Empty(exception.GetErrorResponse()); - } - - [Fact] - public void TestDelete() - { - // arrange - _mockHttp - .Expect(HttpMethod.Delete, $"{BaseUri}/movies/{MovieId}/reviews/{ReviewId}") - .Respond(HttpStatusCode.OK); - - var reviewSvc = CreateMovieReviewService(); - - // act - reviewSvc.DeleteReview(MovieId, ReviewId); - - // assert - _mockHttp.VerifyNoOutstandingExpectation(); - } - [Fact] public async Task TestGetEmptyIEnumerableAsync() { @@ -317,25 +216,6 @@ public async Task TestPostJsonAsync() Assert.Equal(text, result.Text); } - [Fact] - public async Task TestPostTextAsync() - { - // arrange - _mockHttp - .When(HttpMethod.Post, $"{BaseUri}/movies/import.csv") - .WithContent("foobar") - .WithHeaders("Content-Type", "text/csv; charset=utf-8") - .Respond(HttpStatusCode.NoContent); - - var reviewSvc = CreateMovieReviewService(); - - // act - await reviewSvc.Import("foobar"); - - // assert - //Assert.Equal("*REVIEW_ID*", result.ReviewId); - } - [Fact] public void TestPutSync() { @@ -394,40 +274,6 @@ public void TestPatchSync() Assert.Equal(text, result.Text); } - [Fact] - public async Task TestGetHttpContent() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies") - .Respond("application/json", "[]"); - - var reviewSvc = CreateMovieReviewService(); - - // act - var httpContent = reviewSvc.GetHttpContent(); - - // assert - Assert.Equal("[]", await httpContent.ReadAsStringAsync()); - } - - [Fact] - public async Task TestGetHttpResponseMessage() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies") - .Respond("application/json", "[]"); - - var reviewSvc = CreateMovieReviewService(); - - // act - var httpResponseMessage = reviewSvc.GetHttpResponseMessage(); - - // assert - Assert.Equal("[]", await httpResponseMessage.Content.ReadAsStringAsync()); - } - [Fact] public void TestGetJObject() { @@ -463,159 +309,5 @@ public async Task TestGetJArray() string foo = jArray[0].foo; Assert.Equal("bar", foo); } - - [Fact] - public async Task TestGetByteArray() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies/bytes") - .Respond(new ByteArrayContent(new byte[] { 42 })); - - var reviewSvc = CreateMovieReviewService(); - - // act - var bytes = await reviewSvc.GetByteArray(); - - // assert - Assert.Equal(new byte[] { 42 }, bytes); - } - - [Fact] - public async Task TestGetByteArrayObject() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies/byte-object") - .Respond(new ByteArrayContent(new byte[] { 42 })); - - var reviewSvc = CreateMovieReviewService(); - - // act - var byteArrayObject = await reviewSvc.GetByteArrayObject(); - - // assert - Assert.Equal(new byte[] { 42 }, byteArrayObject.Bytes); - } - - [Fact] - public async Task TestGetString() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies/string") - .WithHeaders("accept", "text/plain") - .Respond(new StringContent("foo")); - - var reviewSvc = CreateMovieReviewService(); - - // act - var result = await reviewSvc.GetString(); - - // assert - Assert.Equal("foo", result); - } - - [Fact] - public async Task TestGetStringObject() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies/string-object") - .WithHeaders("accept", "text/plain") - .Respond(new StringContent("bar")); - - var reviewSvc = CreateMovieReviewService(); - - // act - var stringObject = await reviewSvc.GetStringObject(); - - // assert - Assert.Equal("bar", stringObject.Value); - } - - [Fact] - public async Task TestFormPost() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies/form") - .WithFormData("value", "foobar") - .Respond("text/plain", ""); - - var reviewSvc = CreateMovieReviewService(); - - // act - await reviewSvc.FormPost("foobar"); - - // assert - } - - [Fact] - public async Task TestRequestLogger() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies") - .Respond("application/json", "[]"); - - var requestLoggerMock = new Mock(); - requestLoggerMock.Setup(x => x.TimeOperation(It.IsAny())) - .Returns(() => new Mock().Object); - - var reviewSvc = CreateRestClientBuilder() - .With(requestLoggerMock.Object) - .Build(); - - // act - await reviewSvc.GetAllMovies(); - await reviewSvc.GetAllMovies(); - - // assert - requestLoggerMock.Verify(x => x.TimeOperation(It.IsAny()), Times.Exactly(2)); - requestLoggerMock.VerifyNoOtherCalls(); - _mockHttp.VerifyNoOutstandingExpectation(); - } - - [Fact] - public async Task TestHeaderParam() - { - // arrange - _mockHttp - .When($"{BaseUri}/movies/headers") - .WithHeaders("X-Foo", "bar") - .Respond("text/plain", ""); - - var reviewSvc = CreateRestClientBuilder() - .Header(new AuthenticationHeaderValue("Basic", "SECRET")) - .Header("X-Tick", new TickValue()) - .Build(); - - // act - var responseMessage1 = await reviewSvc.SendFooHeader("bar"); - var requestHeaders1 = responseMessage1.RequestMessage.Headers; - - var responseMessage2 = await reviewSvc.SendFooHeader("bar"); - var requestHeaders2 = responseMessage2.RequestMessage.Headers; - - // assert - Assert.NotNull(requestHeaders1.Authorization); - Assert.Equal("Basic SECRET", requestHeaders1.Authorization.ToString()); - Assert.NotEmpty(requestHeaders1.GetValues("X-Tick")); - - Assert.NotEqual( - requestHeaders1.GetValues("X-Tick").First(), - requestHeaders2.GetValues("X-Tick").First()); - } - } - - internal class TickValue - { - private long _ticks = 42; - - public override string ToString() - { - return Interlocked.Increment(ref _ticks).ToString(); - } } } \ No newline at end of file diff --git a/Activout.RestClient.Test/MovieReviews/ErrorResponse.cs b/Activout.RestClient.Test/MovieReviews/ErrorResponse.cs new file mode 100644 index 0000000..6fb09f3 --- /dev/null +++ b/Activout.RestClient.Test/MovieReviews/ErrorResponse.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Activout.RestClient.Test.MovieReviews; + +public class ErrorResponse +{ + public List Errors { get; set; } + + public class Error + { + public string Message { get; set; } + public int Code { get; set; } + } +} diff --git a/Activout.RestClient.Test/MovieReviews/IMovieReviewService.cs b/Activout.RestClient.Test/MovieReviews/IMovieReviewService.cs new file mode 100644 index 0000000..a2a672a --- /dev/null +++ b/Activout.RestClient.Test/MovieReviews/IMovieReviewService.cs @@ -0,0 +1,65 @@ +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Activout.RestClient.Test.MovieReviews; + +[Path("movies")] +[ErrorResponse(typeof(ErrorResponse))] +public interface IMovieReviewService +{ + [Delete("/{movieId}/reviews/{reviewId}")] + void DeleteReview(string movieId, string reviewId); + + [Get("/fail")] + [ErrorResponse(typeof(byte[]))] + void Fail(); + + [Post("/import.csv")] + [ContentType("text/csv")] + Task Import(string csv); + + HttpContent GetHttpContent(); + + HttpResponseMessage GetHttpResponseMessage(); + + [Path("/bytes")] + Task GetByteArray(); + + [Path("/byte-object")] + Task GetByteArrayObject(); + + [Path("/string")] + [Accept("text/plain")] + Task GetString(); + + [Path("/string-object")] + [Accept("text/plain")] + Task GetStringObject(); + + [Post("/form")] + Task FormPost([FormParam] string value); + + [Path("/headers")] + Task SendFooHeader([HeaderParam("X-Foo")] string foo); + + [Path("/string")] + [Accept("text/plain")] + Task GetStringCancellable(CancellationToken cancellationToken); + + [Path("/bytes")] + Task GetByteArrayCancellable(CancellationToken cancellationToken); +} + +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] +public class StringObject(string value) +{ + public string Value { get; } = value; +} + +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] +public class ByteArrayObject(byte[] bytes) +{ + public byte[] Bytes { get; } = bytes; +} \ No newline at end of file diff --git a/Activout.RestClient.Test/NonJsonRestClientTests.cs b/Activout.RestClient.Test/NonJsonRestClientTests.cs new file mode 100644 index 0000000..cd8f49e --- /dev/null +++ b/Activout.RestClient.Test/NonJsonRestClientTests.cs @@ -0,0 +1,342 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Activout.RestClient.Test.MovieReviews; +using Microsoft.Extensions.Logging; +using Moq; +using RichardSzalay.MockHttp; +using Xunit; +using Xunit.Abstractions; + +namespace Activout.RestClient.Test; + +public class NonJsonRestClientTests(ITestOutputHelper outputHelper) +{ + private const string BaseUri = "https://example.com/movieReviewService"; + private const string MovieId = "*MOVIE_ID*"; + private const string ReviewId = "*REVIEW_ID*"; + + private readonly IRestClientFactory _restClientFactory = Services.CreateRestClientFactory(); + private readonly MockHttpMessageHandler _mockHttp = new MockHttpMessageHandler(); + private readonly ILoggerFactory _loggerFactory = LoggerFactoryHelpers.CreateLoggerFactory(outputHelper); + + private IRestClientBuilder CreateRestClientBuilder() + { + return _restClientFactory.CreateBuilder() + .With(_loggerFactory.CreateLogger()) + .With(_mockHttp.ToHttpClient()) + .BaseUri(BaseUri); + } + + private IMovieReviewService CreateMovieReviewService() + { + return CreateRestClientBuilder() + .Build(); + } + + [Fact] + public async Task TestTimeoutAsync() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/string") + .Respond(async () => + { + await Task.Delay(1000); + return null; + }); + + var httpClient = _mockHttp.ToHttpClient(); + httpClient.Timeout = TimeSpan.FromMilliseconds(1); + var reviewSvc = _restClientFactory.CreateBuilder() + .With(httpClient) + .BaseUri(new Uri(BaseUri)) + .Build(); + + // act + await Assert.ThrowsAsync(() => reviewSvc.GetString()); + + // assert + } + + [Fact] + public async Task TestCancellationAsync() + { + // arrange + _mockHttp.When($"{BaseUri}/movies/string") + .Respond(_ => null); + + var reviewSvc = CreateMovieReviewService(); + var cancellationTokenSource = new CancellationTokenSource(); + + // act + cancellationTokenSource.Cancel(); + await Assert.ThrowsAsync(() => + reviewSvc.GetStringCancellable(cancellationTokenSource.Token)); + + // assert + } + + [Fact] + public async Task TestNoCancellationAsync() + { + // arrange + _mockHttp.When($"{BaseUri}/movies/string") + .Respond("text/plain", "test string"); + + var reviewSvc = CreateMovieReviewService(); + + // act + var result = await reviewSvc.GetStringCancellable(default); + + // assert + Assert.Equal("test string", result); + } + + [Fact] + public async Task TestRequestLogger() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/string") + .Respond("text/plain", "test"); + + var requestLoggerMock = new Mock(); + requestLoggerMock.Setup(x => x.TimeOperation(It.IsAny())) + .Returns(() => new Mock().Object); + + var reviewSvc = CreateRestClientBuilder() + .With(requestLoggerMock.Object) + .Build(); + + // act + await reviewSvc.GetString(); + await reviewSvc.GetString(); + + // assert + requestLoggerMock.Verify(x => x.TimeOperation(It.IsAny()), Times.Exactly(2)); + } + + [Fact] + public void TestErrorEmptyNoContentType() + { + // arrange + _mockHttp + .When(HttpMethod.Get, $"{BaseUri}/movies/fail") + .Respond(HttpStatusCode.BadRequest, request => new ByteArrayContent(new byte[0])); + + var reviewSvc = CreateMovieReviewService(); + + // act + var aggregateException = Assert.Throws(() => reviewSvc.Fail()); + + // assert + var exception = (RestClientException)aggregateException.GetBaseException(); + Assert.Equal(HttpStatusCode.BadRequest, exception.StatusCode); + + Assert.NotNull(exception.ErrorResponse); + Assert.IsType(exception.ErrorResponse); + Assert.Empty(exception.GetErrorResponse()); + } + + [Fact] + public void TestDelete() + { + // arrange + _mockHttp + .Expect(HttpMethod.Delete, $"{BaseUri}/movies/{MovieId}/reviews/{ReviewId}") + .Respond(HttpStatusCode.OK); + + var reviewSvc = CreateMovieReviewService(); + + // act + reviewSvc.DeleteReview(MovieId, ReviewId); + + // assert + _mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task TestPostTextAsync() + { + // arrange + _mockHttp + .When(HttpMethod.Post, $"{BaseUri}/movies/import.csv") + .WithContent("foobar") + .WithHeaders("Content-Type", "text/csv; charset=utf-8") + .Respond(HttpStatusCode.NoContent); + + var reviewSvc = CreateMovieReviewService(); + + // act + await reviewSvc.Import("foobar"); + + // assert + } + + [Fact] + public async Task TestGetHttpContent() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies") + .Respond("text/plain", "test content"); + + var reviewSvc = CreateMovieReviewService(); + + // act + var httpContent = reviewSvc.GetHttpContent(); + + // assert + Assert.Equal("test content", await httpContent.ReadAsStringAsync()); + } + + [Fact] + public async Task TestGetHttpResponseMessage() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies") + .Respond("text/plain", "test content"); + + var reviewSvc = CreateMovieReviewService(); + + // act + var httpResponseMessage = reviewSvc.GetHttpResponseMessage(); + + // assert + Assert.Equal("test content", await httpResponseMessage.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task TestGetByteArray() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/bytes") + .Respond(new ByteArrayContent(new byte[] { 42 })); + + var reviewSvc = CreateMovieReviewService(); + + // act + var bytes = await reviewSvc.GetByteArray(); + + // assert + Assert.Equal(new byte[] { 42 }, bytes); + } + + [Fact] + public async Task TestGetByteArrayObject() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/byte-object") + .Respond(new ByteArrayContent(new byte[] { 42 })); + + var reviewSvc = CreateMovieReviewService(); + + // act + var byteArrayObject = await reviewSvc.GetByteArrayObject(); + + // assert + Assert.Equal(new byte[] { 42 }, byteArrayObject.Bytes); + } + + [Fact] + public async Task TestGetString() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/string") + .WithHeaders("accept", "text/plain") + .Respond(new StringContent("foo")); + + var reviewSvc = CreateMovieReviewService(); + + // act + var result = await reviewSvc.GetString(); + + // assert + Assert.Equal("foo", result); + } + + [Fact] + public async Task TestGetStringObject() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/string-object") + .WithHeaders("accept", "text/plain") + .Respond(new StringContent("bar")); + + var reviewSvc = CreateMovieReviewService(); + + // act + var stringObject = await reviewSvc.GetStringObject(); + + // assert + Assert.Equal("bar", stringObject.Value); + } + + [Fact] + public async Task TestFormPost() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/form") + .WithFormData("value", "foobar") + .Respond("text/plain", ""); + + var reviewSvc = CreateMovieReviewService(); + + // act + await reviewSvc.FormPost("foobar"); + + // assert + } + + [Fact] + public async Task TestHeaderParam() + { + // arrange + _mockHttp + .When($"{BaseUri}/movies/headers") + .WithHeaders("X-Foo", "bar") + .Respond("text/plain", ""); + + var reviewSvc = CreateRestClientBuilder() + .Header(new AuthenticationHeaderValue("Basic", "SECRET")) + .Header("X-Tick", new TickValue()) + .Build(); + + // act + var responseMessage1 = await reviewSvc.SendFooHeader("bar"); + var requestHeaders1 = responseMessage1.RequestMessage.Headers; + + var responseMessage2 = await reviewSvc.SendFooHeader("bar"); + var requestHeaders2 = responseMessage2.RequestMessage.Headers; + + // assert + Assert.NotNull(requestHeaders1.Authorization); + Assert.Equal("Basic SECRET", requestHeaders1.Authorization.ToString()); + Assert.NotEmpty(requestHeaders1.GetValues("X-Tick")); + + Assert.NotEqual( + requestHeaders1.GetValues("X-Tick").First(), + requestHeaders2.GetValues("X-Tick").First()); + } +} + +internal class TickValue +{ + private long _ticks = 42; + + public override string ToString() + { + return Interlocked.Increment(ref _ticks).ToString(); + } +} \ No newline at end of file