neon-sprawl/server/NeonSprawl.Server.Tests/Game/Quests/QuestStateOperationsTests.cs

379 lines
13 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.Quests;
using NeonSprawl.Server.Tests;
using Xunit;
namespace NeonSprawl.Server.Tests.Game.Quests;
public sealed class QuestStateOperationsTests
{
private const string PlayerId = "dev-local-1";
private const string UnknownPlayerId = "unknown-player-xyz";
private const string GatherQuestId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId;
private const string RefineQuestId = "prototype_quest_refine_intro";
private const string ChainQuestId = PrototypeE7M1QuestCatalogRules.ChainQuestId;
private const string ChainObjectiveId = PrototypeE7M1QuestCatalogRules.ChainFirstObjectiveId;
[Fact]
public async Task TryAccept_ShouldActivateGatherIntro_WhenNotStarted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
// Act
var result = QuestStateOperations.TryAccept(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.True(result.Success);
Assert.Null(result.ReasonCode);
Assert.NotNull(result.Snapshot);
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
Assert.Equal(0, result.Snapshot.CurrentStepIndex);
}
[Fact]
public async Task TryAccept_ShouldDenyPrerequisiteIncomplete_WhenRefineIntroBeforeGatherComplete()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
// Act
var result = QuestStateOperations.TryAccept(
PlayerId,
RefineQuestId,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.PrerequisiteIncomplete, result.ReasonCode);
Assert.Null(result.Snapshot);
}
[Fact]
public async Task TryAccept_ShouldSucceedRefineIntro_WhenGatherIntroCompleted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
Assert.True(QuestStateOperations.TryAccept(PlayerId, GatherQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
Assert.True(QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider).Success);
// Act
var result = QuestStateOperations.TryAccept(
PlayerId,
RefineQuestId,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.True(result.Success);
Assert.Null(result.ReasonCode);
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
}
[Fact]
public async Task TryAccept_ShouldDenyUnknownQuest_WhenQuestIdNotInCatalog()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
// Act
var result = QuestStateOperations.TryAccept(
PlayerId,
"prototype_quest_missing",
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.UnknownQuest, result.ReasonCode);
}
[Fact]
public async Task TryAccept_ShouldDenyAlreadyActive_WhenQuestAlreadyAccepted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
Assert.True(QuestStateOperations.TryAccept(PlayerId, GatherQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
// Act
var result = QuestStateOperations.TryAccept(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.AlreadyActive, result.ReasonCode);
Assert.NotNull(result.Snapshot);
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
}
[Fact]
public async Task TryAccept_ShouldDenyAlreadyCompleted_WhenQuestAlreadyCompleted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
Assert.True(QuestStateOperations.TryAccept(PlayerId, GatherQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
Assert.True(QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider).Success);
// Act
var result = QuestStateOperations.TryAccept(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.AlreadyCompleted, result.ReasonCode);
Assert.Equal(QuestProgressStatus.Completed, result.Snapshot!.Status);
}
[Fact]
public async Task TryAccept_ShouldDenyUnknownPlayer_WhenPlayerNotInStore()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
// Act
var result = QuestStateOperations.TryAccept(
UnknownPlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.UnknownPlayer, result.ReasonCode);
}
[Fact]
public async Task TryAdvanceStep_ShouldAdvanceChainQuestAndClearCounters()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
AcceptAndComplete(deps, GatherQuestId, timeProvider);
AcceptAndComplete(deps, RefineQuestId, timeProvider);
AcceptAndComplete(deps, "prototype_quest_combat_intro", timeProvider);
Assert.True(QuestStateOperations.TryAccept(PlayerId, ChainQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
Assert.True(deps.ProgressStore.TryUpdateObjectiveCounter(PlayerId, ChainQuestId, ChainObjectiveId, 5, out _));
// Act
var result = QuestStateOperations.TryAdvanceStep(
PlayerId,
ChainQuestId,
1,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.True(result.Success);
Assert.Null(result.ReasonCode);
Assert.Equal(1, result.Snapshot!.CurrentStepIndex);
Assert.Empty(result.Snapshot.ObjectiveCounters);
}
[Theory]
[InlineData(0)]
[InlineData(4)]
public async Task TryAdvanceStep_ShouldDenyInvalidStepIndex_WhenIndexNotIncreasingOrPastLastStep(int newStepIndex)
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
AcceptAndComplete(deps, GatherQuestId, timeProvider);
AcceptAndComplete(deps, RefineQuestId, timeProvider);
AcceptAndComplete(deps, "prototype_quest_combat_intro", timeProvider);
Assert.True(QuestStateOperations.TryAccept(PlayerId, ChainQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
// Act
var result = QuestStateOperations.TryAdvanceStep(
PlayerId,
ChainQuestId,
newStepIndex,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.InvalidStepIndex, result.ReasonCode);
}
[Fact]
public async Task TryAdvanceStep_ShouldDenyNotActive_WhenQuestNotAccepted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
// Act
var result = QuestStateOperations.TryAdvanceStep(
PlayerId,
GatherQuestId,
1,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.NotActive, result.ReasonCode);
}
[Fact]
public async Task TryAdvanceStep_ShouldDenyAlreadyCompleted_WhenQuestCompleted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
Assert.True(QuestStateOperations.TryAccept(PlayerId, GatherQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
Assert.True(QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider).Success);
// Act
var result = QuestStateOperations.TryAdvanceStep(
PlayerId,
GatherQuestId,
1,
deps.QuestRegistry,
deps.ProgressStore);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.AlreadyCompleted, result.ReasonCode);
}
[Fact]
public async Task TryMarkComplete_ShouldBeIdempotentAtOperationsLayer()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
Assert.True(QuestStateOperations.TryAccept(PlayerId, GatherQuestId, deps.QuestRegistry, deps.ProgressStore).Success);
// Act
var first = QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider);
var second = QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider);
// Assert
Assert.True(first.Success);
Assert.Null(first.ReasonCode);
Assert.NotNull(first.Snapshot!.CompletedAt);
Assert.True(second.Success);
Assert.Null(second.ReasonCode);
Assert.Equal(first.Snapshot.CompletedAt, second.Snapshot!.CompletedAt);
Assert.Equal(QuestProgressStatus.Completed, second.Snapshot.Status);
}
[Fact]
public async Task TryMarkComplete_ShouldDenyNotActive_WhenQuestNeverAccepted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var timeProvider = TimeProvider.System;
// Act
var result = QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider);
// Assert
Assert.False(result.Success);
Assert.Equal(QuestStateReasonCodes.NotActive, result.ReasonCode);
}
[Fact]
public async Task Host_ShouldResolveRegistryAndStore_ForQuestStateOperations()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
using var scope = factory.Services.CreateScope();
var questRegistry = scope.ServiceProvider.GetRequiredService<IQuestDefinitionRegistry>();
var progressStore = scope.ServiceProvider.GetRequiredService<IPlayerQuestStateStore>();
// Act
var accept = QuestStateOperations.TryAccept(PlayerId, GatherQuestId, questRegistry, progressStore);
var complete = QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
questRegistry,
progressStore,
TimeProvider.System);
// Assert
Assert.True(accept.Success);
Assert.True(complete.Success);
Assert.Equal(QuestProgressStatus.Completed, complete.Snapshot!.Status);
}
private static void AcceptAndComplete(
(IQuestDefinitionRegistry QuestRegistry, IPlayerQuestStateStore ProgressStore) deps,
string questId,
TimeProvider timeProvider)
{
Assert.True(QuestStateOperations.TryAccept(PlayerId, questId, deps.QuestRegistry, deps.ProgressStore).Success);
Assert.True(QuestStateOperations.TryMarkComplete(
PlayerId,
questId,
deps.QuestRegistry,
deps.ProgressStore,
timeProvider).Success);
}
private static (IQuestDefinitionRegistry QuestRegistry, IPlayerQuestStateStore ProgressStore) ResolveDependencies(
InMemoryWebApplicationFactory factory)
{
var scope = factory.Services.CreateScope();
return (
scope.ServiceProvider.GetRequiredService<IQuestDefinitionRegistry>(),
scope.ServiceProvider.GetRequiredService<IPlayerQuestStateStore>());
}
}