using NeonSprawl.Server.Game.Factions; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.Quests; namespace NeonSprawl.Server.Tests.Game.Factions; public sealed class FactionGateOperationsTests { private const string PlayerId = "dev-local-1"; private const string GridFactionId = PrototypeE7M3QuestFactionRules.GridContractGateFactionId; private const string RustCollectiveFactionId = "prototype_faction_rust_collective"; private const int GridMinStanding = PrototypeE7M3QuestFactionRules.GridContractMinStanding; [Fact] public void TryEvaluate_ShouldPass_WhenRulesEmpty() { // Arrange var standingStore = CreateStandingStore(); // Act var outcome = FactionGateOperations.TryEvaluate(PlayerId, [], standingStore); // Assert Assert.True(outcome.Success); Assert.Null(outcome.ReasonCode); Assert.Null(outcome.FailingFactionId); } [Fact] public void TryEvaluate_ShouldPass_WhenStandingAtMin() { // Arrange var standingStore = CreateStandingStore(); Assert.True(standingStore.TryApplyStandingDelta(PlayerId, GridFactionId, GridMinStanding).Success); var rules = new[] { new FactionGateRuleRow(GridFactionId, GridMinStanding) }; // Act var outcome = FactionGateOperations.TryEvaluate(PlayerId, rules, standingStore); // Assert Assert.True(outcome.Success); Assert.Null(outcome.ReasonCode); } [Fact] public void TryEvaluate_ShouldDeny_WhenStandingBelowMin() { // Arrange var standingStore = CreateStandingStore(); Assert.True(standingStore.TryApplyStandingDelta(PlayerId, GridFactionId, GridMinStanding - 1).Success); var rules = new[] { new FactionGateRuleRow(GridFactionId, GridMinStanding) }; // Act var outcome = FactionGateOperations.TryEvaluate(PlayerId, rules, standingStore); // Assert Assert.False(outcome.Success); Assert.Equal(FactionGateEvaluateReasonCodes.GateBlocked, outcome.ReasonCode); Assert.Equal(GridFactionId, outcome.FailingFactionId); Assert.Equal(GridMinStanding, outcome.RequiredMinStanding); Assert.Equal(GridMinStanding - 1, outcome.CurrentStanding); } [Fact] public void TryEvaluate_ShouldDeny_WhenSecondRuleFailsInAndList() { // Arrange var standingStore = CreateStandingStore(); Assert.True(standingStore.TryApplyStandingDelta(PlayerId, GridFactionId, GridMinStanding).Success); var rules = new[] { new FactionGateRuleRow(GridFactionId, GridMinStanding), new FactionGateRuleRow(RustCollectiveFactionId, 1), }; // Act var outcome = FactionGateOperations.TryEvaluate(PlayerId, rules, standingStore); // Assert Assert.False(outcome.Success); Assert.Equal(FactionGateEvaluateReasonCodes.GateBlocked, outcome.ReasonCode); Assert.Equal(RustCollectiveFactionId, outcome.FailingFactionId); Assert.Equal(1, outcome.RequiredMinStanding); Assert.Equal(0, outcome.CurrentStanding); } [Fact] public void TryEvaluate_ShouldDenyUnknownFaction_WhenStoreDenies() { // Arrange var standingStore = CreateStandingStore(); const string unknownFactionId = "prototype_faction_missing"; var rules = new[] { new FactionGateRuleRow(unknownFactionId, 0) }; // Act var outcome = FactionGateOperations.TryEvaluate(PlayerId, rules, standingStore); // Assert Assert.False(outcome.Success); Assert.Equal(FactionStandingReasonCodes.UnknownFaction, outcome.ReasonCode); Assert.Equal(unknownFactionId, outcome.FailingFactionId); } [Fact] public async Task Host_ShouldResolveStandingStoreForGateEval_WhenStartupSucceeds() { // Arrange — missing standing row reads as 0 (neutral), below grid contract minStanding 15. await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); var standingStore = factory.Services.GetRequiredService(); var rules = new[] { new FactionGateRuleRow(GridFactionId, GridMinStanding) }; // Act var outcome = FactionGateOperations.TryEvaluate(PlayerId, rules, standingStore); // Assert Assert.False(outcome.Success); Assert.Equal(FactionGateEvaluateReasonCodes.GateBlocked, outcome.ReasonCode); } private static InMemoryFactionStandingStore CreateStandingStore() { var registry = CreatePrototypeRegistry(); var options = Options.Create(new GamePositionOptions { DevPlayerId = PlayerId }); return new InMemoryFactionStandingStore(options, registry); } private static FactionDefinitionRegistry CreatePrototypeRegistry() { var byId = new Dictionary(StringComparer.Ordinal) { [GridFactionId] = PrototypeE7M3FactionCatalogRules.ExpectedFactionFreeze[GridFactionId], [RustCollectiveFactionId] = PrototypeE7M3FactionCatalogRules.ExpectedFactionFreeze[RustCollectiveFactionId], }; var catalog = new FactionDefinitionCatalog("/tmp/catalog", byId, catalogJsonFileCount: 1); return new FactionDefinitionRegistry(catalog); } }