Danro-Jump/Assets/Tests/EditMode/Editor/PrizeSessionStateServiceTests.cs
Danil Iarosh 839a68aef1 Improve VFX, UI overlays, session statistics, COM reliability, audio mixer, and expand architecture guides
Подробный список изменений:
- Перенесены эффекты частиц из C#-кода в готовые префабы-ассеты (Vfx Prefabs) в папке Assets/Resources/Vfx/ с автоматической генерацией материалов в Editor.
- Исправлены ошибки duration на играющих системах частиц и розовые текстуры в GameVfxService.cs.
- Исправлена навигация и фокус кнопок на оверлеях ContinueGameOverlay и PrizeResultOverlay с автоматическим подсвечиванием кнопок лимонно-зеленым цветом при фокусе и проигрыванием звука.
- Добавлена проверка настроек в GameplayHudController.cs для скрытия ContinueGameOverlay, если продолжение игры отключено.
- Расширен сбор статистики: создана структура GameplaySessionRecord, добавлена история до 50 раундов в PrizeSessionStateService и JsonPrizeSessionStateRepository, выведен расчет средних показателей и список 5 последних раундов в сервисной статистике.
- Повышена надежность COM-транспорта: добавлен sleep (80мс) в Dispose для graceful shutdown потоков, реализован цикл переподключения до 10 попыток при обрыве связи.
- Интегрированы настройки громкости с микшером: изменен шаг изменения громкости на 5% (0.05) для удобства, добавлен статус-чек и логи в ApplyServiceVolumes, расширены юнит-тесты на GetFloat параметры.
- Созданы новые архитектурные гайды по аппаратному COM-транспорту и призовой экономике автомата в папке GUIDES/architecture/.
- Оптимизирован и обновлен RAG-индекс проекта.
2026-07-03 15:04:08 +03:00

260 lines
11 KiB
C#

using DanroJump.Prizes;
using NUnit.Framework;
namespace DanroJump.Tests.EditMode
{
public sealed class PrizeSessionStateServiceTests
{
[Test]
public void TryBeginSession_CreatesActivePendingRecord()
{
var service = new PrizeSessionStateService();
var result = new PrizeFlowResult(PrizeRewardKind.QRCode, 120, 4, 55f, "run-1");
var started = service.TryBeginSession(result, out var record);
Assert.That(started, Is.True);
Assert.That(record, Is.Not.Null);
Assert.That(record.SessionId, Is.EqualTo("run-1"));
Assert.That(record.Status, Is.EqualTo(PrizeSessionStatus.Pending));
Assert.That(service.ActiveSession, Is.SameAs(record));
Assert.That(service.History.Count, Is.EqualTo(1));
Assert.That(service.StartedCount, Is.EqualTo(1));
}
[Test]
public void TryBeginSession_BlocksDuplicateSessionId()
{
var service = new PrizeSessionStateService();
var result = new PrizeFlowResult(PrizeRewardKind.PrizeStand, 200, 2, 80f, "same-run");
Assert.That(service.TryBeginSession(result, out _), Is.True);
Assert.That(service.TryBeginSession(result, out var duplicate), Is.False);
Assert.That(duplicate.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
Assert.That(service.StartedCount, Is.EqualTo(1));
Assert.That(service.History.Count, Is.EqualTo(2));
}
[Test]
public void TryBeginSession_BlocksNewPrizeWhileAnotherSessionIsActive()
{
var service = new PrizeSessionStateService();
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.QRCode, 120, 0, 40f, "active-run"), out var active);
var started = service.TryBeginSession(
new PrizeFlowResult(PrizeRewardKind.Big, 300, 1, 100f, "next-run"),
out var blocked);
Assert.That(started, Is.False);
Assert.That(service.ActiveSession, Is.SameAs(active));
Assert.That(blocked.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
Assert.That(blocked.Note, Does.Contain("ещё активна"));
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
}
[Test]
public void CompleteActiveSession_ClearsActiveSessionAndUpdatesStatistics()
{
var service = new PrizeSessionStateService();
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.Big, 300, 0, 120f, "run-2"), out _);
service.MarkActiveSessionShown();
var completed = service.CompleteActiveSession("closed");
Assert.That(completed, Is.True);
Assert.That(service.ActiveSession, Is.Null);
Assert.That(service.CompletedCount, Is.EqualTo(1));
Assert.That(service.History[0].Status, Is.EqualTo(PrizeSessionStatus.Completed));
Assert.That(service.History[0].Note, Is.EqualTo("closed"));
}
[Test]
public void TryBeginSession_BlocksSameSessionAfterCompletion()
{
var service = new PrizeSessionStateService();
var result = new PrizeFlowResult(PrizeRewardKind.Hopper, 400, 0, 130f, "completed-run");
service.TryBeginSession(result, out _);
service.CompleteActiveSession("done");
var restarted = service.TryBeginSession(result, out var blocked);
Assert.That(restarted, Is.False);
Assert.That(blocked.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
Assert.That(service.ActiveSession, Is.Null);
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
}
[Test]
public void FailActiveSession_ClearsActiveSessionAndUpdatesStatistics()
{
var service = new PrizeSessionStateService();
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.QRCode, 150, 1, 60f, "run-3"), out _);
var failed = service.FailActiveSession("no phone");
Assert.That(failed, Is.True);
Assert.That(service.ActiveSession, Is.Null);
Assert.That(service.FailedCount, Is.EqualTo(1));
Assert.That(service.History[0].Status, Is.EqualTo(PrizeSessionStatus.Failed));
Assert.That(service.History[0].Note, Is.EqualTo("no phone"));
}
[Test]
public void BuildSummary_IncludesCountersAndLastRecord()
{
var service = new PrizeSessionStateService();
service.TryBeginSession(new PrizeFlowResult(PrizeRewardKind.Small, 90, 0, 20f, "run-4"), out _);
service.MarkActiveSessionShown();
var summary = service.BuildSummary();
Assert.That(summary, Does.Contain("начато 1"));
Assert.That(summary, Does.Contain("Активная выдача"));
Assert.That(summary, Does.Contain("малый приз"));
Assert.That(summary, Does.Contain("показана"));
}
[Test]
public void Constructor_RestoresCompletedSessionAndStillBlocksDuplicateAfterRestart()
{
var repository = new StubPrizeSessionStateRepository(new PrizeSessionStateSnapshot(
new[]
{
new PrizeSessionRecord(
"restored-run",
new PrizeFlowResult(PrizeRewardKind.Hopper, 400, 0, 120f, "restored-run"),
PrizeSessionStatus.Completed,
Utc(10),
Utc(11),
"done")
},
new[] { "restored-run" },
activeSessionId: string.Empty,
startedCount: 1,
completedCount: 1,
failedCount: 0,
duplicateBlockedCount: 0,
fallbackSessionCounter: 0));
var service = new PrizeSessionStateService(repository);
var restarted = service.TryBeginSession(
new PrizeFlowResult(PrizeRewardKind.Hopper, 400, 0, 120f, "restored-run"),
out var blocked);
Assert.That(restarted, Is.False);
Assert.That(blocked, Is.Not.Null);
Assert.That(blocked.Status, Is.EqualTo(PrizeSessionStatus.DuplicateBlocked));
Assert.That(service.DuplicateBlockedCount, Is.EqualTo(1));
Assert.That(service.History.Count, Is.EqualTo(2));
}
[Test]
public void Constructor_RecoversUnfinishedActiveSessionAsFailedAndPersistsUpdatedSnapshot()
{
var repository = new StubPrizeSessionStateRepository(new PrizeSessionStateSnapshot(
new[]
{
new PrizeSessionRecord(
"crash-run",
new PrizeFlowResult(PrizeRewardKind.QRCode, 150, 1, 60f, "crash-run"),
PrizeSessionStatus.Shown,
Utc(20),
Utc(21),
"qr opened")
},
new[] { "crash-run" },
activeSessionId: "crash-run",
startedCount: 1,
completedCount: 0,
failedCount: 0,
duplicateBlockedCount: 0,
fallbackSessionCounter: 0));
var service = new PrizeSessionStateService(repository);
Assert.That(service.ActiveSession, Is.Null);
Assert.That(service.FailedCount, Is.EqualTo(1));
Assert.That(service.History.Count, Is.EqualTo(1));
Assert.That(service.History[0].Status, Is.EqualTo(PrizeSessionStatus.Failed));
Assert.That(service.History[0].Note, Does.Contain("восстановлена после перезапуска"));
Assert.That(repository.SaveCalls, Is.EqualTo(1));
Assert.That(repository.LastSavedSnapshot, Is.Not.Null);
Assert.That(repository.LastSavedSnapshot.ActiveSessionId, Is.Empty);
}
[Test]
public void StateChanges_PersistSnapshotOnBeginAndCompletion()
{
var repository = new StubPrizeSessionStateRepository();
var service = new PrizeSessionStateService(repository);
Assert.That(service.TryBeginSession(
new PrizeFlowResult(PrizeRewardKind.Big, 320, 0, 90f, "persist-run"),
out _), Is.True);
Assert.That(service.CompleteActiveSession("done"), Is.True);
Assert.That(repository.SaveCalls, Is.GreaterThanOrEqualTo(2));
Assert.That(repository.LastSavedSnapshot, Is.Not.Null);
Assert.That(repository.LastSavedSnapshot.CompletedCount, Is.EqualTo(1));
Assert.That(repository.LastSavedSnapshot.ActiveSessionId, Is.Empty);
}
[Test]
public void StatisticsCounters_IncrementCorrectly()
{
var service = new PrizeSessionStateService();
service.RecordGameplaySession("test-session", 100, 5, 25.5f, 1, PrizeRewardKind.None);
Assert.That(service.TotalGamesPlayed, Is.EqualTo(1));
Assert.That(service.TotalScore, Is.EqualTo(100));
Assert.That(service.TotalCoins, Is.EqualTo(5));
service.RecordQrShow();
Assert.That(service.QrShows, Is.EqualTo(1));
service.RecordComError();
Assert.That(service.ComErrors, Is.EqualTo(1));
service.RecordPhysicalDispense(true);
Assert.That(service.PhysicalDispenses, Is.EqualTo(1));
Assert.That(service.PhysicalDispenseFailures, Is.EqualTo(0));
service.RecordPhysicalDispense(false);
Assert.That(service.PhysicalDispenses, Is.EqualTo(1));
Assert.That(service.PhysicalDispenseFailures, Is.EqualTo(1));
}
private static System.DateTime Utc(int minutes)
{
return new System.DateTime(2026, 6, 16, 12, minutes, 0, System.DateTimeKind.Utc);
}
private sealed class StubPrizeSessionStateRepository : IPrizeSessionStateRepository
{
private readonly PrizeSessionStateSnapshot snapshot;
public StubPrizeSessionStateRepository(PrizeSessionStateSnapshot snapshot = null)
{
this.snapshot = snapshot;
}
public int SaveCalls { get; private set; }
public PrizeSessionStateSnapshot LastSavedSnapshot { get; private set; }
public bool TryLoad(out PrizeSessionStateSnapshot loadedSnapshot)
{
loadedSnapshot = snapshot;
return loadedSnapshot != null;
}
public bool Save(PrizeSessionStateSnapshot newSnapshot)
{
SaveCalls++;
LastSavedSnapshot = newSnapshot;
return true;
}
}
}
}