using System; using System.Collections.Generic; using System.IO; using System.Linq; using Xunit; using ytLive.Models; using ytLive.Services; namespace ytLive.Tests; /// /// The rate the app suggests next session, from real sessions — and the two rules that keep it from /// becoming a nuisance: five sessions of evidence per tier and encoder, a 10% gap before asking for /// more, and a lower bar for backing off. /// public class RateHistoryTests { private static SessionRateResult Session( double rated, double settled = -1, int steps = 0, string tier = "1080p60", string encoder = "h264_nvenc") => new(tier, encoder, rated, settled < 0 ? rated : settled, steps); [Fact] public void Suggestions_need_five_sessions_of_the_same_tier_and_encoder_and_persist_with_them() { // Four sessions is not evidence. Assert.Null(RateHistory.Suggest( Enumerable.Range(0, 4).Select(_ => Session(15)), "1080p60", "h264_nvenc", 12)); // Five clean sessions holding 15 against a 12 target: worth asking, and the ask is 10% up. var raised = RateHistory.Suggest( Enumerable.Range(0, 5).Select(_ => Session(15)), "1080p60", "h264_nvenc", 12); Assert.NotNull(raised); Assert.True(raised!.Value.Raise); Assert.Equal(15, raised.Value.Mbps); // The same evidence means nothing below the threshold (12 → 12.6 is under 10%). Assert.Null(RateHistory.Suggest( Enumerable.Range(0, 5).Select(_ => Session(13)), "1080p60", "h264_nvenc", 12)); // A different encoder is a different machine: no suggestion. Assert.Null(RateHistory.Suggest( Enumerable.Range(0, 5).Select(_ => Session(15)), "1080p60", "h264_qsv", 12)); // Three step-downs out of five asks to back off to the settled median, faster than it asks to // climb — fixing damage should not wait for a 10% gap. var lowered = RateHistory.Suggest( new[] { Session(12, 9.6, 1), Session(12, 8.0, 2), Session(12), Session(12, 7.2, 1), Session(12), }, "1080p60", "h264_nvenc", 12); Assert.NotNull(lowered); Assert.False(lowered!.Value.Raise); Assert.Equal(9.6, lowered.Value.Mbps); // median of 9.6, 8.0, 12, 7.2, 12 // And the storage form survives a round trip through the settings row, which is where the // evidence actually lives between runs. var db = Path.Combine(Path.GetTempPath(), $"llamacasty-rate-history-{Guid.NewGuid():N}.db"); using (var store = new LayoutStore(db)) { foreach (var entry in Enumerable.Range(0, 5).Select(_ => Session(15))) store.AppendRateHistory(entry); var reloaded = store.LoadRateHistory(); Assert.Equal(5, reloaded.Count); Assert.Equal(Session(15), reloaded[0]); Assert.Equal(15, RateHistory.Suggest(reloaded, "1080p60", "h264_nvenc", 12)!.Value.Mbps); } try { File.Delete(db); } catch (IOException) { } } }