diff --git a/src/StripeTests/Infrastructure/JsonConverters/STJAnyOfConverterTest.cs b/src/StripeTests/Infrastructure/JsonConverters/STJAnyOfConverterTest.cs new file mode 100644 index 0000000000..9f7c6ff5c0 --- /dev/null +++ b/src/StripeTests/Infrastructure/JsonConverters/STJAnyOfConverterTest.cs @@ -0,0 +1,113 @@ +namespace StripeTests +{ + using System.Text.Json; + using System.Text.Json.Serialization; + using Stripe; + using Stripe.Infrastructure; + using Xunit; + + public class STJAnyOfConverterTest : BaseStripeTest + { + [Fact] + public void DeserializeFirstType() + { + var json = "{\n \"any_of\": \"String!\"\n}"; + var obj = JsonSerializer.Deserialize(json); + + Assert.NotNull(obj.AnyOf); + Assert.Equal("String!", obj.AnyOf); + } + + [Fact] + public void DeserializeSecondType() + { + var json = "{\n \"any_of\": {\n \"id\": \"id_123\",\n \"bar\": 42\n }\n}"; + var obj = JsonSerializer.Deserialize(json); + + Assert.NotNull(obj.AnyOf); + Assert.Equal("id_123", ((TestSubObject)obj.AnyOf).Id); + Assert.Equal(42, ((TestSubObject)obj.AnyOf).Bar); + } + + [Fact] + public void DeserializeNull() + { + var json = "{\n \"any_of\": null\n}"; + var obj = JsonSerializer.Deserialize(json); + + Assert.Null(obj.AnyOf); + } + + [Fact] + public void DeserializeUnexpectedType() + { + var json = "{\n \"any_of\": []\n}"; + + var exception = Assert.Throws(() => + JsonSerializer.Deserialize(json)); + + Assert.Contains( + "Cannot deserialize the current JSON object into any of the expected types", + exception.Message); + } + + [Fact] + public void SerializeFirstType() + { + var obj = new TestObject + { + AnyOf = "String!", + }; + + var expected = "{\n \"any_of\": \"String!\"\n}"; + Assert.Equal(expected, obj.ToJson().Replace("\r\n", "\n")); + } + + [Fact] + public void SerializeSecondType() + { + var obj = new TestObject + { + AnyOf = new TestSubObject + { + Id = "id_123", + Bar = 42, + }, + }; + + var expected = + "{\n \"any_of\": {\n \"id\": \"id_123\",\n \"bar\": 42\n }\n}"; + Assert.Equal(expected, obj.ToJson().Replace("\r\n", "\n")); + } + + [Fact] + public void SerializeNull() + { + var obj = new TestObject + { + AnyOf = null, + }; + + var expected = "{\n \"any_of\": null\n}"; + Assert.Equal(expected, obj.ToJson().Replace("\r\n", "\n")); + } + + [JsonConverter(typeof(STJStripeEntityConverter))] + private class TestSubObject : StripeEntity, IHasId + { + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("bar")] + public int Bar { get; set; } + } + + [JsonConverter(typeof(STJStripeEntityConverter))] + private class TestObject : StripeEntity + { + [JsonPropertyName("any_of")] + [JsonConverter(typeof(STJAnyOfConverter))] + internal AnyOf AnyOf { get; set; } + } + } +} diff --git a/src/StripeTests/Infrastructure/JsonConverters/STJExpandableFieldConverterTest.cs b/src/StripeTests/Infrastructure/JsonConverters/STJExpandableFieldConverterTest.cs new file mode 100644 index 0000000000..946fa0d900 --- /dev/null +++ b/src/StripeTests/Infrastructure/JsonConverters/STJExpandableFieldConverterTest.cs @@ -0,0 +1,195 @@ +namespace StripeTests +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using Stripe; + using Stripe.Infrastructure; + using Xunit; + + public class STJExpandableFieldConverterTest : BaseStripeTest + { + [Fact] + public void DeserializeNotExpanded() + { + var json = "{\n \"nested\": \"id_not_expanded\"\n}"; + var obj = JsonSerializer.Deserialize(json); + + Assert.Equal("id_not_expanded", obj.NestedId); + Assert.Null(obj.Nested); + } + + [Fact] + public void DeserializeExpanded() + { + var json = "{\n \"nested\": {\n \"id\": \"id_expanded\",\n \"bar\": 42\n }\n}"; + var obj = JsonSerializer.Deserialize(json); + + Assert.Equal("id_expanded", obj.NestedId); + Assert.NotNull(obj.Nested); + Assert.Equal("id_expanded", obj.Nested.Id); + Assert.Equal(42, obj.Nested.Bar); + } + + [Fact] + public void DeserializeNull() + { + var json = "{\n \"nested\": null\n}"; + var obj = JsonSerializer.Deserialize(json); + + Assert.Null(obj.NestedId); + Assert.Null(obj.Nested); + } + + [Fact] + public void SerializeNotExpanded() + { + var obj = new TestTopLevelObject + { + InternalNested = new ExpandableField + { + Id = "id_not_expanded", + ExpandedObject = null, + }, + }; + + var expected = "{\"nested\":\"id_not_expanded\"}"; + var actual = JsonSerializer.Serialize(obj); + Assert.Equal(expected, actual); + + var indentedExpected = "{\n \"nested\": \"id_not_expanded\"\n}"; + var options = new JsonSerializerOptions { WriteIndented = true }; + var indentedActual = JsonSerializer.Serialize(obj, options); + Assert.Equal(indentedExpected, indentedActual); + } + + [Fact] + public void SerializeExpanded() + { + var nested = new TestNestedObject + { + Id = "id_expanded", + Bar = 42, + }; + var obj = new TestTopLevelObject + { + InternalNested = new ExpandableField + { + Id = nested.Id, + ExpandedObject = nested, + }, + }; + + var expected = + "{\"nested\":{\"id\":\"id_expanded\",\"bar\":42}}"; + var actual = JsonSerializer.Serialize(obj); + Assert.Equal(expected, actual); + + var indentedExpected = + "{\n \"nested\": {\n \"id\": \"id_expanded\",\n \"bar\": 42\n }\n}"; + var options = new JsonSerializerOptions { WriteIndented = true }; + var indentedActual = JsonSerializer.Serialize(obj, options); + Assert.Equal(indentedExpected, indentedActual); + } + + [Fact] + public void SerializeListOfExpanded() + { + var nested = new TestNestedObject + { + Id = "id_expanded", + Bar = 42, + }; + var obj = new TestTopLevelObjectWithList + { + InternalNestedList = new List>() + { + new ExpandableField + { + Id = nested.Id, + ExpandedObject = nested, + }, + }, + }; + + var expected = + "{\"nested_list\":[{\"id\":\"id_expanded\",\"bar\":42}]}"; + var actual = JsonSerializer.Serialize(obj); + Assert.Equal(expected, actual); + + var indentedExpected = + "{\n \"nested_list\": [\n {\n \"id\": \"id_expanded\",\n \"bar\": 42\n }\n ]\n}"; + var options = new JsonSerializerOptions { WriteIndented = true }; + var indentedActual = JsonSerializer.Serialize(obj, options); + Assert.Equal(indentedExpected, indentedActual); + } + + [Fact] + public void SerializeNull() + { + var obj = new TestTopLevelObject + { + InternalNested = new ExpandableField + { + Id = null, + ExpandedObject = null, + }, + }; + + var expected = "{\"nested\":null}"; + var actual = JsonSerializer.Serialize(obj); + Assert.Equal(expected, actual); + + var indentedExpected = "{\n \"nested\": null\n}"; + var options = new JsonSerializerOptions { WriteIndented = true }; + var indentedActual = JsonSerializer.Serialize(obj, options); + Assert.Equal(indentedExpected, indentedActual); + } + + private class TestNestedObject : StripeEntity, IHasId + { + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("bar")] + public int Bar { get; set; } + } + + [JsonConverter(typeof(STJStripeEntityConverter))] + private class TestTopLevelObject : StripeEntity + { + [JsonIgnore] + public string NestedId => this.InternalNested.Id; + + [JsonIgnore] + public TestNestedObject Nested => this.InternalNested.ExpandedObject; + + [JsonPropertyName("nested")] + [JsonConverter(typeof(STJExpandableFieldConverter))] + internal ExpandableField InternalNested { get; set; } + } + + [JsonConverter(typeof(STJStripeEntityConverter))] + private class TestTopLevelObjectWithList : StripeEntity + { + [JsonIgnore] + public List NestedListIds + { + get => this.InternalNestedList?.Select((x) => x.Id).ToList(); + set => this.InternalNestedList = SetExpandableArrayIds(value); + } + + [JsonIgnore] + public List NestedList + { + get => this.InternalNestedList?.Select((x) => x.ExpandedObject).ToList(); + set => this.InternalNestedList = SetExpandableArrayObjects(value); + } + + [JsonPropertyName("nested_list")] + internal List> InternalNestedList { get; set; } + } + } +} diff --git a/src/StripeTests/Wholesome/CorrectJsonConvertersForTypes.cs b/src/StripeTests/Wholesome/CorrectJsonConvertersForTypes.cs index 6bfdde9f64..12eaa382b1 100644 --- a/src/StripeTests/Wholesome/CorrectJsonConvertersForTypes.cs +++ b/src/StripeTests/Wholesome/CorrectJsonConvertersForTypes.cs @@ -103,6 +103,13 @@ public void Check() continue; } + // if we have a Int64StringConverter on a long type, assume its good. + if ((property.PropertyType == typeof(long) || property.PropertyType == typeof(long?)) + && actualConverterName == "Int64StringConverter") + { + continue; + } + results.Add( $"{stripeClass.Name}.{property.Name}, expected = {expectedConverterName}, " + $"actual = {actualConverterName}"); diff --git a/src/StripeTests/Wholesome/PropertiesHaveAllNecessaryJsonAttributes.cs b/src/StripeTests/Wholesome/PropertiesHaveAllNecessaryJsonAttributes.cs index 5047ab7794..6b2132372d 100644 --- a/src/StripeTests/Wholesome/PropertiesHaveAllNecessaryJsonAttributes.cs +++ b/src/StripeTests/Wholesome/PropertiesHaveAllNecessaryJsonAttributes.cs @@ -55,6 +55,24 @@ public void Check() } } + if (property.PropertyType == typeof(long) || property.PropertyType == typeof(long?)) + { + var jsonAttribute = property.GetCustomAttribute(typeof(Newtonsoft.Json.JsonConverterAttribute), false) as Newtonsoft.Json.JsonConverterAttribute; + var stjAttribute = property.GetCustomAttribute(typeof(JsonNumberHandlingAttribute)) as JsonNumberHandlingAttribute; + if (jsonAttribute?.ConverterType == typeof(Int64StringConverter)) + { + var hasCorrectAttributes = + (stjAttribute.Handling & (JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)) != 0; + + if (!hasCorrectAttributes) + { + results.Add($"{type.FullName}.{property.Name}"); + } + + continue; + } + } + foreach (Attribute attribute in property.GetCustomAttributes()) { if (attribute.GetType().Namespace.StartsWith("Newtonsoft", true, null))