error tests expansion
.NET Test / .NET tests (push) Failing after 2m5s

This commit is contained in:
2026-07-12 18:06:53 +04:00
parent 2dbfd781ec
commit e8ae93799e
23 changed files with 1190 additions and 156 deletions
+8 -8
View File
@@ -10,8 +10,8 @@ public class Satisfy
.LessThan(100)
.Result();
Assert.True(result.IsSuccess);
Assert.Equal(69, result.Value);
result.IsSuccess.ShouldBeTrue();
result.Value.ShouldBe(69);
}
[Fact]
public void WhenRequirementWasNotSatisfied_ShouldBeFailureWithDefaultError()
@@ -22,8 +22,8 @@ public class Satisfy
.GreaterThan(100)
.Result();
Assert.True(result.IsFailure);
Assert.Equal(error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(error);
}
[Fact]
@@ -37,8 +37,8 @@ public class Satisfy
.EqualTo("69")
.Result();
Assert.True(result.IsSuccess);
Assert.Equal("69", result.Value);
result.IsSuccess.ShouldBeTrue();
result.Value.ShouldBe("69");
}
[Fact]
@@ -53,7 +53,7 @@ public class Satisfy
.EqualTo("69")
.Result();
Assert.True(result.IsFailure);
Assert.Equal(error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(error);
}
}
+572
View File
@@ -0,0 +1,572 @@
namespace Raliway.Tests.Errors;
public class Construction
{
public class New_Factory
{
[Fact]
public void With_string_creates_ExpectedError_with_default_type()
{
// Given
// When
var error = Error.New("something went wrong");
// Then
error.ShouldBeOfType<ExpectedError>();
error.Type.ShouldBe("error");
error.Message.ShouldBe("something went wrong");
error.IsExpected.ShouldBeTrue();
error.IsExceptional.ShouldBeFalse();
error.IsEmpty.ShouldBeFalse();
error.Count.ShouldBe(1);
}
[Fact]
public void With_type_and_message_creates_ExpectedError_with_custom_type()
{
// Given
// When
var error = Error.New("validation_error", "Name is required");
// Then
error.ShouldBeOfType<ExpectedError>();
error.Type.ShouldBe("validation_error");
error.Message.ShouldBe("Name is required");
}
[Fact]
public void With_Exception_creates_ExceptionalError()
{
// Given
var exception = new InvalidOperationException("bad state");
// When
var error = Error.New(exception);
// Then
error.ShouldBeOfType<ExceptionalError>();
error.Type.ShouldBe("System.InvalidOperationException");
error.Message.ShouldBe("bad state");
error.IsExpected.ShouldBeFalse();
error.IsExceptional.ShouldBeTrue();
error.IsEmpty.ShouldBeFalse();
error.Count.ShouldBe(1);
}
[Fact]
public void With_message_and_Exception_overrides_message_preserves_exception()
{
// Given
var exception = new InvalidOperationException("internal detail");
// When
var error = Error.New("sanitized message", exception);
// Then
error.ShouldBeOfType<ExceptionalError>();
error.Type.ShouldBe("System.InvalidOperationException");
error.Message.ShouldBe("sanitized message");
error.IsExceptional.ShouldBeTrue();
var ex = error.ToException();
ex.ShouldBe(exception);
}
[Fact]
public void With_AggregateException_many_inners_creates_ManyErrors()
{
// Given
var inner1 = new InvalidOperationException("first");
var inner2 = new ArgumentException("second");
var aggregate = new AggregateException(inner1, inner2);
// When
var error = Error.New(aggregate);
// Then
error.ShouldBeOfType<ManyErrors>();
var many = (ManyErrors)error;
many.Count.ShouldBe(2);
many[0].Type.ShouldBe("System.InvalidOperationException");
many[0].Message.ShouldBe("first");
many[1].Type.ShouldBe("System.ArgumentException");
many[1].Message.ShouldBe("second");
}
[Fact]
public void With_AggregateException_single_inner_creates_ExceptionalError()
{
// Given
var inner = new InvalidOperationException("only one");
var aggregate = new AggregateException(inner);
// When
var error = Error.New(aggregate);
// Then
error.ShouldBeOfType<ExceptionalError>();
error.Type.ShouldBe("System.InvalidOperationException");
error.Message.ShouldBe("only one");
}
[Fact]
public void With_AggregateException_zero_inners_creates_ExceptionalError()
{
// Given
var aggregate = new AggregateException();
// When
var error = Error.New(aggregate);
// Then
error.ShouldBeOfType<ExceptionalError>();
error.Type.ShouldBe("System.AggregateException");
}
[Fact]
public void With_nested_AggregateException_recursively_unwraps()
{
// Given
var inner1 = new InvalidOperationException("first");
var inner2 = new ArgumentException("second");
var nestedAggregate = new AggregateException(inner1, inner2);
// Outer must have >1 inners to trigger ManyErrors path; one of them is itself an AggregateException
var outerAggregate = new AggregateException(nestedAggregate, new InvalidOperationException("extra"));
// When
var error = Error.New(outerAggregate);
// Then
error.ShouldBeOfType<ManyErrors>();
var many = (ManyErrors)error;
many.Count.ShouldBe(3); // nested 2-flattened + extra
}
}
public class Many_Factory
{
[Fact]
public void With_both_null_returns_empty_ManyErrors()
{
// Given
// When
var error = Error.Many(null!, null!);
// Then
error.ShouldBeOfType<ManyErrors>();
error.IsEmpty.ShouldBeTrue();
error.Count.ShouldBe(0);
}
[Fact]
public void With_error_and_null_returns_the_error()
{
// Given
var expected = Error.New("test");
// When
var result = Error.Many(expected, null!);
// Then
result.ShouldBe(expected);
result.ShouldBeOfType<ExpectedError>();
}
[Fact]
public void With_null_and_error_returns_the_error()
{
// Given
var expected = Error.New("test");
// When
var result = Error.Many(null!, expected);
// Then
result.ShouldBe(expected);
result.ShouldBeOfType<ExpectedError>();
}
[Fact]
public void Params_with_null_element_should_not_throw()
{
// Given
Error?[] errors = [Error.New("first"), null!, Error.New("second")];
// When
var exception = Record.Exception(() => Error.Many(errors!));
// Then
exception.ShouldBeNull();
}
[Fact]
public void Params_with_null_element_filters_null()
{
// Given
Error?[] errors = [Error.New("first"), null!, Error.New("second")];
// When
var result = Error.Many(errors!);
// Then
result.ShouldBeOfType<ManyErrors>();
var many = (ManyErrors)result;
many.Count.ShouldBe(2);
many[0].Message.ShouldBe("first");
many[1].Message.ShouldBe("second");
}
[Fact]
public void Params_with_empty_array_returns_empty_ManyErrors()
{
// Given
// When
var result = Error.Many(Array.Empty<Error>());
// Then
result.ShouldBeOfType<ManyErrors>();
result.IsEmpty.ShouldBeTrue();
result.Count.ShouldBe(0);
}
[Fact]
public void With_single_error_returns_it_unwrapped()
{
// Given
var single = Error.New("only one");
// When
var result = Error.Many(single);
// Then
result.ShouldBe(single);
result.ShouldBeOfType<ExpectedError>();
}
[Fact]
public void With_ManyErrors_and_error_flattens()
{
// Given
var first = Error.New("first");
var second = Error.New("second");
var many = Error.Many(first, second);
var third = Error.New("third");
// When
var result = Error.Many(many, third);
// Then
result.ShouldBeOfType<ManyErrors>();
var resultMany = (ManyErrors)result;
resultMany.Count.ShouldBe(3);
resultMany[0].Message.ShouldBe("first");
resultMany[1].Message.ShouldBe("second");
resultMany[2].Message.ShouldBe("third");
}
}
public class Operator_Null_Safety
{
[Fact]
public void Error_plus_null_returns_original_error()
{
// Given
var error = Error.New("test");
// When
var result = error + null;
// Then
result.ShouldBe(error);
}
[Fact]
public void Null_plus_error_returns_the_error()
{
// Given
var error = Error.New("test");
// When
var result = null + error;
// Then
result.ShouldBe(error);
}
[Fact]
public void Null_plus_null_is_null()
{
// Given
// When
var result = (Error?)null + (Error?)null;
// Then
result.ShouldBeNull();
}
}
public class Append_Edge_Cases
{
[Fact]
public void Append_null_returns_original_error()
{
// Given
var error = Error.New("test");
// When
var result = error.Append(null);
// Then
result.ShouldBe(error);
}
[Fact]
public void Append_empty_ManyErrors_returns_original_error()
{
// Given
var error = Error.New("test");
var empty = Error.Many(Array.Empty<Error>());
// When
var result = error.Append(empty);
// Then
result.ShouldBe(error);
}
[Fact]
public void Empty_append_error_returns_the_appended_error()
{
// Given
var empty = Error.Many(Array.Empty<Error>());
var error = Error.New("test");
// When
var result = empty.Append(error);
// Then
result.ShouldBe(error);
}
}
public class Property_Correctness
{
[Fact]
public void ExpectedError_IsEmpty_is_false()
{
var error = Error.New("test");
error.IsEmpty.ShouldBeFalse();
}
[Fact]
public void ExceptionalError_IsEmpty_is_false()
{
var error = Error.New(new Exception("test"));
error.IsEmpty.ShouldBeFalse();
}
[Fact]
public void ManyErrors_IsEmpty_when_empty()
{
var error = Error.Many(Array.Empty<Error>());
error.IsEmpty.ShouldBeTrue();
}
[Fact]
public void ManyErrors_IsExpected_when_all_are_expected()
{
// Given
var errors = Error.Many(
Error.New("err1"),
Error.New("err2"));
// When / Then
errors.IsExpected.ShouldBeTrue();
}
[Fact]
public void ManyErrors_IsExpected_when_mixed_is_false()
{
// Given
var errors = Error.Many(
Error.New("err1"),
Error.New(new InvalidOperationException("boom")));
// When / Then
errors.IsExpected.ShouldBeFalse();
}
[Fact]
public void ManyErrors_IsExceptional_when_any_is_exceptional()
{
// Given
var errors = Error.Many(
Error.New("err1"),
Error.New(new InvalidOperationException("boom")));
// When / Then
errors.IsExceptional.ShouldBeTrue();
}
[Fact]
public void ManyErrors_IsExceptional_when_none_is_false()
{
// Given
var errors = Error.Many(
Error.New("err1"),
Error.New("err2"));
// When / Then
errors.IsExceptional.ShouldBeFalse();
}
}
public class ToException
{
[Fact]
public void ExceptionalError_ToException_returns_original_exception()
{
// Given
var original = new InvalidOperationException("boom");
var error = Error.New(original);
// When
var result = error.ToException();
// Then
result.ShouldBe(original);
}
[Fact]
public void ExceptionalError_without_exception_falls_back_to_ErrorException()
{
// Given
// Use deserialization path — creates ExceptionalError without internal Exception
var json = """{"type":"System.Exception","msg":"deserialized error"}""";
var error = JsonSerializer.Deserialize<ExceptionalError>(json)!;
// When
var result = error.ToException();
// Then
result.ShouldNotBeOfType<InvalidOperationException>();
result.ShouldBeOfType<ErrorException>();
var errEx = (ErrorException)result;
errEx.Type.ShouldBe("System.Exception");
errEx.Message.ShouldBe("deserialized error");
}
[Fact]
public void ManyErrors_ToException_returns_AggregateException()
{
// Given
var errors = Error.Many(
Error.New("first"),
Error.New("second"));
// When
var result = errors.ToException();
// Then
result.ShouldBeOfType<AggregateException>();
var aggregate = (AggregateException)result;
aggregate.InnerExceptions.Count.ShouldBe(2);
aggregate.InnerExceptions[0].Message.ShouldBe("first");
aggregate.InnerExceptions[1].Message.ShouldBe("second");
}
}
public class ExtensionData
{
[Fact]
public void Extracted_from_Exception_Data()
{
// Given
var exception = new InvalidOperationException("boom");
exception.Data["key1"] = "value1";
exception.Data["key2"] = 42; // int value → ToString → "42"
// When
var error = Error.New(exception);
// Then
error.ExtensionData["key1"].ShouldBe("value1");
error.ExtensionData["key2"].ShouldBe("42");
}
[Fact]
public void Exception_Data_with_null_value_is_skipped()
{
// Given
var exception = new InvalidOperationException("boom");
exception.Data["key"] = null;
// When
var error = Error.New(exception);
// Then
error.ExtensionData.Count.ShouldBe(0);
}
[Fact]
public void Exception_without_Data_has_empty_ExtensionData()
{
// Given
var exception = new InvalidOperationException("boom");
// When
var error = Error.New(exception);
// Then
error.ExtensionData.ShouldNotBeNull();
error.ExtensionData.IsEmpty.ShouldBeTrue();
}
}
public class Indexer
{
[Fact]
public void Existing_key_returns_value()
{
// Given
var exception = new InvalidOperationException("boom");
exception.Data["mykey"] = "myvalue";
var error = Error.New(exception);
// When
var value = error["mykey"];
// Then
value.ShouldBe("myvalue");
}
[Fact]
public void Missing_key_returns_null()
{
// Given
var error = Error.New("test");
// When
var value = error["nonexistent"];
// Then
value.ShouldBeNull();
}
}
public class Deconstruct
{
[Fact]
public void Produces_Type_and_Message()
{
// Given
var error = Error.New("custom_type", "custom message");
// When
var (type, message) = error;
// Then
type.ShouldBe("custom_type");
message.ShouldBe("custom message");
}
}
}
+225
View File
@@ -0,0 +1,225 @@
namespace Raliway.Tests.Errors;
public class Equality
{
public class Equals_Tests
{
[Fact]
public void Identical_ExpectedErrors_are_equal()
{
var a = Error.New("custom_type", "some message");
var b = Error.New("custom_type", "some message");
a.Equals(b).ShouldBeTrue();
b.Equals(a).ShouldBeTrue();
(a == b).ShouldBeTrue();
}
[Fact]
public void Different_type_ExpectedErrors_are_not_equal()
{
var a = Error.New("type_a", "same message");
var b = Error.New("type_b", "same message");
a.Equals(b).ShouldBeFalse();
(a == b).ShouldBeFalse();
(a != b).ShouldBeTrue();
}
[Fact]
public void Different_message_ExpectedErrors_are_not_equal()
{
var a = Error.New("same_type", "message a");
var b = Error.New("same_type", "message b");
a.Equals(b).ShouldBeFalse();
(a == b).ShouldBeFalse();
}
[Fact]
public void Same_Type_and_Message_but_different_ExtensionData_are_still_equal()
{
var a = Error.New("type", "msg", new Dictionary<string, string> { ["key"] = "a" });
var b = Error.New("type", "msg", new Dictionary<string, string> { ["key"] = "b" });
a.Equals(b).ShouldBeTrue();
(a == b).ShouldBeTrue();
}
[Fact]
public void ExpectedError_and_ExceptionalError_with_same_Type_and_Message_are_equal()
{
Error expected = Error.New("shared_type", "shared message");
Error exceptional = new ExceptionalError("shared_type", "shared message");
expected.Equals(exceptional).ShouldBeTrue();
exceptional.Equals(expected).ShouldBeTrue();
(expected == exceptional).ShouldBeTrue();
}
[Fact]
public void ManyErrors_with_same_elements_same_order_are_equal()
{
var a = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
var b = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
a.Equals(b).ShouldBeTrue();
(a == b).ShouldBeTrue();
}
[Fact]
public void ManyErrors_with_same_elements_different_order_are_not_equal()
{
var a = Error.Many(Error.New("a"), Error.New("b"));
var b = Error.Many(Error.New("b"), Error.New("a"));
a.Equals(b).ShouldBeFalse();
(a == b).ShouldBeFalse();
}
}
public class CompareTo_Tests
{
[Fact]
public void CompareTo_null_returns_minus_one()
{
var error = Error.New("type", "msg");
error.CompareTo(null).ShouldBe(-1);
}
[Fact]
public void CompareTo_same_returns_zero()
{
var a = Error.New("type", "msg");
var b = Error.New("type", "msg");
a.CompareTo(b).ShouldBe(0);
}
[Fact]
public void CompareTo_different_type_compares_by_type()
{
var a = Error.New("aaa", "msg");
var b = Error.New("bbb", "msg");
a.CompareTo(b).ShouldBeLessThan(0);
b.CompareTo(a).ShouldBeGreaterThan(0);
}
[Fact]
public void CompareTo_same_type_different_message_compares_by_message()
{
var a = Error.New("type", "aaa");
var b = Error.New("type", "bbb");
a.CompareTo(b).ShouldBeLessThan(0);
b.CompareTo(a).ShouldBeGreaterThan(0);
}
[Fact]
public void ManyErrors_CompareTo_different_length_compares_by_count()
{
var shorter = Error.Many(Error.New("a"), Error.New("b"));
var longer = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
shorter.CompareTo(longer).ShouldBeLessThan(0);
longer.CompareTo(shorter).ShouldBeGreaterThan(0);
}
[Fact]
public void ManyErrors_CompareTo_same_length_element_by_element()
{
var a = Error.Many(Error.New("a"), Error.New("b"));
var b = Error.Many(Error.New("a"), Error.New("c")); // differs at position 1
a.CompareTo(b).ShouldBeLessThan(0);
b.CompareTo(a).ShouldBeGreaterThan(0);
}
}
public class IsSimilarTo_Tests
{
[Fact]
public void Same_Type_IsSimilarTo_returns_true_ignoring_Message()
{
var a = Error.New("custom_type", "message one");
var b = Error.New("custom_type", "message two");
a.IsSimilarTo(b).ShouldBeTrue();
}
[Fact]
public void Different_Type_IsSimilarTo_returns_false()
{
var a = Error.New("type_a", "same message");
var b = Error.New("type_b", "same message");
a.IsSimilarTo(b).ShouldBeFalse();
}
[Fact]
public void IsSimilarTo_null_returns_false()
{
var error = Error.New("type", "msg");
error.IsSimilarTo(null!).ShouldBeFalse();
}
[Fact]
public void ManyErrors_IsSimilarTo_different_count_returns_false()
{
var a = Error.Many(Error.New("a"), Error.New("b"));
var b = Error.Many(Error.New("a"), Error.New("b"), Error.New("c"));
a.IsSimilarTo(b).ShouldBeFalse();
}
[Fact]
public void ManyErrors_IsSimilarTo_same_types_different_messages_returns_true()
{
var a = Error.Many(Error.New("type", "msg1"));
var b = Error.Many(Error.New("type", "msg2"));
a.IsSimilarTo(b).ShouldBeTrue();
}
[Fact]
public void ManyErrors_IsSimilarTo_different_type_at_same_position_returns_false()
{
var a = Error.Many(Error.New("type_a", "msg"), Error.New("type_b", "msg"));
var b = Error.Many(Error.New("type_a", "msg"), Error.New("type_c", "msg"));
a.IsSimilarTo(b).ShouldBeFalse();
}
}
public class GetHashCode_Tests
{
[Fact]
public void Equal_errors_have_same_hashcode()
{
var a = Error.New("type", "msg");
var b = Error.New("type", "msg");
a.GetHashCode().ShouldBe(b.GetHashCode());
}
[Fact]
public void Equal_ManyErrors_have_same_hashcode()
{
var a = Error.Many(Error.New("a"), Error.New("b"));
var b = Error.Many(Error.New("a"), Error.New("b"));
a.GetHashCode().ShouldBe(b.GetHashCode());
}
[Fact]
public void Empty_ManyErrors_hashcode_is_zero()
{
var empty = Error.Many(Array.Empty<Error>());
empty.GetHashCode().ShouldBe(0);
}
}
}
+20 -33
View File
@@ -1,4 +1,4 @@
namespace Railway.Tests.Errors;
namespace Raliway.Tests.Errors;
public class Serialization
{
@@ -8,18 +8,17 @@ public class Serialization
// Given
Error many_errors = new ManyErrors(
[
Error.New("err1", "msg1", new KeyValuePair<string, string>[]
{
Error.New("err1", "msg1",
[
new("ext", "ext_value"),
}),
]),
Error.New(new Exception("msg2")),
]);
// When
var result = JsonSerializer.Serialize(many_errors);
// Then
Assert.Equal(
expected: "[{\"type\":\"err1\",\"msg\":\"msg1\",\"ext\":\"ext_value\"},{\"type\":\"System.Exception\",\"msg\":\"msg2\"}]",
result);
result.ShouldBe(
"[{\"type\":\"err1\",\"msg\":\"msg1\",\"ext\":\"ext_value\"},{\"type\":\"System.Exception\",\"msg\":\"msg2\"}]");
}
[Fact]
@@ -30,23 +29,17 @@ public class Serialization
// When
var result = JsonSerializer.Deserialize<Error>(json);
// Then
Assert.IsType<ManyErrors>(result);
result.ShouldBeOfType<ManyErrors>();
ManyErrors manyErrors = (ManyErrors)result;
Assert.True(manyErrors.Count == 2);
Assert.Equal(
expected: Error.Many(
manyErrors.Count.ShouldBe(2);
manyErrors.ShouldBe(
Error.Many(
Error.New("err1", "msg1"),
Error.New(new Exception("msg2"))
).ToEnumerable(),
manyErrors
);
Assert.Equal(
expected: "ext_value1",
manyErrors[0]["ext1"]);
Assert.Equal(
expected: "ext_value2",
manyErrors[0]["ext2"]);
).ToEnumerable());
manyErrors[0]["ext1"].ShouldBe("ext_value1");
manyErrors[0]["ext2"].ShouldBe("ext_value2");
}
[Fact]
@@ -57,20 +50,14 @@ public class Serialization
// When
var result = JsonSerializer.Deserialize<ManyErrors>(json);
// Then
Assert.NotNull(result);
Assert.True(result.Count == 2);
Assert.Equal(
expected: Error.Many(
result.ShouldNotBeNull();
result.Count.ShouldBe(2);
result.ShouldBe(
Error.Many(
Error.New("err1", "msg1"),
Error.New(new Exception("msg2"))
).ToEnumerable(),
result
);
Assert.Equal(
expected: "ext_value1",
result[0]["ext1"]);
Assert.Equal(
expected: "ext_value2",
result[0]["ext2"]);
).ToEnumerable());
result[0]["ext1"].ShouldBe("ext_value1");
result[0]["ext2"].ShouldBe("ext_value2");
}
}
@@ -0,0 +1,226 @@
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<Error>(json);
// Then
deserialized.ShouldBeOfType<ExpectedError>();
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<ExpectedError>(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<Error>(json);
// Then
error.ShouldBeOfType<ExpectedError>();
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<Error>(json);
// Then
error.ShouldBeOfType<ExpectedError>();
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<ExceptionalError>(json);
// Then
deserialized.ShouldBeOfType<ExceptionalError>();
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<Error>(json);
// Then — should NOT be an ExceptionalError (known bug)
deserialized.ShouldBeOfType<ExpectedError>();
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<Error>(json);
// Then
deserialized.ShouldBeOfType<ManyErrors>();
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<ManyErrors>(json);
// Then
deserialized.ShouldBeOfType<ManyErrors>();
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<Error>());
// When
var json = JsonSerializer.Serialize(original);
var deserialized = JsonSerializer.Deserialize<Error>(json);
// Then
deserialized.ShouldBeOfType<ManyErrors>();
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<string, string> { ["meta"] = "value" }),
Error.New("err2", "msg2"));
// When
var json = JsonSerializer.Serialize(original);
var deserialized = JsonSerializer.Deserialize<Error>(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<Error>(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<JsonException>(() => JsonSerializer.Deserialize<Error>(json));
}
[Fact]
public void Malformed_JSON_throws_JsonException()
{
// Given
var json = "{not valid json at all";
// When / Then
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<Error>(json));
}
}
}
+5 -2
View File
@@ -10,11 +10,14 @@
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" Version="3.2.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="10.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
+10 -10
View File
@@ -11,8 +11,8 @@ public class Combine
// When
var result = Result.Combine(result1, result2);
// Then
Assert.True(result.IsFailure);
Assert.Equal(result2.Error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(result2.Error);
}
[Fact]
public void TwoResultCombination_WhenThereAreTwoErrors()
@@ -23,8 +23,8 @@ public class Combine
// When
var result = Result.Combine(result1, result2);
// Then
Assert.True(result.IsFailure);
Assert.Equal(result1.Error + result2.Error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(result1.Error + result2.Error);
}
[Fact]
public void TwoResultCombination_WhenThereIsNoError()
@@ -35,7 +35,7 @@ public class Combine
// When
var result = Result.Combine(result1, result2);
// Then
Assert.True(result.IsSuccess);
result.IsSuccess.ShouldBeTrue();
}
[Fact]
public void ThreeResultCombination_WhenThereIsAnError()
@@ -47,8 +47,8 @@ public class Combine
// When
Result<(int, double)> result = Result.Combine(result1, result2, result3);
// Then
Assert.True(result.IsFailure);
Assert.Equal(result3.Error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(result3.Error);
}
[Fact]
public void ThreeResultCombination_WhenThereAreTwoErrors()
@@ -60,8 +60,8 @@ public class Combine
// When
Result<(int?, double)> result = Result.Combine(result1, result2, result3);
// Then
Assert.True(result.IsFailure);
Assert.Equal(result1.Error + result3.Error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(result1.Error + result3.Error);
}
[Fact]
public void ThreeResultCombination_WhenThereIsNoError()
@@ -73,6 +73,6 @@ public class Combine
// When
var result = Result.Combine(result1, result2, result3);
// Then
Assert.True(result.IsSuccess);
result.IsSuccess.ShouldBeTrue();
}
}
+32 -32
View File
@@ -18,12 +18,12 @@ public class GeneralUsage
onSuccess: s => s.ToUpper(),
onFailure: _ =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
}
);
Assert.Equal("TEST_1;SOME", result);
result.ShouldBe("TEST_1;SOME");
}
[Fact]
@@ -39,30 +39,30 @@ public class GeneralUsage
.Append("test")
.Map((i, s) =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
})
.Append("some")
.Bind((s1, s2) =>
.Bind((s1, s2) =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return Result.Success("");
})
.Match(
onSuccess: _ =>
onSuccess: _ =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
},
onFailure: err =>
{
Assert.Equal(error, err);
err.ShouldBe(error);
return "satisfied";
}
);
// Then
Assert.Equal("satisfied", result);
result.ShouldBe("satisfied");
}
[Fact]
@@ -72,7 +72,7 @@ public class GeneralUsage
// When
var result = await Result.Success()
.Append(() => ValueTask.FromResult(Result.Success(1)))
.Append(() => Task.FromResult(Result.Success(1)))
.Append("test")
.Map((i, s) => $"{s}_{i}")
.Append("some")
@@ -81,12 +81,12 @@ public class GeneralUsage
onSuccess: s => s.ToUpper(),
onFailure: _ =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
}
);
Assert.Equal("TEST_1;SOME", result);
result.ShouldBe("TEST_1;SOME");
}
[Fact]
@@ -102,31 +102,31 @@ public class GeneralUsage
.Append("test")
.Map((i, s) =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
})
.Append("some")
.Bind(async (s1, s2) =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
await Task.CompletedTask;
return Result.Success("");
})
.Match(
onSuccess: _ =>
onSuccess: _ =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
},
onFailure: err =>
{
Assert.Equal(error, err);
err.ShouldBe(error);
return "satisfied";
}
);
// Then
Assert.Equal("satisfied", result);
result.ShouldBe("satisfied");
}
[Fact]
@@ -137,17 +137,17 @@ public class GeneralUsage
// When
var result = failed.TryRecover(err =>
{
Assert.IsType<NotImplementedException>(err.ToException());
err.ToException().ShouldBeOfType<NotImplementedException>();
if (err.Type == "System.NotImplementedException")
return "recovered";
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
});
// Then
Assert.True(result.IsSuccess);
Assert.Equal("recovered", result.Value);
result.IsSuccess.ShouldBeTrue();
result.Value.ShouldBe("recovered");
}
[Fact]
@@ -162,12 +162,12 @@ public class GeneralUsage
if (err.Type == "System.NotImplementedException")
return error;
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return "";
});
// Then
Assert.True(result.IsFailure);
Assert.Equal(error, result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(error);
}
[Fact]
@@ -179,8 +179,8 @@ public class GeneralUsage
var result = success
.Extend((i, s) => Result.Success($"{i} + {s}"));
Assert.True(result.IsSuccess);
Assert.Equal((1, "2", "1 + 2"), result.Value);
result.IsSuccess.ShouldBeTrue();
result.Value.ShouldBe((1, "2", "1 + 2"));
}
[Fact]
@@ -192,12 +192,12 @@ public class GeneralUsage
var result = failure
.Extend((i, s) =>
{
Assert.Fail();
true.ShouldBeFalse("should not reach this path");
return Result.Success("");
});
Assert.True(result.IsFailure);
Assert.Equal(Error.New("failure"), result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(Error.New("failure"));
}
[Fact]
@@ -209,7 +209,7 @@ public class GeneralUsage
var result = success
.Extend((i, s) => Result.Failure<string>("failure"));
Assert.True(result.IsFailure);
Assert.Equal(Error.New("failure"), result.Error);
result.IsFailure.ShouldBeTrue();
result.Error.ShouldBe(Error.New("failure"));
}
}
+1
View File
@@ -1,3 +1,4 @@
global using System.Text.Json;
global using Xunit;
global using Shouldly;
global using Just.Railway;