using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.Contracts; using NeonSprawl.Server.Game.Encounters; using NeonSprawl.Server.Game.Factions; using NeonSprawl.Server.Game.Items; using NeonSprawl.Server.Game.Mastery; using NeonSprawl.Server.Game.Quests; using NeonSprawl.Server.Game.Rewards; using NeonSprawl.Server.Game.Skills; using NeonSprawl.Server.Tests; using Xunit; namespace NeonSprawl.Server.Tests.Game.Contracts; public sealed class ContractCompletionOperationsIntegrationTests { private const string PlayerId = "dev-local-1"; private const string TemplateId = "prototype_contract_clear_combat_pocket"; private const string EncounterId = "prototype_combat_pocket"; private const string MeleeNpc = "prototype_npc_melee"; private const string RangedNpc = "prototype_npc_ranged"; private const string EliteNpc = "prototype_npc_elite"; private const string SeedBucket = "2026-06-22"; [Fact] public async Task TryIssueThenEncounterComplete_ShouldMarkContractCompleted_WhenResolvedFromDi() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); var deps = ResolveDependencies(factory); var issued = ContractGeneratorOperations.TryIssue( PlayerId, TemplateId, SeedBucket, zoneDifficultyBand: null, deps.TemplateRegistry, deps.InstanceStore, deps.StandingStore, deps.TimeProvider); Assert.True(issued.Success); var instanceId = issued.Snapshot!.ContractInstanceId; DefeatAllRequiredTargets(deps); // Act var encounterResult = EncounterCompletionOperations.TryCompleteAndGrant( PlayerId, EncounterId, deps.EncounterRegistry, deps.RewardTableRegistry, deps.ProgressStore, deps.CompletionStore, deps.EventStore, deps.ItemRegistry, deps.InventoryStore, deps.QuestRegistry, deps.QuestProgressStore, deps.SkillRegistry, deps.SkillProgressionStore, deps.LevelCurve, deps.PerkUnlockEngine, deps.PerkStore, deps.DeliveryStore, deps.StandingStore, deps.AuditStore, deps.InstanceStore, deps.OutcomeStore, deps.TemplateRegistry, deps.TimeProvider); // Assert Assert.True(encounterResult.Success); Assert.True(deps.InstanceStore.TryGet(PlayerId, instanceId, out var instance)); Assert.Equal(ContractInstanceStatus.Completed, instance.Status); Assert.True( deps.DeliveryStore.TryGet( PlayerId, RewardDeliverySourceKinds.ContractCompletion, instanceId, out _)); Assert.Single(deps.OutcomeStore.GetOutcomesForInstance(instanceId)); deps.InventoryStore.TryGetSnapshot(PlayerId, out var inventory); Assert.Equal(15, CountBagItem(inventory!, "scrap_metal_bulk")); Assert.Equal(1, CountBagItem(inventory!, "contract_handoff_token")); } private static void DefeatAllRequiredTargets(IntegrationTestDependencies deps) { foreach (var npcId in new[] { MeleeNpc, RangedNpc, EliteNpc }) { _ = EncounterProgressOperations.TryActivateOnFirstEngagement( PlayerId, npcId, deps.EncounterRegistry, deps.ProgressStore, deps.CompletionStore); _ = EncounterProgressOperations.TryMarkTargetDefeated( PlayerId, npcId, deps.EncounterRegistry, deps.ProgressStore, deps.CompletionStore); } } private static int CountBagItem(PlayerInventorySnapshot snapshot, string itemId) { var total = 0; foreach (var slot in snapshot.BagSlots) { if (string.Equals(slot.ItemId, itemId, StringComparison.Ordinal)) { total += slot.Quantity; } } return total; } private static IntegrationTestDependencies ResolveDependencies(InMemoryWebApplicationFactory factory) { using var scope = factory.Services.CreateScope(); var services = scope.ServiceProvider; return new IntegrationTestDependencies( services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService()); } private sealed record IntegrationTestDependencies( IContractTemplateRegistry TemplateRegistry, IContractInstanceStore InstanceStore, IContractOutcomeStore OutcomeStore, IEncounterDefinitionRegistry EncounterRegistry, IRewardTableDefinitionRegistry RewardTableRegistry, IEncounterProgressStore ProgressStore, IEncounterCompletionStore CompletionStore, IEncounterCompleteEventStore EventStore, IItemDefinitionRegistry ItemRegistry, IPlayerInventoryStore InventoryStore, IQuestDefinitionRegistry QuestRegistry, IPlayerQuestStateStore QuestProgressStore, ISkillDefinitionRegistry SkillRegistry, IPlayerSkillProgressionStore SkillProgressionStore, ISkillLevelCurve LevelCurve, PerkUnlockEngine PerkUnlockEngine, IPlayerPerkStateStore PerkStore, IRewardDeliveryStore DeliveryStore, IFactionStandingStore StandingStore, IReputationDeltaStore AuditStore, TimeProvider TimeProvider); }