extensions rework pt1
.NET Test / .NET tests (push) Successful in 2m0s

This commit is contained in:
2026-07-13 22:20:47 +04:00
parent 46487a4794
commit 0e7986996f
30 changed files with 1476 additions and 160 deletions
+185
View File
@@ -0,0 +1,185 @@
namespace Raliway.Tests.Results;
public class Bugs
{
#region BUG-1: Result.Equals asymmetry
[Fact]
public void Result_Success_ShouldNotEqual_Default()
{
// default(Result) has State=Bottom — it's uninitialized.
// Equals should throw because one operand is Bottom.
var success = Result.Success();
Result bottom = default;
// Should throw ResultNotInitializedException, NOT return true
Should.Throw<ResultNotInitializedException>(() => success.Equals(bottom));
}
[Fact]
public void Result_Default_ShouldNotEqual_Success()
{
Result bottom = default;
var success = Result.Success();
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
}
[Fact]
public void Result_Success_EqualsOperator_WithDefault()
{
var success = Result.Success();
Result bottom = default;
Should.Throw<ResultNotInitializedException>(() => success == bottom);
}
[Fact]
public void Result_Error_Equals_WithBottom_ShouldThrow()
{
var error = Result.Failure("test");
Result bottom = default;
Should.Throw<ResultNotInitializedException>(() => error.Equals(bottom));
}
[Fact]
public void Result_Bottom_Equals_WithSuccess_ShouldThrow()
{
Result bottom = default;
var success = Result.Success();
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
}
#endregion
#region BUG-2: Result<T>.Equals asymmetry
[Fact]
public void ResultOfT_Success_ShouldNotEqual_Default()
{
var success = Result.Success(42);
Result<int> bottom = default;
Should.Throw<ResultNotInitializedException>(() => success.Equals(bottom));
}
[Fact]
public void ResultOfT_Error_ShouldNotEqual_Default()
{
var error = Result.Failure<int>("test");
Result<int> bottom = default;
Should.Throw<ResultNotInitializedException>(() => error.Equals(bottom));
}
[Fact]
public void ResultOfT_Default_ShouldNotEqual_Success()
{
Result<int> bottom = default;
var success = Result.Success(42);
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
}
#endregion
#region BUG-3: Task.WhenAny Merge faulted task
[Fact]
public async Task Merge_Tasks_WhenOneFaults_ShouldReturnError_NotThrow()
{
var tasks = new Task<Result>[]
{
Task.FromResult(Result.Success()),
Task.Run(() =>
{
throw new InvalidOperationException("boom");
#pragma warning disable CS0162 // Unreachable code
return Result.Success();
#pragma warning restore CS0162
}),
};
// Should not throw AggregateException escaping the Result error channel
var result = await tasks.Merge();
result.IsFailure.ShouldBeTrue();
}
[Fact]
public async Task Merge_Tasks_WhenOneIsCanceled_ShouldReturnError_NotThrow()
{
var cts = new CancellationTokenSource();
cts.Cancel();
var canceledTask = Task.FromCanceled<Result>(cts.Token);
var tasks = new Task<Result>[]
{
Task.FromResult(Result.Success()),
canceledTask,
};
// Should not throw — canceled tasks have null Exception
var result = await tasks.Merge();
result.IsFailure.ShouldBeTrue();
}
#endregion
#region BUG-4: Task.WhenAny Merge<T> canceled task + O(n²)
[Fact]
public async Task MergeOfT_Tasks_WhenOneIsCanceled_ShouldReturnError_NotThrow()
{
var cts = new CancellationTokenSource();
cts.Cancel();
var canceledTask = Task.FromCanceled<Result<int>>(cts.Token);
var tasks = new Task<Result<int>>[]
{
Task.FromResult(Result.Success(1)),
canceledTask,
};
// Should not crash on canceled task (task.Exception is null → null ref in implicit operator)
var result = await tasks.Merge();
result.IsFailure.ShouldBeTrue();
}
[Fact]
public async Task MergeOfT_Tasks_WhenOneFaults_ShouldReturnError_NotThrow()
{
var tasks = new Task<Result<int>>[]
{
Task.FromResult(Result.Success(1)),
Task.Run(() =>
{
throw new InvalidOperationException("boom");
#pragma warning disable CS0162 // Unreachable code
return Result.Success(2);
#pragma warning restore CS0162
}),
};
var result = await tasks.Merge();
result.IsFailure.ShouldBeTrue();
}
#endregion
#region BUG-5: Error.New null type
[Fact]
public void Error_New_WithNullType_ShouldThrowArgumentNullException()
{
// Null type should throw immediately at construction, not crash later in CompareTo/IsSimilarTo
Should.Throw<ArgumentNullException>(() => Error.New(null!, "some message"));
}
#endregion
}
+2 -1
View File
@@ -37,6 +37,7 @@ public class GeneralUsage
var result = Result.Success()
.Append(() => Result.Failure<int>(error))
.Append("test")
.Tap(onSuccess: (i, s) => true.ShouldBeFalse("should not reach this path"))
.Map((i, s) =>
{
true.ShouldBeFalse("should not reach this path");
@@ -85,7 +86,7 @@ public class GeneralUsage
return "";
}
);
result.ShouldBe("TEST_1;SOME");
}