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

591 lines
26 KiB
C#

using System.Net;
using System.Net.Http.Json;
using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.Contracts;
using NeonSprawl.Server.Game.Crafting;
using NeonSprawl.Server.Game.Encounters;
using NeonSprawl.Server.Game.Factions;
using NeonSprawl.Server.Game.Gathering;
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.Quests;
public sealed class QuestProgressApiTests
{
private const string PlayerId = "dev-local-1";
private const string AlphaNodeId = "prototype_resource_node_alpha";
private const string GatherQuestId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId;
private const string RefineQuestId = PrototypeE7M1QuestCatalogRules.RefineIntroQuestId;
private const string CombatQuestId = PrototypeE7M1QuestCatalogRules.CombatIntroQuestId;
private const string ChainQuestId = PrototypeE7M1QuestCatalogRules.ChainQuestId;
private const string GridContractQuestId = PrototypeE7M1QuestCatalogRules.GridContractQuestId;
private const string RustCollectiveFactionId = "prototype_faction_rust_collective";
private const string GatherObjectiveId = PrototypeE7M1QuestCatalogRules.GatherIntroFirstObjectiveId;
private const string ChainGatherObjectiveId = PrototypeE7M1QuestCatalogRules.ChainFirstObjectiveId;
[Fact]
public async Task GetQuestProgress_ShouldReturnNotFound_WhenPlayerUnknown()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/players/missing-player/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnNotFound_WhenPlayerIdIsWhitespaceOnly()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/players/%20/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnNotStartedForAllQuests_WhenNoProgress()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
var registry = factory.Services.GetRequiredService<IQuestDefinitionRegistry>();
string[] expectedQuestOrder = [.. registry.GetDefinitionsInIdOrder().Select(static d => d.Id)];
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
Assert.Equal(QuestProgressListResponse.CurrentSchemaVersion, body!.SchemaVersion);
Assert.Equal(PlayerId, body.PlayerId);
Assert.Equal(expectedQuestOrder.Length, body.Quests.Count);
string[] actualQuestOrder = [.. body.Quests.Select(static row => row.QuestId)];
Assert.Equal(expectedQuestOrder, actualQuestOrder);
foreach (var row in body.Quests)
{
Assert.Equal(QuestProgressApi.StatusNotStarted, row.Status);
Assert.Equal(0, row.CurrentStepIndex);
Assert.Empty(row.ObjectiveCounters);
Assert.Null(row.CompletedAt);
Assert.Null(row.CompletionRewardSummary);
}
}
[Fact]
public async Task GetQuestProgress_ShouldReturnActivePartialCounters_WhenGatherIntroInProgress()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
Assert.True(TryAccept(deps, GatherQuestId).Success);
Assert.True(deps.ProgressStore.TryUpdateObjectiveCounter(
PlayerId,
GatherQuestId,
GatherObjectiveId,
2,
out _));
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
var row = Assert.Single(body!.Quests, static r => r.QuestId == GatherQuestId);
Assert.Equal(QuestProgressApi.StatusActive, row.Status);
Assert.Equal(0, row.CurrentStepIndex);
Assert.Equal(2, row.ObjectiveCounters[GatherObjectiveId]);
Assert.Null(row.CompletedAt);
Assert.Null(row.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnCompletedGatherIntro_WhenObjectivesSatisfied()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
Assert.True(TryAccept(deps, GatherQuestId).Success);
for (var i = 0; i < 3; i++)
{
Assert.True(TryGather(deps, AlphaNodeId).Success);
}
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
var row = Assert.Single(body!.Quests, static r => r.QuestId == GatherQuestId);
Assert.Equal(QuestProgressApi.StatusCompleted, row.Status);
Assert.Equal(0, row.CurrentStepIndex);
Assert.NotNull(row.CompletedAt);
AssertGatherIntroCompletionRewardSummary(row.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnSameCompletionRewardSummary_WhenGatherIntroCompletedTwice()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
Assert.True(TryAccept(deps, GatherQuestId).Success);
for (var i = 0; i < 3; i++)
{
Assert.True(TryGather(deps, AlphaNodeId).Success);
}
// Act
var firstResponse = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
var secondResponse = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
var firstBody = await firstResponse.Content.ReadFromJsonAsync<QuestProgressListResponse>();
var secondBody = await secondResponse.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(firstBody);
Assert.NotNull(secondBody);
var firstRow = Assert.Single(firstBody!.Quests, static r => r.QuestId == GatherQuestId);
var secondRow = Assert.Single(secondBody!.Quests, static r => r.QuestId == GatherQuestId);
AssertGatherIntroCompletionRewardSummary(firstRow.CompletionRewardSummary);
AssertCompletionRewardSummariesEqual(firstRow.CompletionRewardSummary, secondRow.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnSameCompletionRewardSummary_WhenOperatorChainCompletedTwice()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
CompleteOperatorChain(deps);
// Act
var firstResponse = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
var secondResponse = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
var firstBody = await firstResponse.Content.ReadFromJsonAsync<QuestProgressListResponse>();
var secondBody = await secondResponse.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(firstBody);
Assert.NotNull(secondBody);
var firstRow = Assert.Single(firstBody!.Quests, static r => r.QuestId == ChainQuestId);
var secondRow = Assert.Single(secondBody!.Quests, static r => r.QuestId == ChainQuestId);
AssertOperatorChainCompletionRewardSummary(firstRow.CompletionRewardSummary);
AssertCompletionRewardSummariesEqual(firstRow.CompletionRewardSummary, secondRow.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldOmitCompletionRewardSummary_WhenCompletedWithoutDeliveryRecord()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
Assert.True(TryAccept(deps, GatherQuestId).Success);
Assert.True(deps.ProgressStore.TryMarkComplete(PlayerId, GatherQuestId, DateTimeOffset.UtcNow, out _));
Assert.False(deps.DeliveryStore.TryGet(PlayerId, GatherQuestId, out _));
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
var row = Assert.Single(body!.Quests, static r => r.QuestId == GatherQuestId);
Assert.Equal(QuestProgressApi.StatusCompleted, row.Status);
Assert.NotNull(row.CompletedAt);
Assert.Null(row.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnCompletedChainAtTerminalStepIndex_WhenOperatorChainFinished()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
CompleteOperatorChain(deps);
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
var row = Assert.Single(body!.Quests, static r => r.QuestId == ChainQuestId);
Assert.Equal(QuestProgressApi.StatusCompleted, row.Status);
Assert.Equal(3, row.CurrentStepIndex);
Assert.NotNull(row.CompletedAt);
AssertOperatorChainCompletionRewardSummary(row.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldReturnGridContractReputationGrant_WhenAcceptedAfterOperatorChain()
{
// Arrange — operator-chain kit satisfies grid contract inventory_has_item; accept delivers +10 Rust Collective rep.
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
CompleteOperatorChain(deps);
Assert.True(TryAccept(deps, GridContractQuestId).Success);
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
var row = Assert.Single(body!.Quests, static r => r.QuestId == GridContractQuestId);
Assert.Equal(QuestProgressApi.StatusCompleted, row.Status);
AssertGridContractCompletionRewardSummary(row.CompletionRewardSummary);
}
[Fact]
public async Task GetQuestProgress_ShouldCompleteChainTerminal_WhenTokenHeldAndInventoryRefreshRunsOnGet()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var deps = ResolveDependencies(factory);
var client = factory.CreateClient();
AcceptAndMarkComplete(deps, GatherQuestId);
AcceptAndMarkComplete(deps, RefineQuestId);
AcceptAndMarkComplete(deps, CombatQuestId);
SeedStack(deps, PrototypeE7M1QuestCatalogRules.ChainTerminalItemId, 1);
Assert.True(TryAccept(deps, ChainQuestId).Success);
Assert.True(deps.ProgressStore.TryUpdateObjectiveCounter(PlayerId, ChainQuestId, ChainGatherObjectiveId, 5, out _));
Assert.True(deps.ProgressStore.TryAdvanceStep(PlayerId, ChainQuestId, 1, out _));
Assert.True(deps.ProgressStore.TryUpdateObjectiveCounter(PlayerId, ChainQuestId, "chain_obj_refine", 1, out _));
Assert.True(deps.ProgressStore.TryAdvanceStep(PlayerId, ChainQuestId, 2, out _));
Assert.True(deps.ProgressStore.TryUpdateObjectiveCounter(PlayerId, ChainQuestId, "chain_obj_stim", 1, out _));
Assert.True(deps.ProgressStore.TryAdvanceStep(PlayerId, ChainQuestId, 3, out _));
Assert.True(deps.ProgressStore.TryGetProgress(PlayerId, ChainQuestId, out var beforeGet));
Assert.Equal(QuestProgressStatus.Active, beforeGet.Status);
Assert.Equal(3, beforeGet.CurrentStepIndex);
// Act
var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(body);
var row = Assert.Single(body!.Quests, static r => r.QuestId == ChainQuestId);
Assert.Equal(QuestProgressApi.StatusCompleted, row.Status);
Assert.Equal(3, row.CurrentStepIndex);
Assert.NotNull(row.CompletedAt);
AssertOperatorChainCompletionRewardSummary(row.CompletionRewardSummary);
Assert.True(deps.ProgressStore.TryGetProgress(PlayerId, ChainQuestId, out var afterGet));
Assert.Equal(QuestProgressStatus.Completed, afterGet.Status);
Assert.Equal(3, afterGet.CurrentStepIndex);
Assert.NotNull(afterGet.CompletedAt);
}
private static void AssertGatherIntroCompletionRewardSummary(QuestCompletionRewardSummaryJson? summary)
{
Assert.NotNull(summary);
Assert.Empty(summary!.ItemGrants);
Assert.Single(summary.SkillXpGrants);
Assert.Equal("salvage", summary.SkillXpGrants[0].SkillId);
Assert.Equal(25, summary.SkillXpGrants[0].Amount);
Assert.Empty(summary.ReputationGrants);
}
private static void AssertOperatorChainCompletionRewardSummary(QuestCompletionRewardSummaryJson? summary)
{
Assert.NotNull(summary);
Assert.Single(summary!.ItemGrants);
Assert.Equal("survey_drone_kit", summary.ItemGrants[0].ItemId);
Assert.Equal(1, summary.ItemGrants[0].Quantity);
Assert.Single(summary.SkillXpGrants);
Assert.Equal("salvage", summary.SkillXpGrants[0].SkillId);
Assert.Equal(50, summary.SkillXpGrants[0].Amount);
Assert.Single(summary.ReputationGrants);
Assert.Equal(PrototypeE7M3QuestFactionRules.GridContractGateFactionId, summary.ReputationGrants[0].FactionId);
Assert.Equal(15, summary.ReputationGrants[0].Amount);
}
private static void AssertGridContractCompletionRewardSummary(QuestCompletionRewardSummaryJson? summary)
{
Assert.NotNull(summary);
Assert.Empty(summary!.ItemGrants);
Assert.Empty(summary.SkillXpGrants);
Assert.Single(summary.ReputationGrants);
Assert.Equal(RustCollectiveFactionId, summary.ReputationGrants[0].FactionId);
Assert.Equal(10, summary.ReputationGrants[0].Amount);
}
private static void AssertCompletionRewardSummariesEqual(
QuestCompletionRewardSummaryJson? first,
QuestCompletionRewardSummaryJson? second)
{
Assert.NotNull(first);
Assert.NotNull(second);
Assert.Equal(first!.ItemGrants.Count, second!.ItemGrants.Count);
for (var i = 0; i < first.ItemGrants.Count; i++)
{
Assert.Equal(first.ItemGrants[i].ItemId, second.ItemGrants[i].ItemId);
Assert.Equal(first.ItemGrants[i].Quantity, second.ItemGrants[i].Quantity);
}
Assert.Equal(first.SkillXpGrants.Count, second.SkillXpGrants.Count);
for (var i = 0; i < first.SkillXpGrants.Count; i++)
{
Assert.Equal(first.SkillXpGrants[i].SkillId, second.SkillXpGrants[i].SkillId);
Assert.Equal(first.SkillXpGrants[i].Amount, second.SkillXpGrants[i].Amount);
}
Assert.Equal(first.ReputationGrants.Count, second.ReputationGrants.Count);
for (var i = 0; i < first.ReputationGrants.Count; i++)
{
Assert.Equal(first.ReputationGrants[i].FactionId, second.ReputationGrants[i].FactionId);
Assert.Equal(first.ReputationGrants[i].Amount, second.ReputationGrants[i].Amount);
}
}
private static void CompleteOperatorChain(QuestWiringTestDependencies deps)
{
AcceptAndMarkComplete(deps, GatherQuestId);
AcceptAndMarkComplete(deps, RefineQuestId);
CompleteCombatEncounter(deps);
Assert.True(TryAccept(deps, ChainQuestId).Success);
Assert.True(TryGather(deps, "prototype_urban_bulk_delta").Success);
SeedStack(deps, "scrap_metal_bulk", 5);
Assert.True(TryCraft(deps, "refine_scrap_standard").Success);
SeedStack(deps, "refined_plate_stock", 2);
SeedStack(deps, "scrap_metal_bulk", 1);
Assert.True(TryCraft(deps, "make_field_stim_mk0").Success);
Assert.True(deps.ProgressStore.TryGetProgress(PlayerId, ChainQuestId, out var progress));
Assert.Equal(QuestProgressStatus.Completed, progress.Status);
Assert.Equal(3, progress.CurrentStepIndex);
}
private static void CompleteCombatEncounter(QuestWiringTestDependencies deps)
{
Assert.True(TryAccept(deps, CombatQuestId).Success);
DefeatAllRequiredTargets(deps);
var result = EncounterCompletionOperations.TryCompleteAndGrant(
PlayerId,
"prototype_combat_pocket",
deps.EncounterRegistry,
deps.RewardTableRegistry,
deps.EncounterProgressStore,
deps.CompletionStore,
deps.CompleteEventStore,
deps.ItemRegistry,
deps.InventoryStore,
deps.QuestRegistry,
deps.ProgressStore,
deps.SkillRegistry,
deps.XpStore,
deps.LevelCurve,
deps.PerkUnlockEngine,
deps.PerkStore,
deps.DeliveryStore,
deps.StandingStore,
deps.AuditStore,
deps.ContractInstanceStore,
deps.ContractOutcomeStore,
deps.ContractTemplateRegistry,
deps.TimeProvider);
Assert.True(result.Success);
}
private static void DefeatAllRequiredTargets(QuestWiringTestDependencies deps)
{
foreach (var npcId in new[] { "prototype_npc_melee", "prototype_npc_ranged", "prototype_npc_elite" })
{
_ = EncounterProgressOperations.TryActivateOnFirstEngagement(
PlayerId,
npcId,
deps.EncounterRegistry,
deps.EncounterProgressStore,
deps.CompletionStore);
_ = EncounterProgressOperations.TryMarkTargetDefeated(
PlayerId,
npcId,
deps.EncounterRegistry,
deps.EncounterProgressStore,
deps.CompletionStore);
}
}
private static QuestStateOperationResult TryAccept(QuestWiringTestDependencies deps, string questId) =>
QuestStateOperations.TryAccept(
PlayerId,
questId,
deps.QuestRegistry,
deps.ProgressStore,
deps.InventoryStore,
deps.ItemRegistry,
deps.SkillRegistry,
deps.XpStore,
deps.LevelCurve,
deps.PerkUnlockEngine,
deps.PerkStore,
deps.DeliveryStore,
deps.StandingStore,
deps.AuditStore,
deps.TimeProvider);
private static QuestStateOperationResult TryMarkComplete(QuestWiringTestDependencies deps, string questId) =>
QuestStateOperations.TryMarkComplete(
PlayerId,
questId,
deps.QuestRegistry,
deps.ProgressStore,
deps.ItemRegistry,
deps.InventoryStore,
deps.SkillRegistry,
deps.XpStore,
deps.LevelCurve,
deps.PerkUnlockEngine,
deps.PerkStore,
deps.DeliveryStore,
deps.StandingStore,
deps.AuditStore,
deps.TimeProvider);
private static void AcceptAndMarkComplete(QuestWiringTestDependencies deps, string questId)
{
Assert.True(TryAccept(deps, questId).Success);
Assert.True(TryMarkComplete(deps, questId).Success);
}
private static GatherResult TryGather(QuestWiringTestDependencies deps, string nodeId) =>
GatherOperations.TryGather(
PlayerId,
nodeId,
deps.NodeRegistry,
deps.ItemRegistry,
deps.InventoryStore,
deps.SkillRegistry,
deps.XpStore,
deps.LevelCurve,
deps.PerkUnlockEngine,
deps.InstanceStore,
deps.QuestRegistry,
deps.ProgressStore,
deps.PerkStore,
deps.DeliveryStore,
deps.StandingStore,
deps.AuditStore,
deps.TimeProvider);
private static CraftResult TryCraft(QuestWiringTestDependencies deps, string recipeId) =>
CraftOperations.TryCraft(
PlayerId,
recipeId,
quantity: 1,
deps.RecipeRegistry,
deps.ItemRegistry,
deps.InventoryStore,
deps.SkillRegistry,
deps.XpStore,
deps.LevelCurve,
deps.PerkUnlockEngine,
deps.QuestRegistry,
deps.ProgressStore,
deps.PerkStore,
deps.DeliveryStore,
deps.StandingStore,
deps.AuditStore,
deps.TimeProvider);
private static void SeedStack(QuestWiringTestDependencies deps, string itemId, int quantity)
{
var outcome = PlayerInventoryOperations.TryAddStack(
PlayerId,
itemId,
quantity,
deps.ItemRegistry,
deps.InventoryStore);
Assert.Equal(PlayerInventoryMutationKind.Applied, outcome.Kind);
}
private static QuestWiringTestDependencies ResolveDependencies(InMemoryWebApplicationFactory factory)
{
var services = factory.Services;
return new QuestWiringTestDependencies(
services.GetRequiredService<IQuestDefinitionRegistry>(),
services.GetRequiredService<IPlayerQuestStateStore>(),
services.GetRequiredService<IPlayerInventoryStore>(),
services.GetRequiredService<IItemDefinitionRegistry>(),
services.GetRequiredService<IResourceNodeDefinitionRegistry>(),
services.GetRequiredService<IResourceNodeInstanceStore>(),
services.GetRequiredService<IRecipeDefinitionRegistry>(),
services.GetRequiredService<ISkillDefinitionRegistry>(),
services.GetRequiredService<IPlayerSkillProgressionStore>(),
services.GetRequiredService<ISkillLevelCurve>(),
services.GetRequiredService<PerkUnlockEngine>(),
services.GetRequiredService<IEncounterDefinitionRegistry>(),
services.GetRequiredService<IRewardTableDefinitionRegistry>(),
services.GetRequiredService<IEncounterProgressStore>(),
services.GetRequiredService<IEncounterCompletionStore>(),
services.GetRequiredService<IEncounterCompleteEventStore>(),
services.GetRequiredService<IPlayerPerkStateStore>(),
services.GetRequiredService<IRewardDeliveryStore>(),
services.GetRequiredService<IFactionStandingStore>(),
services.GetRequiredService<IReputationDeltaStore>(),
services.GetRequiredService<IContractInstanceStore>(),
services.GetRequiredService<IContractOutcomeStore>(),
services.GetRequiredService<IContractTemplateRegistry>(),
TimeProvider.System);
}
private readonly record struct QuestWiringTestDependencies(
IQuestDefinitionRegistry QuestRegistry,
IPlayerQuestStateStore ProgressStore,
IPlayerInventoryStore InventoryStore,
IItemDefinitionRegistry ItemRegistry,
IResourceNodeDefinitionRegistry NodeRegistry,
IResourceNodeInstanceStore InstanceStore,
IRecipeDefinitionRegistry RecipeRegistry,
ISkillDefinitionRegistry SkillRegistry,
IPlayerSkillProgressionStore XpStore,
ISkillLevelCurve LevelCurve,
PerkUnlockEngine PerkUnlockEngine,
IEncounterDefinitionRegistry EncounterRegistry,
IRewardTableDefinitionRegistry RewardTableRegistry,
IEncounterProgressStore EncounterProgressStore,
IEncounterCompletionStore CompletionStore,
IEncounterCompleteEventStore CompleteEventStore,
IPlayerPerkStateStore PerkStore,
IRewardDeliveryStore DeliveryStore,
IFactionStandingStore StandingStore,
IReputationDeltaStore AuditStore,
IContractInstanceStore ContractInstanceStore,
IContractOutcomeStore ContractOutcomeStore,
IContractTemplateRegistry ContractTemplateRegistry,
TimeProvider TimeProvider);
}