using Exchanger.Core.Audio; using Exchanger.Core.Configuration; using NUnit.Framework; using System.Text.Json; namespace Exchanger.Tests; [TestFixture] public sealed class AppSettingsTests { [Test] public void DefaultConfiguration_DeserializesAndNormalizes() { string root = ProjectPaths.FindRoot(); string json = File.ReadAllText(Path.Combine(root, "config", "default_settings.json")); AppSettings? settings = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, }); Assert.That(settings, Is.Not.Null); settings!.Normalize(); using (Assert.EnterMultipleScope()) { Assert.That(settings.ConfigurationVersion, Is.EqualTo(AppSettings.CurrentConfigurationVersion)); Assert.That(settings.Window.DebugWindowWidth, Is.EqualTo(720)); Assert.That(settings.Window.DebugWindowHeight, Is.EqualTo(1280)); Assert.That(settings.Pricing.PaymentPresets, Is.Not.Empty); Assert.That(settings.Advertisement.Muted, Is.True); Assert.That(settings.Audio.MasterVolumePercent, Is.EqualTo(100)); Assert.That(settings.Audio.Muted, Is.False); Assert.That(settings.Audio.AdvertisementVolumePercent, Is.EqualTo(100)); Assert.That(settings.Payments.CashEnabled, Is.True); Assert.That(settings.Payments.CardEnabled, Is.True); Assert.That(settings.MenuVisibility.ShowStockStatus, Is.True); Assert.That(settings.MenuVisibility.ShowCustomText, Is.True); Assert.That(settings.MenuVisibility.ShowPromotionBlock, Is.True); Assert.That(settings.MenuVisibility.ShowInteractivePet, Is.True); Assert.That(settings.TokenInventory.Enabled, Is.True); Assert.That(settings.TokenInventory.PurchaseLimitEnabled, Is.True); Assert.That(settings.Branding.ShortText, Is.EqualTo("ПОЛУЧИ СВОИ ЖЕТОНЫ!")); Assert.That(settings.Hardware.BaudRate, Is.EqualTo(115200)); Assert.That(settings.Hardware.FrameTimeoutMilliseconds, Is.EqualTo(100)); Assert.That(settings.Hardware.HandshakeTimeoutMilliseconds, Is.EqualTo(2000)); Assert.That(settings.Hardware.ControllerAesMode, Is.EqualTo("ECB")); Assert.That(settings.Hardware.DispenseMode, Is.EqualTo("Unsupported")); Assert.That(settings.Hardware.ServiceButtonInputIndex, Is.EqualTo(8)); Assert.That(settings.Hardware.ServiceButtonActiveHigh, Is.False); Assert.That(settings.Hardware.StockSensorInputIndex, Is.EqualTo(1)); Assert.That(settings.Hardware.StockLowWhenInputHigh, Is.False); Assert.That(settings.Hardware.Hopper2StockSensorInputIndex, Is.EqualTo(4)); Assert.That(settings.Hardware.Hopper2StockLowWhenInputHigh, Is.False); Assert.That(settings.Hardware.Hopper1DispenseSensorInputIndex, Is.EqualTo(24)); Assert.That(settings.Hardware.Hopper1DispenseSensorActiveHigh, Is.False); Assert.That(settings.Hardware.Hopper2DispenseSensorInputIndex, Is.EqualTo(25)); Assert.That(settings.Hardware.Hopper2DispenseSensorActiveHigh, Is.False); Assert.That(settings.Hardware.InputPollMilliseconds, Is.EqualTo(100)); Assert.That(settings.Hardware.StockPollSeconds, Is.EqualTo(5)); Assert.That(settings.Themes.ExternalDlcEnabled, Is.True); Assert.That(settings.Themes.ExternalDlcPath, Is.EqualTo("user://theme_dlc/active_theme.pck")); Assert.That(settings.Security.ServicePinEnabled, Is.False); Assert.That(settings.Security.ServicePinSaltBase64, Is.Empty); Assert.That(settings.Security.ServicePinHashBase64, Is.Empty); Assert.That(settings.Security.ServicePinIterations, Is.EqualTo(ServiceAccessPolicy.DefaultPbkdf2Iterations)); } } [Test] public void MenuVisibility_LegacyJsonKeyStillControlsCustomText() { MenuVisibilitySettings? visibility = JsonSerializer.Deserialize( "{\"ShowRightCharacter\":false}", new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); Assert.That(visibility, Is.Not.Null); Assert.That(visibility!.ShowCustomText, Is.False); Assert.That(JsonSerializer.Serialize(visibility), Does.Contain("\"ShowRightCharacter\"")); } [Test] public void Normalize_ClampsUnsafeValuesAndCleansAdvertisementPaths() { var settings = new AppSettings { Timeouts = new TimeoutSettings { SessionIdleSeconds = 1, CashPaymentSeconds = 1, CashlessPaymentSeconds = 9999, SuccessSeconds = 1, }, Pricing = new PricingSettings { TokenPriceRubles = 0, MaxCardAmountRubles = 0, CustomAmountStepRubles = 0, }, Hardware = new HardwareSettings { HandshakeTimeoutMilliseconds = 0, ServiceButtonInputIndex = 100, StockSensorInputIndex = -100, Hopper2StockSensorInputIndex = -100, Hopper1DispenseSensorInputIndex = 100, Hopper2DispenseSensorInputIndex = 100, InputPollMilliseconds = 1, MockStockLevel = "NotARealLevel", }, Advertisement = new AdvertisementSettings { PosterPath = " res://poster.png ", VideoPaths = [" res://ad.ogv ", "res://ad.ogv", "C:\\Ads\\clip.mp4", "C:/Ads/clip.mp4", " "], }, Audio = new AudioSettings { MasterVolumePercent = -10, AdvertisementVolumePercent = 250, }, Security = new SecuritySettings { ServicePinEnabled = true, ServicePinSaltBase64 = "broken-salt", ServicePinHashBase64 = "broken-hash", ServicePinIterations = 1, }, }; settings.Normalize(); using (Assert.EnterMultipleScope()) { Assert.That(settings.Timeouts.SessionIdleSeconds, Is.EqualTo(15)); Assert.That(settings.Timeouts.CashPaymentSeconds, Is.EqualTo(15)); Assert.That(settings.Timeouts.CashlessPaymentSeconds, Is.EqualTo(600)); Assert.That(settings.Timeouts.SuccessSeconds, Is.EqualTo(3)); Assert.That(settings.Pricing.TokenPriceRubles, Is.EqualTo(1)); Assert.That(settings.Pricing.CustomAmountStepRubles, Is.EqualTo(1)); Assert.That(settings.Hardware.MockStockLevel, Is.EqualTo("Enough")); Assert.That(settings.Hardware.HandshakeTimeoutMilliseconds, Is.EqualTo(1000)); Assert.That(settings.Hardware.ServiceButtonInputIndex, Is.EqualTo(25)); Assert.That(settings.Hardware.StockSensorInputIndex, Is.EqualTo(-1)); Assert.That(settings.Hardware.Hopper2StockSensorInputIndex, Is.EqualTo(-1)); Assert.That(settings.Hardware.Hopper1DispenseSensorInputIndex, Is.EqualTo(25)); Assert.That(settings.Hardware.Hopper2DispenseSensorInputIndex, Is.EqualTo(25)); Assert.That(settings.Hardware.InputPollMilliseconds, Is.EqualTo(50)); Assert.That(settings.Advertisement.PosterPath, Is.EqualTo("res://poster.png")); Assert.That(settings.Advertisement.VideoPaths, Is.EqualTo(new[] { "res://ad.ogv", "C:/Ads/clip.mp4" })); Assert.That(settings.Audio.MasterVolumePercent, Is.Zero); Assert.That(settings.Audio.AdvertisementVolumePercent, Is.EqualTo(100)); Assert.That(settings.Themes.ExternalDlcPath, Is.EqualTo("user://theme_dlc/active_theme.pck")); Assert.That(settings.Security.ServicePinSaltBase64, Is.Empty); Assert.That(settings.Security.ServicePinHashBase64, Is.Empty); Assert.That(settings.Security.ServicePinIterations, Is.EqualTo(ServiceAccessPolicy.DefaultPbkdf2Iterations)); } } [Test] public void Normalize_ReplacesLegacyAdvertisementFallbackTextWithPromoTerm() { var settings = new AppSettings { Advertisement = new AdvertisementSettings { FallbackText = "РЕКЛАМНЫЙ БЛОК", }, }; settings.Normalize(); Assert.That(settings.Advertisement.FallbackText, Is.EqualTo("ПРОМО-БЛОК")); } [TestCase(100, 0.0f)] [TestCase(50, -30.0f)] [TestCase(3, -58.2f)] [TestCase(0, AudioSettingsApplier.MinimumDecibels)] [TestCase(-25, AudioSettingsApplier.MinimumDecibels)] [TestCase(250, 0.0f)] public void AudioVolume_ConvertsPercentToDecibels(int percent, float expected) { Assert.That(AudioSettingsApplier.PercentToDecibels(percent), Is.EqualTo(expected).Within(0.001f)); } } internal static class ProjectPaths { public static string FindRoot() { DirectoryInfo? directory = new(AppContext.BaseDirectory); while (directory is not null) { if (File.Exists(Path.Combine(directory.FullName, "project.godot"))) { return directory.FullName; } directory = directory.Parent; } throw new DirectoryNotFoundException("Не найден корень проекта Exchanger."); } }