using System; using System.IO; using System.Linq; using System.Xml.Linq; using Xunit; using ytLive.Models; namespace ytLive.Tests; /// /// The output-profile rule: a free build may emit every shipped resolution at 30 fps; /// 60 fps requires Pro, independent of resolution. /// /// The one thing that must never break: a cracked or unlicensed build silently gets a /// *smaller frame*, never a smaller feature set. If a future tier is added above the budget, /// the last test here fails instead of shipping it to everyone. /// public class OutputProfileTests { /// Mirror of MainViewModel.QualityOptions — the shipped tiers. Kept in /// step deliberately: this is the table the budget has to cover. private static readonly QualityOption[] Shipped = { new("1080p60", 60, 8.0, 1920, 1080), new("1080p30", 30, 8.0, 1920, 1080), new("720p60", 60, 6.0, 1280, 720), new("720p30", 30, 6.0, 1280, 720), new("Vertical 1080p30", 30, 8.0, 1080, 1920), new("Vertical 1080p60", 60, 8.0, 1080, 1920), }; private static QualityOption Tier(string name) => Shipped.Single(t => t.Display == name); [Fact] public void A_free_build_resolves_1080p60_to_1080p30() { var resolved = OutputProfile.Resolve(Tier("1080p60"), Shipped, pro: false); Assert.Equal("1080p30", resolved.Display); Assert.Equal(1920, resolved.Width); Assert.Equal(1080, resolved.Height); Assert.Equal(30, resolved.Fps); Assert.Equal(8.0, resolved.Bitrate); } [Fact] public void A_free_build_leaves_its_own_tier_untouched() { var requested = Tier("1080p30"); Assert.Same(requested, OutputProfile.Resolve(requested, Shipped, pro: false)); } [Fact] public void Pro_passes_every_shipped_tier_through() { foreach (var tier in Shipped) Assert.Same(tier, OutputProfile.Resolve(tier, Shipped, pro: true)); } [Fact] public void Free_access_depends_on_frame_rate_not_resolution() { foreach (var tier in Shipped) { var freeAllowed = tier.Fps <= OutputProfile.FreeMaxFps; Assert.Equal(freeAllowed, OutputProfile.IsAllowed(tier, pro: false)); Assert.Equal(!freeAllowed, tier.RequiresPro); } } [Fact] public void An_empty_tier_table_still_yields_a_legal_frame() { var resolved = OutputProfile.Resolve(Tier("1080p60"), Array.Empty(), pro: false); Assert.Equal("1080p30", resolved.Display); Assert.Equal(1920, resolved.Width); Assert.Equal(1080, resolved.Height); Assert.Equal(30, resolved.Fps); Assert.Equal(8.0, resolved.Bitrate); } /// /// The developer override must not exist in a shipped binary. A runtime test cannot prove /// that (the test host is a Debug build), so this asserts the build rule itself: the /// panel's XAML and code-behind are removed whenever the configuration is not Debug. /// [Fact] public void The_developer_panel_is_excluded_from_the_release_compile() { var project = XDocument.Load(SourceAssert.RepoPath("ytLive.csproj")); var guards = project.Descendants() .Where(e => e.Name.LocalName is "Page" or "Compile") .Where(e => (string?)e.Attribute("Remove") is string remove && remove.Contains("DeveloperPanel", StringComparison.OrdinalIgnoreCase)) .ToArray(); Assert.Equal(2, guards.Length); foreach (var guard in guards) { // The condition may sit on the item or on its ItemGroup — either excludes it. var condition = (string?)guard.Attribute("Condition") ?? (string?)guard.Parent?.Attribute("Condition"); Assert.NotNull(condition); Assert.Contains("'$(Configuration)' != 'Debug'", condition, StringComparison.Ordinal); } } }