using MeterVault.App.Analysis;
namespace MeterVault.Integration.Tests.Analysis;
///
/// Only the latest requested load is committed (brief ยง8, finding A13): a delayed first request cannot overwrite the
/// scope or range the user chose after it, and a failure keeps the previous result visible. Pure; no database.
///
public sealed class LoadSequencerTests
{
[Fact]
public void A_new_ticket_cancels_the_previous_one()
{
using var loads = new LoadSequencer();
var first = loads.Next();
Assert.True(loads.IsCurrent(first));
var second = loads.Next();
Assert.True(first.Token.IsCancellationRequested);
Assert.False(loads.IsCurrent(first));
Assert.True(loads.IsCurrent(second));
Assert.Equal(2, second.Generation);
Assert.Equal(2, loads.Generation);
}
[Fact]
public void Disposing_cancels_the_load_in_flight()
{
var loads = new LoadSequencer();
var ticket = loads.Next();
loads.Dispose();
loads.Dispose();
Assert.True(ticket.Token.IsCancellationRequested);
Assert.False(loads.IsCurrent(ticket));
Assert.Throws(() => loads.Next());
}
[Fact]
public async Task A_delayed_first_load_cannot_overwrite_the_later_one()
{
using var loads = new LoadSequencer();
var state = new LoadState();
var slow = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var fast = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
// Type 1 is requested, then type 2 before type 1's answer arrives; the answers arrive in the other order.
var first = loads.RunAsync(state, _ => slow.Task);
var second = loads.RunAsync(state, _ => fast.Task);
Assert.True(state.IsInitialLoad);
fast.SetResult("type 2");
Assert.True(await second);
Assert.Equal("type 2", state.Value);
Assert.False(state.IsLoading);
slow.SetResult("type 1");
Assert.False(await first);
Assert.Equal("type 2", state.Value);
}
[Fact]
public async Task A_superseded_load_that_honours_its_token_just_stops()
{
using var loads = new LoadSequencer();
var state = new LoadState();
var first = loads.RunAsync(state, async token =>
{
await Task.Delay(Timeout.Infinite, token);
return "never";
});
var second = loads.RunAsync(state, _ => Task.FromResult("latest"));
Assert.True(await second);
Assert.False(await first);
Assert.Equal("latest", state.Value);
Assert.Null(state.Error);
}
[Fact]
public async Task A_failure_keeps_the_previous_value_visible_and_is_reported()
{
using var loads = new LoadSequencer();
var state = new LoadState();
await loads.RunAsync(state, _ => Task.FromResult("before"));
var refresh = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var running = loads.RunAsync(state, _ => refresh.Task);
Assert.True(state.IsRefreshing);
Assert.True(state.IsStale);
Assert.False(state.IsInitialLoad);
refresh.SetException(new InvalidOperationException("database gone"));
Assert.True(await running);
Assert.Equal("before", state.Value);
Assert.IsType(state.Error);
Assert.True(state.IsStale);
Assert.False(state.IsLoading);
// Retry succeeds: fresh again.
await loads.RunAsync(state, _ => Task.FromResult("after"));
Assert.Equal("after", state.Value);
Assert.Null(state.Error);
Assert.False(state.IsStale);
}
}