167 lines
6.1 KiB
C#
167 lines
6.1 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
using NeonSprawl.Server.Game.Quests;
|
|
using NeonSprawl.Server.Tests;
|
|
using Xunit;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Quests;
|
|
|
|
public sealed class InMemoryPlayerQuestStateStoreTests
|
|
{
|
|
private const string PlayerId = "dev-local-1";
|
|
private const string UnknownPlayerId = "unknown-player-xyz";
|
|
private const string GatherQuestId = "prototype_quest_gather_intro";
|
|
private const string ChainQuestId = PrototypeE7M1QuestCatalogRules.ChainQuestId;
|
|
private const string GatherObjectiveId = "gather_intro_obj_scrap";
|
|
private static readonly DateTimeOffset CompletedAt = new(2026, 6, 3, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
[Fact]
|
|
public void TryGetProgress_ShouldReturnFalse_WhenRowMissing()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
// Act
|
|
var found = store.TryGetProgress(PlayerId, GatherQuestId, out _);
|
|
// Assert
|
|
Assert.False(found);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryActivate_ShouldCreateActiveRowAtStepZero()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
// Act
|
|
var activated = store.TryActivate(PlayerId, GatherQuestId, out var snapshot);
|
|
// Assert
|
|
Assert.True(activated);
|
|
Assert.Equal(QuestProgressStatus.Active, snapshot.Status);
|
|
Assert.Equal(0, snapshot.CurrentStepIndex);
|
|
Assert.Empty(snapshot.ObjectiveCounters);
|
|
Assert.Null(snapshot.CompletedAt);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryActivate_ShouldReturnFalse_WhenAlreadyActive()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
Assert.True(store.TryActivate(PlayerId, GatherQuestId, out _));
|
|
// Act
|
|
var second = store.TryActivate(PlayerId, GatherQuestId, out var snapshot);
|
|
// Assert
|
|
Assert.False(second);
|
|
Assert.Equal(QuestProgressStatus.Active, snapshot.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryUpdateObjectiveCounter_ShouldUpdateMap_WhenActive()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
Assert.True(store.TryActivate(PlayerId, GatherQuestId, out _));
|
|
// Act
|
|
var updated = store.TryUpdateObjectiveCounter(PlayerId, GatherQuestId, GatherObjectiveId, 3, out var snapshot);
|
|
// Assert
|
|
Assert.True(updated);
|
|
Assert.Equal(3, snapshot.ObjectiveCounters[GatherObjectiveId]);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryAdvanceStep_ShouldClearCountersAndBumpIndex()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
Assert.True(store.TryActivate(PlayerId, ChainQuestId, out _));
|
|
Assert.True(store.TryUpdateObjectiveCounter(PlayerId, ChainQuestId, "chain_obj_gather", 5, out _));
|
|
// Act
|
|
var advanced = store.TryAdvanceStep(PlayerId, ChainQuestId, 1, out var snapshot);
|
|
// Assert
|
|
Assert.True(advanced);
|
|
Assert.Equal(1, snapshot.CurrentStepIndex);
|
|
Assert.Empty(snapshot.ObjectiveCounters);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryAdvanceStep_ShouldReturnFalse_WhenIndexNotIncreasing()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
Assert.True(store.TryActivate(PlayerId, GatherQuestId, out _));
|
|
// Act
|
|
var sameIndex = store.TryAdvanceStep(PlayerId, GatherQuestId, 0, out _);
|
|
// Assert
|
|
Assert.False(sameIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryMarkComplete_ShouldBeIdempotent()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
Assert.True(store.TryActivate(PlayerId, GatherQuestId, out _));
|
|
// Act
|
|
var first = store.TryMarkComplete(PlayerId, GatherQuestId, CompletedAt, out var firstSnapshot);
|
|
var second = store.TryMarkComplete(PlayerId, GatherQuestId, CompletedAt.AddHours(1), out var secondSnapshot);
|
|
// Assert
|
|
Assert.True(first);
|
|
Assert.False(second);
|
|
Assert.Equal(QuestProgressStatus.Completed, secondSnapshot.Status);
|
|
Assert.Equal(CompletedAt, secondSnapshot.CompletedAt);
|
|
Assert.Equal(firstSnapshot.CurrentStepIndex, secondSnapshot.CurrentStepIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryMarkComplete_ShouldDenyRegression_OnActivateAdvanceAndCounter()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
Assert.True(store.TryActivate(PlayerId, GatherQuestId, out _));
|
|
Assert.True(store.TryMarkComplete(PlayerId, GatherQuestId, CompletedAt, out _));
|
|
// Act
|
|
var reactivate = store.TryActivate(PlayerId, GatherQuestId, out _);
|
|
var advance = store.TryAdvanceStep(PlayerId, GatherQuestId, 1, out _);
|
|
var counter = store.TryUpdateObjectiveCounter(PlayerId, GatherQuestId, GatherObjectiveId, 1, out _);
|
|
// Assert
|
|
Assert.False(reactivate);
|
|
Assert.False(advance);
|
|
Assert.False(counter);
|
|
Assert.True(store.TryGetProgress(PlayerId, GatherQuestId, out var snapshot));
|
|
Assert.Equal(QuestProgressStatus.Completed, snapshot.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryActivate_ShouldReturnFalse_ForUnknownPlayer()
|
|
{
|
|
// Arrange
|
|
var store = CreateStore();
|
|
// Act
|
|
var activated = store.TryActivate(UnknownPlayerId, GatherQuestId, out _);
|
|
// Assert
|
|
Assert.False(activated);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Host_ShouldResolveStore_AndActivateGatherIntro()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
using var scope = factory.Services.CreateScope();
|
|
var store = scope.ServiceProvider.GetRequiredService<IPlayerQuestStateStore>();
|
|
// Act
|
|
var activated = store.TryActivate(PlayerId, GatherQuestId, out var snapshot);
|
|
var found = store.TryGetProgress(PlayerId, GatherQuestId, out var readBack);
|
|
// Assert
|
|
Assert.True(activated);
|
|
Assert.True(found);
|
|
Assert.Equal(snapshot.QuestId, readBack.QuestId);
|
|
Assert.Equal(QuestProgressStatus.Active, readBack.Status);
|
|
}
|
|
|
|
private static InMemoryPlayerQuestStateStore CreateStore()
|
|
{
|
|
var options = Microsoft.Extensions.Options.Options.Create(new GamePositionOptions { DevPlayerId = PlayerId });
|
|
return new InMemoryPlayerQuestStateStore(options);
|
|
}
|
|
}
|