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