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

326 lines
13 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
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.Quests;
public sealed class QuestAcceptApiTests
{
private const string PlayerId = "dev-local-1";
private const string GatherQuestId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId;
private const string RefineQuestId = PrototypeE7M1QuestCatalogRules.RefineIntroQuestId;
private const string ChainQuestId = PrototypeE7M1QuestCatalogRules.ChainQuestId;
private const string GridContractQuestId = PrototypeE7M1QuestCatalogRules.GridContractQuestId;
[Fact]
public async Task PostQuestAccept_ShouldReturnNotFound_WhenPlayerUnknown()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsync(
$"/game/players/missing-player/quests/{GatherQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PostQuestAccept_ShouldReturnNotFound_WhenPlayerIdIsWhitespaceOnly()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsync(
$"/game/players/%20/quests/{GatherQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PostQuestAccept_ShouldReturnAcceptedTrue_WhenGatherIntroNotStarted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.Equal(QuestAcceptResponse.CurrentSchemaVersion, body!.SchemaVersion);
Assert.True(body.Accepted);
Assert.Null(body.ReasonCode);
Assert.NotNull(body.Quest);
Assert.Equal(GatherQuestId, body.Quest!.QuestId);
Assert.Equal(QuestProgressApi.StatusActive, body.Quest.Status);
Assert.Equal(0, body.Quest.CurrentStepIndex);
Assert.Empty(body.Quest.ObjectiveCounters);
Assert.Null(body.Quest.CompletedAt);
Assert.Null(body.Quest.CompletionRewardSummary);
var getResponse = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
var progress = await getResponse.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(progress);
var row = Assert.Single(progress!.Quests, static r => r.QuestId == GatherQuestId);
Assert.Equal(QuestProgressApi.StatusActive, row.Status);
}
[Fact]
public async Task PostQuestAccept_ShouldReturnAcceptedTrue_WhenRequestBodyOmitted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
content: null);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.True(body!.Accepted);
Assert.Equal(GatherQuestId, body.Quest!.QuestId);
}
[Fact]
public async Task PostQuestAccept_ShouldReturnAcceptedTrue_WhenRequestBodyIsEmptyObject()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
using var content = new StringContent("{}", Encoding.UTF8, "application/json");
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.True(body!.Accepted);
Assert.Equal(GatherQuestId, body.Quest!.QuestId);
}
[Fact]
public async Task PostQuestAccept_ShouldReturnBadRequest_WhenSchemaVersionInvalid()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
using var content = new StringContent(
JsonSerializer.Serialize(new { schemaVersion = 99 }),
Encoding.UTF8,
"application/json");
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
content);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task PostQuestAccept_ShouldDenyPrerequisiteIncomplete_WhenRefineIntroBeforeGatherComplete()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{RefineQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.False(body!.Accepted);
Assert.Equal(QuestStateReasonCodes.PrerequisiteIncomplete, body.ReasonCode);
Assert.Null(body.Quest);
var getResponse = await client.GetAsync($"/game/players/{PlayerId}/quest-progress");
var progress = await getResponse.Content.ReadFromJsonAsync<QuestProgressListResponse>();
Assert.NotNull(progress);
var row = Assert.Single(progress!.Quests, static r => r.QuestId == RefineQuestId);
Assert.Equal(QuestProgressApi.StatusNotStarted, row.Status);
}
[Fact]
public async Task PostQuestAccept_ShouldDenyAlreadyActive_WhenQuestAlreadyAccepted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
_ = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.False(body!.Accepted);
Assert.Equal(QuestStateReasonCodes.AlreadyActive, body.ReasonCode);
Assert.NotNull(body.Quest);
Assert.Equal(QuestProgressApi.StatusActive, body.Quest!.Status);
Assert.Equal(0, body.Quest.CurrentStepIndex);
}
[Fact]
public async Task PostQuestAccept_ShouldDenyAlreadyCompleted_WhenQuestAlreadyCompleted()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
var services = factory.Services;
var questRegistry = services.GetRequiredService<IQuestDefinitionRegistry>();
var progressStore = services.GetRequiredService<IPlayerQuestStateStore>();
var inventoryStore = services.GetRequiredService<IPlayerInventoryStore>();
var itemRegistry = services.GetRequiredService<IItemDefinitionRegistry>();
var skillRegistry = services.GetRequiredService<ISkillDefinitionRegistry>();
var skillProgressionStore = services.GetRequiredService<IPlayerSkillProgressionStore>();
var levelCurve = services.GetRequiredService<ISkillLevelCurve>();
var perkUnlockEngine = services.GetRequiredService<PerkUnlockEngine>();
var perkStore = services.GetRequiredService<IPlayerPerkStateStore>();
var deliveryStore = services.GetRequiredService<IRewardDeliveryStore>();
var standingStore = services.GetRequiredService<IFactionStandingStore>();
var auditStore = services.GetRequiredService<IReputationDeltaStore>();
var timeProvider = TimeProvider.System;
Assert.True(QuestStateOperations.TryAccept(
PlayerId,
GatherQuestId,
questRegistry,
progressStore,
inventoryStore,
itemRegistry,
skillRegistry,
skillProgressionStore,
levelCurve,
perkUnlockEngine,
perkStore,
deliveryStore,
standingStore,
auditStore,
timeProvider).Success);
Assert.True(QuestStateOperations.TryMarkComplete(
PlayerId,
GatherQuestId,
questRegistry,
progressStore,
itemRegistry,
inventoryStore,
skillRegistry,
skillProgressionStore,
levelCurve,
perkUnlockEngine,
perkStore,
deliveryStore,
standingStore,
auditStore,
timeProvider).Success);
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GatherQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.False(body!.Accepted);
Assert.Equal(QuestStateReasonCodes.AlreadyCompleted, body.ReasonCode);
Assert.NotNull(body.Quest);
Assert.Equal(QuestProgressApi.StatusCompleted, body.Quest!.Status);
Assert.Equal(0, body.Quest.CurrentStepIndex);
Assert.NotNull(body.Quest.CompletedAt);
Assert.NotNull(body.Quest.CompletionRewardSummary);
Assert.Empty(body.Quest.CompletionRewardSummary!.ItemGrants);
Assert.Single(body.Quest.CompletionRewardSummary.SkillXpGrants);
Assert.Equal("salvage", body.Quest.CompletionRewardSummary.SkillXpGrants[0].SkillId);
Assert.Equal(25, body.Quest.CompletionRewardSummary.SkillXpGrants[0].Amount);
}
[Fact]
public async Task PostQuestAccept_ShouldDenyUnknownQuest_WhenQuestIdNotInCatalog()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/prototype_quest_missing/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.False(body!.Accepted);
Assert.Equal(QuestStateReasonCodes.UnknownQuest, body.ReasonCode);
Assert.Null(body.Quest);
}
[Fact]
public async Task PostQuestAccept_ShouldDenyFactionGateBlocked_WhenGridContractBelowThreshold()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
var progressStore = factory.Services.GetRequiredService<IPlayerQuestStateStore>();
Assert.True(progressStore.TryActivate(PlayerId, ChainQuestId, out _));
Assert.True(progressStore.TryMarkComplete(PlayerId, ChainQuestId, DateTimeOffset.UtcNow, out _));
// Act
var response = await client.PostAsync(
$"/game/players/{PlayerId}/quests/{GridContractQuestId}/accept",
JsonContent.Create(new QuestAcceptRequest { SchemaVersion = QuestAcceptRequest.CurrentSchemaVersion }));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<QuestAcceptResponse>();
Assert.NotNull(body);
Assert.False(body!.Accepted);
Assert.Equal(QuestStateReasonCodes.FactionGateBlocked, body.ReasonCode);
Assert.Null(body.Quest);
}
}