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); } }