using System; using System.IO; using System.Linq; using System.Xml.Linq; using Xunit; using ytLive.Models; namespace ytLive.Tests; /// /// The output-profile rule (2026-11-01): a build without Pro may emit at most 921,600 pixels /// at 30 fps, and this is the only place that decision is made. /// /// 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_down_to_720p30_with_its_own_bitrate() { var resolved = OutputProfile.Resolve(Tier("1080p60"), Shipped, pro: false); Assert.Equal(1280, resolved.Width); Assert.Equal(720, resolved.Height); Assert.Equal(30, resolved.Fps); // The bitrate follows the profile: a capped user is not offered an 8 Mbps budget. Assert.Equal(6.0, resolved.Bitrate); } [Fact] public void A_free_build_leaves_its_own_tier_untouched() { var requested = Tier("720p30"); 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)); } [Theory] [InlineData("1080p60")] [InlineData("1080p30")] [InlineData("720p60")] [InlineData("720p30")] [InlineData("Vertical 1080p30")] [InlineData("Vertical 1080p60")] public void No_free_selection_can_emit_more_than_the_budget(string name) { var resolved = OutputProfile.Resolve(Tier(name), Shipped, pro: false); Assert.True(resolved.Width * resolved.Height <= OutputProfile.FreeMaxPixels, $"{resolved.Display} resolves to {resolved.Width * resolved.Height} px on a free build"); Assert.True(resolved.Fps <= OutputProfile.FreeMaxFps, $"{resolved.Display} resolves to {resolved.Fps} fps on a free build"); } [Fact] public void An_empty_tier_table_still_yields_a_legal_frame() { var resolved = OutputProfile.Resolve(Tier("1080p60"), Array.Empty(), pro: false); Assert.Equal(1280, resolved.Width); Assert.Equal(720, resolved.Height); Assert.Equal(30, resolved.Fps); } /// /// 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); } } }