Danro-Jump/Assets/Tests/EditMode/Editor/MainMenuControllerPaymentTests.cs
Danil Iarosh d39d160d92 Implement physical prize dispensing, service stats, and QR screen timeout
- Добавлен асинхронный процесс аппаратной выдачи физических призов (хоппер и витрина) в GameplayHudController.
- Реализована блокировка выдачи по 10-секундному таймауту датчика хоппера с гарантированной отправкой команды отключения двигателя '300'.
- Реализован автоматический таймаут неактивности на экране QR-кода с последующим переходом в главное меню.
- Расширен сервис статистики PrizeSessionStateService новыми долгосрочными счетчиками (сыгранные игры, очки, монеты, сбои связи COM, физ. выдачи и ошибки).
- Обновлен построитель сводки статистики в меню настроек для отображения новых счетчиков и списка последних 5 сессий.
- Написаны EditMode тесты для проверки инкремента счетчиков и запуска выдачи призов.
2026-06-23 10:36:13 +03:00

346 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using DanroJump.Hardware.Com;
using DanroJump.Prizes.Qr;
using DanroJump.SceneFlow;
using DanroJump.Settings;
using DanroJump.UI.ArcadeInput;
using DanroJump.UI.MainMenu;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
namespace DanroJump.Tests.EditMode
{
public sealed class MainMenuControllerPaymentTests
{
private readonly List<GameObject> createdObjects = new();
[TearDown]
public void TearDown()
{
foreach (var createdObject in createdObjects)
{
if (createdObject != null)
{
UnityEngine.Object.DestroyImmediate(createdObject);
}
}
createdObjects.Clear();
}
[Test]
public void StartGame_SpendsConfiguredBalanceBeforeLoadingGameplay()
{
var controller = CreateController(
new StubSettings(freePlay: false, startCost: 7),
new StubWallet(balance: 10),
out var sceneFlow,
out var wallet);
InvokeStartGame(controller);
Assert.That(wallet.Credits, Is.EqualTo(3));
Assert.That(wallet.SpendCalls, Is.EqualTo(1));
Assert.That(sceneFlow.LoadedScene, Is.EqualTo("Game"));
}
[Test]
public void StartGame_DoesNotLoadGameplayWhenBalanceIsInsufficient()
{
var controller = CreateController(
new StubSettings(freePlay: false, startCost: 7),
new StubWallet(balance: 4),
out var sceneFlow,
out var wallet);
InvokeStartGame(controller);
Assert.That(wallet.Credits, Is.EqualTo(4));
Assert.That(wallet.SpendCalls, Is.EqualTo(0));
Assert.That(sceneFlow.LoadedScene, Is.Null);
}
[Test]
public void StartGame_FreePlayLoadsGameplayWithoutSpendingBalance()
{
var controller = CreateController(
new StubSettings(freePlay: true, startCost: 7),
new StubWallet(balance: 4),
out var sceneFlow,
out var wallet);
InvokeStartGame(controller);
Assert.That(wallet.Credits, Is.EqualTo(4));
Assert.That(wallet.SpendCalls, Is.EqualTo(0));
Assert.That(sceneFlow.LoadedScene, Is.EqualTo("Game"));
}
[Test]
public void RefreshStaticLabels_UsesConfiguredSupportNumber()
{
var controller = CreateController(
new StubSettings(freePlay: false, startCost: 7, showSupportNumber: true, supportNumber: "+71112223344"),
new StubWallet(balance: 10),
out _,
out _);
var supportLabel = new Label("тех. поддержка +79999999999");
TestObjectFactory.SetPrivateField(controller, "supportPhoneLabel", supportLabel);
InvokeRefreshStaticLabels(controller);
Assert.That(supportLabel.text, Is.EqualTo("тех. поддержка +71112223344"));
Assert.That(supportLabel.style.display.value, Is.EqualTo(DisplayStyle.Flex));
}
[Test]
public void RefreshStaticLabels_HidesSupportNumberWhenDisabled()
{
var controller = CreateController(
new StubSettings(freePlay: false, startCost: 7, showSupportNumber: false, supportNumber: "+71112223344"),
new StubWallet(balance: 10),
out _,
out _);
var supportLabel = new Label("тех. поддержка +79999999999");
TestObjectFactory.SetPrivateField(controller, "supportPhoneLabel", supportLabel);
InvokeRefreshStaticLabels(controller);
Assert.That(supportLabel.style.display.value, Is.EqualTo(DisplayStyle.None));
}
[Test]
public void ResolveTextInputProfile_UsesShortTextForQrPrizeTexts()
{
var method = typeof(MainMenuController).GetMethod(
"ResolveTextInputProfile",
BindingFlags.Static | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null);
var profile = (ArcadeStringInputProfile)method.Invoke(
null,
new object[] { ServiceSettingsKeys.QrPrizeTextGold });
Assert.That(profile.MaxLength, Is.EqualTo(QrPrizeTextSettings.MaxPrizeTextLength));
Assert.That(profile.AllowedSymbols, Does.Not.Contain("{"));
Assert.That(profile.AllowedSymbols, Does.Not.Contain("}"));
}
private MainMenuController CreateController(
IReadOnlyServiceSettings settings,
StubWallet wallet,
out RecordingSceneFlowService sceneFlow,
out StubWallet returnedWallet)
{
var gameObject = new GameObject("MainMenuControllerPaymentTest");
createdObjects.Add(gameObject);
var controller = gameObject.AddComponent<MainMenuController>();
sceneFlow = new RecordingSceneFlowService();
returnedWallet = wallet;
TestObjectFactory.SetPrivateField(controller, "gameplaySceneName", "Game");
TestObjectFactory.SetPrivateField(controller, "serviceSettings", settings);
TestObjectFactory.SetPrivateField(controller, "sceneFlow", sceneFlow);
TestObjectFactory.SetPrivateField(controller, "creditWallet", wallet);
return controller;
}
private static void InvokeStartGame(MainMenuController controller)
{
var method = typeof(MainMenuController).GetMethod(
"StartGame",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null);
method.Invoke(controller, null);
}
private static void InvokeRefreshStaticLabels(MainMenuController controller)
{
var method = typeof(MainMenuController).GetMethod(
"RefreshStaticLabels",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null);
method.Invoke(controller, null);
}
private sealed class StubSettings : IReadOnlyServiceSettings
{
private readonly bool freePlay;
private readonly int startCost;
private readonly bool showSupportNumber;
private readonly string supportNumber;
public StubSettings(
bool freePlay,
int startCost,
bool showSupportNumber = true,
string supportNumber = "+79999999999")
{
this.freePlay = freePlay;
this.startCost = startCost;
this.showSupportNumber = showSupportNumber;
this.supportNumber = supportNumber;
}
public ServiceSettingsDatabase Database => null;
public event Action SettingsLoaded { add { } remove { } }
public event Action SettingsSaved { add { } remove { } }
public event Action<string, ServiceSettingValue> SettingChanged { add { } remove { } }
public bool TryGetDefinition(string key, out ServiceSettingDefinition definition)
{
definition = null;
return false;
}
public IReadOnlyDictionary<string, ServiceSettingValue> Snapshot()
{
return new Dictionary<string, ServiceSettingValue>();
}
public ServiceSettingValue Get(string key)
{
if (string.Equals(key, ServiceSettingsKeys.FreePlay, StringComparison.Ordinal))
{
return ServiceSettingValue.Bool(freePlay);
}
if (string.Equals(key, ServiceSettingsKeys.StartGameCost, StringComparison.Ordinal))
{
return ServiceSettingValue.Int(startCost);
}
if (string.Equals(key, ServiceSettingsKeys.ShowSupportNumber, StringComparison.Ordinal))
{
return ServiceSettingValue.Bool(showSupportNumber);
}
if (string.Equals(key, ServiceSettingsKeys.SupportNumber, StringComparison.Ordinal))
{
return ServiceSettingValue.String(supportNumber);
}
return default;
}
public bool GetBool(string key)
{
if (string.Equals(key, ServiceSettingsKeys.FreePlay, StringComparison.Ordinal))
{
return freePlay;
}
return string.Equals(key, ServiceSettingsKeys.ShowSupportNumber, StringComparison.Ordinal) &&
showSupportNumber;
}
public int GetInt(string key)
{
return string.Equals(key, ServiceSettingsKeys.StartGameCost, StringComparison.Ordinal) ? startCost : 0;
}
public float GetFloat(string key) => 0f;
public string GetString(string key)
{
return string.Equals(key, ServiceSettingsKeys.SupportNumber, StringComparison.Ordinal)
? supportNumber
: string.Empty;
}
public int GetOptionIndex(string key) => 0;
}
private sealed class StubWallet : IComCreditWallet
{
private readonly UnityEvent<int> creditAdded = new();
private readonly UnityEvent<int> creditRemoved = new();
private readonly UnityEvent<int> creditsChanged = new();
public StubWallet(int balance)
{
Credits = balance;
}
public int Credits { get; private set; }
public int SpendCalls { get; private set; }
public UnityEvent<int> CreditAdded => creditAdded;
public UnityEvent<int> CreditRemoved => creditRemoved;
public UnityEvent<int> CreditsChanged => creditsChanged;
public void AddCredit(int amount = 1)
{
Credits += amount;
creditAdded.Invoke(Credits);
creditsChanged.Invoke(Credits);
}
public bool TrySpendCredits(int amount = 1)
{
if (amount <= 0)
{
return true;
}
if (Credits < amount)
{
return false;
}
SpendCalls++;
Credits -= amount;
creditRemoved.Invoke(Credits);
creditsChanged.Invoke(Credits);
return true;
}
public void SetCredits(int value)
{
Credits = Mathf.Max(0, value);
creditsChanged.Invoke(Credits);
}
}
private sealed class RecordingSceneFlowService : ISceneFlowService
{
public string LoadedScene { get; private set; }
public void LoadScene(string sceneName)
{
LoadedScene = sceneName;
}
public void OpenMainMenu()
{
}
public void StartGameplay(string sceneName)
{
LoadedScene = sceneName;
}
public void OpenSettingsScene(string sceneName)
{
LoadedScene = sceneName;
}
public void ReturnToMainMenuAfterGameOver(TimeSpan delay)
{
}
public void CancelGameOverReturn()
{
}
public bool SceneExists(string sceneName)
{
return !string.IsNullOrWhiteSpace(sceneName);
}
}
}
}