namespace Raliway.Tests.Errors; public class Serialization_RoundTrip { public class ExpectedError_Tests { [Fact] public void Round_trip_as_Error_preserves_Type_and_Message() { // Given Error original = Error.New("custom_type", "custom message"); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then deserialized.ShouldBeOfType(); deserialized!.Type.ShouldBe("custom_type"); deserialized.Message.ShouldBe("custom message"); deserialized.IsExpected.ShouldBeTrue(); } [Fact] public void Round_trip_as_ExpectedError_preserves_Type_and_Message() { // Given var original = new ExpectedError("custom_type", "custom message"); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then deserialized!.Type.ShouldBe("custom_type"); deserialized.Message.ShouldBe("custom message"); } [Fact] public void Deserialize_single_object_as_Error_produces_ExpectedError() { // Given var json = """{"type":"foo","msg":"bar"}"""; // When var error = JsonSerializer.Deserialize(json); // Then error.ShouldBeOfType(); error!.Type.ShouldBe("foo"); error.Message.ShouldBe("bar"); } [Fact] public void Unknown_properties_become_ExtensionData() { // Given var json = """{"type":"err","msg":"details","meta_key":"meta_value","flag":"1"}"""; // When var error = JsonSerializer.Deserialize(json); // Then error.ShouldBeOfType(); error!.ExtensionData["meta_key"].ShouldBe("meta_value"); error.ExtensionData["flag"].ShouldBe("1"); } } public class ExceptionalError_Tests { [Fact] public void Round_trip_as_ExceptionalError_preserves_Type_and_Message() { // Given Error original = Error.New(new InvalidOperationException("boom")); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then deserialized.ShouldBeOfType(); deserialized!.Type.ShouldBe("System.InvalidOperationException"); deserialized.Message.ShouldBe("boom"); deserialized.IsExceptional.ShouldBeTrue(); } [Fact] public void ExceptionalError_serialized_then_deserialized_as_Error_becomes_ExpectedError() { // ErrorJsonConverter.Read always produces ExpectedError for JSON objects. // Given Error original = Error.New(new InvalidOperationException("boom")); var json = JsonSerializer.Serialize(original); // When — deserialize as the base Error type var deserialized = JsonSerializer.Deserialize(json); // Then — should NOT be an ExceptionalError (known bug) deserialized.ShouldBeOfType(); deserialized!.Type.ShouldBe("System.InvalidOperationException"); deserialized.Message.ShouldBe("boom"); deserialized.IsExceptional.ShouldBeFalse(); deserialized.IsExpected.ShouldBeTrue(); } } public class ManyErrors_Tests { [Fact] public void Round_trip_preserves_all_elements() { // Given Error original = Error.Many( Error.New("err1", "first error"), Error.New("err2", "second error")); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then deserialized.ShouldBeOfType(); var many = (ManyErrors)deserialized!; many.Count.ShouldBe(2); many[0].Type.ShouldBe("err1"); many[0].Message.ShouldBe("first error"); many[1].Type.ShouldBe("err2"); many[1].Message.ShouldBe("second error"); } [Fact] public void Round_trip_as_ManyErrors_preserves_elements() { // Given var original = new ManyErrors([ Error.New("err1", "first"), Error.New("err2", "second") ]); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then deserialized.ShouldBeOfType(); deserialized!.Count.ShouldBe(2); deserialized[0].Type.ShouldBe("err1"); deserialized[1].Type.ShouldBe("err2"); } [Fact] public void Empty_ManyErrors_round_trip() { // Given Error original = Error.Many(Array.Empty()); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then deserialized.ShouldBeOfType(); deserialized!.IsEmpty.ShouldBeTrue(); deserialized.Count.ShouldBe(0); } [Fact] public void ExtensionData_round_trip_within_ManyErrors() { // Given Error original = Error.Many( Error.New("err", "msg", new Dictionary { ["meta"] = "value" }), Error.New("err2", "msg2")); // When var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); // Then var many = (ManyErrors)deserialized!; many[0]["meta"].ShouldBe("value"); } } public class Null_Handling { [Fact] public void Deserialize_null_JSON_returns_null() { // Given var json = "null"; // When var result = JsonSerializer.Deserialize(json); // Then result.ShouldBeNull(); } } public class Error_Handling { [Fact] public void Non_string_property_value_throws_JsonException() { // Given var json = """{"type":"err","msg":"ok","bad":42}"""; // When / Then Should.Throw(() => JsonSerializer.Deserialize(json)); } [Fact] public void Malformed_JSON_throws_JsonException() { // Given var json = "{not valid json at all"; // When / Then Should.Throw(() => JsonSerializer.Deserialize(json)); } } }