Files
Just.Railway/Raliway.Tests/Results/Bugs.cs
T
just 0e7986996f
.NET Test / .NET tests (push) Successful in 2m0s
extensions rework pt1
2026-07-13 22:20:47 +04:00

186 lines
5.0 KiB
C#

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
}