Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Activout.RestClient.Json.Test/SimpleValueObjectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ public async Task TestSimpleValueObjectSerialization()
.Expect(HttpMethod.Post, BaseUri)
.WithContent(JsonSerializer.Serialize(new
{
fooBar = "foobar",
nullableInteger = 42
FooBar = "foobar",
NullableInteger = 42
}))
.Respond(HttpStatusCode.OK);

Expand Down
9 changes: 8 additions & 1 deletion Activout.RestClient.Json/SystemTextJsonDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,16 @@ public static class SystemTextJsonDefaults
/// Gets the default JSON serializer options.
/// </summary>
public static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = null,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
PropertyNameCaseInsensitive = true
};

public static readonly JsonSerializerOptions CamelCaseSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
PropertyNameCaseInsensitive = true
};

Expand Down
15 changes: 6 additions & 9 deletions Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,29 +191,26 @@ public async Task TestQueryParamAsync()
public async Task TestPostJsonAsync()
{
// arrange
var movieId = "FOOBAR";
_mockHttp
.When(HttpMethod.Post, $"{BaseUri}/movies/{movieId}/reviews")
.When(HttpMethod.Post, $"{BaseUri}/movies/FOOBAR/reviews")
.WithHeaders("Content-Type", "application/json; charset=utf-8")
.Respond(request =>
{
var content = request.Content.ReadAsStringAsync().Result;
var content = request.Content!.ReadAsStringAsync().Result;
content = content.Replace("\"ReviewId\":null", "\"ReviewId\":\"*REVIEW_ID*\"");
return new StringContent(content, Encoding.UTF8, "application/json");
});

var reviewSvc = CreateMovieReviewService();

// act
var text = "This was a delightful comedy, but not terribly realistic.";
var stars = 3;
var review = new Review(stars, text);
var result = await reviewSvc.SubmitReview(movieId, review);
var review = new Review(3, "This was a delightful comedy, but not terribly realistic.");
var result = await reviewSvc.SubmitReview("FOOBAR", review);

// assert
Assert.Equal("*REVIEW_ID*", result.ReviewId);
Assert.Equal(stars, result.Stars);
Assert.Equal(text, result.Text);
Assert.Equal(3, result.Stars);
Assert.Equal("This was a delightful comedy, but not terribly realistic.", result.Text);
}

[Fact]
Expand Down
12 changes: 12 additions & 0 deletions Activout.RestClient.Newtonsoft.Json/NewtonsoftJsonDefaults.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Activout.RestClient.Newtonsoft.Json;

Expand All @@ -13,8 +14,19 @@ public static class NewtonsoftJsonDefaults

public static readonly JsonConverter[] DefaultJsonConverters = [new SimpleValueObjectConverter()];

public static readonly DefaultContractResolver CamelCasePropertyNamesContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
};

public static readonly JsonSerializerSettings DefaultJsonSerializerSettings = new()
{
Converters = DefaultJsonConverters.ToList()
};

public static readonly JsonSerializerSettings CamelCaseSerializerSettings = new()
{
Converters = DefaultJsonConverters.ToList(),
ContractResolver = CamelCasePropertyNamesContractResolver,
};
}
112 changes: 75 additions & 37 deletions Activout.RestClient.Test.Json/RestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using RichardSzalay.MockHttp;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Xunit;
using Xunit.Abstractions;

Expand All @@ -18,6 +20,12 @@ public enum JsonImplementation
NewtonsoftJson
}

public enum JsonNullValueHandling
{
Include,
Ignore
}

public class RestClientTests(ITestOutputHelper outputHelper)
{
private const string BaseUri = "https://example.com/movieReviewService";
Expand All @@ -28,21 +36,55 @@ public class RestClientTests(ITestOutputHelper outputHelper)
private readonly MockHttpMessageHandler _mockHttp = new();
private readonly ILoggerFactory _loggerFactory = LoggerFactoryHelpers.CreateLoggerFactory(outputHelper);

private IRestClientBuilder CreateRestClientBuilder(JsonImplementation jsonImplementation)
private static readonly JsonSerializerOptions SystemTextJsonIncludeNulls =
new(SystemTextJsonDefaults.SerializerOptions)
{
DefaultIgnoreCondition = JsonIgnoreCondition.Never
};

private static readonly JsonSerializerOptions SystemTextJsonIgnoreNulls =
new(SystemTextJsonDefaults.SerializerOptions)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

private static readonly JsonSerializerSettings NewtonsoftJsonIncludeNulls =
new(NewtonsoftJsonDefaults.DefaultJsonSerializerSettings)
{
NullValueHandling = NullValueHandling.Include
};

private static readonly JsonSerializerSettings NewtonsoftJsonIgnoreNulls =
new(NewtonsoftJsonDefaults.DefaultJsonSerializerSettings)
{
NullValueHandling = NullValueHandling.Ignore
};

private IRestClientBuilder CreateRestClientBuilder(JsonImplementation jsonImplementation,
JsonNullValueHandling nullValueHandling = JsonNullValueHandling.Ignore)
{
var builder = _restClientFactory.CreateBuilder();


var jsonSerializerOptions = nullValueHandling == JsonNullValueHandling.Ignore
? SystemTextJsonIgnoreNulls
: SystemTextJsonIncludeNulls;

var jsonSerializerSettings = nullValueHandling == JsonNullValueHandling.Ignore
? NewtonsoftJsonIgnoreNulls
: NewtonsoftJsonIncludeNulls;

return jsonImplementation switch
{
JsonImplementation.SystemTextJson => builder.WithSystemTextJson(),
JsonImplementation.NewtonsoftJson => builder.WithNewtonsoftJson(),
JsonImplementation.SystemTextJson => builder.WithSystemTextJson(jsonSerializerOptions),
JsonImplementation.NewtonsoftJson => builder.WithNewtonsoftJson(jsonSerializerSettings),
_ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation))
};
}

private IMovieReviewService CreateMovieReviewService(JsonImplementation jsonImplementation)
private IMovieReviewService CreateMovieReviewService(JsonImplementation jsonImplementation,
JsonNullValueHandling nullValueHandling = JsonNullValueHandling.Ignore)
{
return CreateRestClientBuilder(jsonImplementation)
return CreateRestClientBuilder(jsonImplementation, nullValueHandling)
.Accept("application/json")
.ContentType("application/json")
.With(_loggerFactory.CreateLogger<RestClientTests>())
Expand Down Expand Up @@ -112,12 +154,12 @@ private void ExpectGetAllReviewsAndReturnError(string movieId = MovieId)
_mockHttp
.Expect(HttpMethod.Get, $"{BaseUri}/movies/{movieId}/reviews")
.Respond(HttpStatusCode.NotFound, request => new StringContent(JsonConvert.SerializeObject(new
{
Errors = new object[]
{
Errors = new object[]
{
new {Message = "Sorry, that page does not exist", Code = 34}
new { Message = "Sorry, that page does not exist", Code = 34 }
}
}),
}),
Encoding.UTF8,
"application/json"));
}
Expand All @@ -131,12 +173,12 @@ public void TestErrorSync(JsonImplementation jsonImplementation)
_mockHttp
.When(HttpMethod.Get, $"{BaseUri}/movies/{MovieId}/reviews/{ReviewId}")
.Respond(HttpStatusCode.NotFound, request => new StringContent(JsonConvert.SerializeObject(new
{
Errors = new object[]
{
Errors = new object[]
{
new {Message = "Sorry, that page does not exist", Code = 34}
new { Message = "Sorry, that page does not exist", Code = 34 }
}
}),
}),
Encoding.UTF8,
"application/json"));

Expand Down Expand Up @@ -205,45 +247,41 @@ public async Task TestQueryParamAsync(JsonImplementation jsonImplementation)
}

[Theory]
[InlineData(JsonImplementation.SystemTextJson)]
[InlineData(JsonImplementation.NewtonsoftJson)]
public async Task TestPostJsonAsync(JsonImplementation jsonImplementation)
[InlineData(JsonImplementation.SystemTextJson, JsonNullValueHandling.Include)]
[InlineData(JsonImplementation.NewtonsoftJson, JsonNullValueHandling.Include)]
[InlineData(JsonImplementation.SystemTextJson, JsonNullValueHandling.Ignore)]
[InlineData(JsonImplementation.NewtonsoftJson, JsonNullValueHandling.Ignore)]
public async Task TestPostJsonAsync(JsonImplementation jsonImplementation, JsonNullValueHandling nullValueHandling)
{
// arrange
var movieId = "FOOBAR";

// The replacement logic needs to handle different JSON formats
_mockHttp
.When(HttpMethod.Post, $"{BaseUri}/movies/{movieId}/reviews")
.When(HttpMethod.Post, $"{BaseUri}/movies/FOOBAR/reviews")
.WithHeaders("Content-Type", "application/json; charset=utf-8")
.Respond(request =>
{
var content = request.Content!.ReadAsStringAsync().Result;

// Handle different serialization formats
content = jsonImplementation switch
if (nullValueHandling == JsonNullValueHandling.Include)
{
JsonImplementation.SystemTextJson => content.Replace("\"reviewId\":null", "\"reviewId\":\"*REVIEW_ID*\"")
.Replace("}", ",\"reviewId\":\"*REVIEW_ID*\"}"), // Add reviewId if not present
JsonImplementation.NewtonsoftJson => content.Replace("\"ReviewId\":null", "\"ReviewId\":\"*REVIEW_ID*\""),
_ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation))
};

content = content.Replace("\"ReviewId\":null", "\"ReviewId\":\"*REVIEW_ID*\"");
}
else
{
content = content.Replace("}", ",\"ReviewId\":\"*REVIEW_ID*\"}");
}

return new StringContent(content, Encoding.UTF8, "application/json");
});

var reviewSvc = CreateMovieReviewService(jsonImplementation);
var reviewSvc = CreateMovieReviewService(jsonImplementation, nullValueHandling);

// act
var text = "This was a delightful comedy, but not terribly realistic.";
var stars = 3;
var review = new Review(stars, text);
var result = await reviewSvc.SubmitReview(movieId, review);
var review = new Review(3, "This was a delightful comedy, but not terribly realistic.");
var result = await reviewSvc.SubmitReview("FOOBAR", review);

// assert
Assert.Equal("*REVIEW_ID*", result.ReviewId);
Assert.Equal(stars, result.Stars);
Assert.Equal(text, result.Text);
Assert.Equal(3, result.Stars);
Assert.Equal("This was a delightful comedy, but not terribly realistic.", result.Text);
}

[Theory]
Expand Down
35 changes: 3 additions & 32 deletions Activout.RestClient.Test.Json/SimpleValueObjectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,9 @@ public class SimpleValueObjectTest
public async Task TestSimpleValueObjectSerialization(JsonImplementation jsonImplementation)
{
// Arrange
var expectedContent = jsonImplementation switch
{
JsonImplementation.SystemTextJson => System.Text.Json.JsonSerializer.Serialize(new
{
fooBar = "foobar",
nullableInteger = 42
}),
JsonImplementation.NewtonsoftJson => JsonConvert.SerializeObject(new
{
FooBar = "foobar",
NullableInteger = 42
}),
_ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation))
};

_mockHttp
.Expect(HttpMethod.Post, BaseUri)
.WithContent(expectedContent)
.WithContent("{\"FooBar\":\"foobar\",\"NullableInteger\":42}")
.Respond(HttpStatusCode.OK);

var client = CreateClient(jsonImplementation);
Expand All @@ -78,24 +63,10 @@ public async Task TestSimpleValueObjectSerialization(JsonImplementation jsonImpl
public async Task TestSimpleValueObjectDeserialization(JsonImplementation jsonImplementation)
{
// Arrange
var responseContent = jsonImplementation switch
{
JsonImplementation.SystemTextJson => System.Text.Json.JsonSerializer.Serialize(new
{
FooBar = "foobar",
NullableInteger = 42
}),
JsonImplementation.NewtonsoftJson => JsonConvert.SerializeObject(new
{
FooBar = "foobar",
NullableInteger = 42
}),
_ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation))
};

_mockHttp
.Expect(BaseUri)
.Respond(new StringContent(responseContent, Encoding.UTF8, "application/json"));
.Respond(new StringContent("{\"FooBar\":\"foobar\",\"NullableInteger\":42}", Encoding.UTF8,
"application/json"));

var client = CreateClient(jsonImplementation);

Expand Down