using System;
using System.IO;
using System.Linq;
using Xunit;
using ytLive.Models;
using ytLive.Services.Entitlement;
using ytLive.ViewModels;
namespace ytLive.Tests;
///
/// Steam ownership is cached once into the app-wide binary flag.
///
/// The rule that must never break: a missing paid app ID, Steam client, or native library
/// fails closed.
///
// Shares the static MainViewModel.LayoutPathOverride with the sibling class; without a
// shared collection xUnit runs them in parallel and the two view models fight over one temp DB.
[Collection("MainViewModelLayoutPath")]
public class EntitlementTests
{
[Fact]
public void No_configured_paid_app_id_reads_as_not_owned()
{
var entitlement = SteamEntitlement.NotConfigured();
Assert.False(entitlement.IsAppOwned);
// The diagnostic is deliberately neutral (2026-11-05): the licence state is shown to the
// creator as a status indicator, so the log text carries no "contact support" wording.
Assert.Contains("Steam licensing is not configured", entitlement.ErrorMessage);
}
[Fact]
public void Steam_ownership_checks_the_paid_app_id()
{
const uint paidAppId = 1234567u;
uint queriedAppId = 0;
var entitlement = new SteamEntitlement(
paidAppId,
initializeSteam: () => true,
isAppSubscribed: appId =>
{
queriedAppId = appId;
return true;
},
shutdownSteam: () => { });
Assert.True(entitlement.IsAppOwned);
Assert.Equal(paidAppId, queriedAppId);
Assert.Null(entitlement.ErrorMessage);
}
[Fact]
public void A_steam_entitlement_without_a_client_fails_closed()
{
var entitlement = new SteamEntitlement(1234567u, () => false, _ => true, () => { });
Assert.False(entitlement.IsAppOwned);
Assert.Contains("Open Steam", entitlement.ErrorMessage);
}
[Fact]
public void A_valid_steam_check_for_an_unowned_account_is_not_an_authentication_error()
{
var entitlement = new SteamEntitlement(1234567u, () => true, _ => false, () => { });
Assert.False(entitlement.IsAppOwned);
Assert.Null(entitlement.ErrorMessage);
}
[Fact]
public void The_fake_reports_what_a_test_sets()
{
var fake = new FakeEntitlement(true);
Assert.True(fake.IsAppOwned);
fake.IsAppOwned = false;
Assert.False(fake.IsAppOwned);
}
[Fact]
public void The_view_model_takes_its_answer_from_the_rail()
{
var tempDb = Path.Combine(Path.GetTempPath(), $"llamacasty-entitlement-{Guid.NewGuid():N}.db");
MainViewModel.LayoutPathOverride = tempDb;
try
{
var vm = new MainViewModel();
Assert.True(vm.IsAppOwned);
Assert.True(vm.HasProOutput);
vm.EntitlementOverride = new FakeEntitlement(false);
Assert.False(vm.IsAppOwned);
// Pro output is what the frame-rate boundary answers, and while the pre-release switch is
// off that answer is yes regardless of ownership. The rail still moves IsAppOwned itself,
// and the paid-limit row is absent because nothing is being limited.
Assert.Equal(!OutputProfile.EntitlementEnforced, vm.HasProOutput);
Assert.Equal(OutputProfile.EntitlementEnforced,
vm.AttentionAlerts.Any(alert => alert.Id == "steam-licence"));
vm.EntitlementOverride = new FakeEntitlement(true);
Assert.True(vm.IsAppOwned);
Assert.True(vm.HasProOutput);
}
finally
{
MainViewModel.LayoutPathOverride = null;
try { File.Delete(tempDb); } catch (IOException) { }
}
}
///
/// The pre-release switch is the only thing that lets an unowned build reach 60 fps, which
/// is what makes Release-build performance testing possible before the paid edition ships.
///
/// Written against both states of so
/// flipping it at launch does not leave a red test behind: with the switch off the unowned view
/// model keeps 1080p60; with it on the tier setter refuses it and the free tier stands.
///
[Fact]
public void An_unowned_build_reaches_60_fps_only_while_the_gate_is_unenforced()
{
var tempDb = Path.Combine(Path.GetTempPath(), $"llamacasty-entitlement-{Guid.NewGuid():N}.db");
MainViewModel.LayoutPathOverride = tempDb;
try
{
var vm = new MainViewModel { EntitlementOverride = new FakeEntitlement(false) };
vm.SelectedQuality = vm.QualityOptions.Single(tier => tier.Display == "1080p60");
Assert.False(vm.IsAppOwned);
// The tier the encoder would actually be built from (MainViewModel.Streaming.Operations).
var landed = OutputProfile.Resolve(vm.SelectedQuality, vm.QualityOptions, vm.HasProOutput);
Assert.Equal(OutputProfile.EntitlementEnforced ? 30 : 60, landed.Fps);
}
finally
{
MainViewModel.LayoutPathOverride = null;
try { File.Delete(tempDb); } catch (IOException) { }
}
}
}