305 lines
11 KiB
C#
305 lines
11 KiB
C#
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text;
|
|
using NeonSprawl.Server.Game.Mastery;
|
|
using NeonSprawl.Server.Game.Skills;
|
|
using Xunit;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Mastery;
|
|
|
|
public sealed class PerkStateApiTests
|
|
{
|
|
private const string DevPlayer = "dev-local-1";
|
|
|
|
private static PerkBranchSelectRequest SelectBranch(
|
|
string skillId,
|
|
int tierIndex,
|
|
string branchId,
|
|
int schemaVersion = PerkBranchSelectRequest.CurrentSchemaVersion) =>
|
|
new PerkBranchSelectRequest
|
|
{
|
|
SchemaVersion = schemaVersion,
|
|
SkillId = skillId,
|
|
TierIndex = tierIndex,
|
|
BranchId = branchId,
|
|
};
|
|
|
|
private static SkillProgressionGrantRequest SalvageGrant(int amount) =>
|
|
new SkillProgressionGrantRequest
|
|
{
|
|
SchemaVersion = SkillProgressionGrantRequest.CurrentSchemaVersion,
|
|
SkillId = "salvage",
|
|
Amount = amount,
|
|
SourceKind = "activity",
|
|
};
|
|
|
|
[Fact]
|
|
public async Task GetPerkState_ShouldReturnNotFound_WhenPlayerUnknown()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.GetAsync("/game/players/missing-player/perk-state");
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPerkState_ShouldReturnSchemaV1_WithEmptyState_ForDevPlayer()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.GetAsync($"/game/players/{DevPlayer}/perk-state");
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var body = await response.Content.ReadFromJsonAsync<PerkStateSnapshotResponse>();
|
|
Assert.NotNull(body);
|
|
Assert.Equal(PerkStateSnapshotResponse.CurrentSchemaVersion, body!.SchemaVersion);
|
|
Assert.Equal(DevPlayer, body.PlayerId);
|
|
Assert.Empty(body.BranchPicks);
|
|
Assert.Empty(body.UnlockedPerkIds);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldReturnBadRequest_WhenBodyNull()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
new StringContent(string.Empty, Encoding.UTF8, "application/json"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldReturnBadRequest_WhenSchemaVersionMismatch()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
var request = SelectBranch("salvage", 1, "scrap_efficiency", PerkBranchSelectRequest.CurrentSchemaVersion + 42);
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync($"/game/players/{DevPlayer}/perk-state", request);
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldReturnNotFound_WhenPlayerUnknown()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
"/game/players/missing-player/perk-state",
|
|
SelectBranch("salvage", 1, "scrap_efficiency"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldDenyLevelTooLow_WhenSalvageBelowTierGate()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 1, "scrap_efficiency"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Selected);
|
|
Assert.Equal(PerkUnlockReasonCodes.LevelTooLow, envelope.ReasonCode);
|
|
Assert.Empty(envelope.UnlockedEvents);
|
|
Assert.Empty(envelope.PerkState.BranchPicks);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldSelectTier1_AndGetReflectsPick_AfterSalvageLevel2()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
var grant = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/skill-progression",
|
|
SalvageGrant(100));
|
|
Assert.Equal(HttpStatusCode.OK, grant.StatusCode);
|
|
|
|
// Act
|
|
var post = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 1, "scrap_efficiency"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
|
var envelope = await post.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Selected);
|
|
Assert.Null(envelope.ReasonCode);
|
|
Assert.Empty(envelope.UnlockedEvents);
|
|
var track = Assert.Single(envelope.PerkState.BranchPicks);
|
|
Assert.Equal("salvage", track.SkillId);
|
|
var pick = Assert.Single(track.Picks);
|
|
Assert.Equal(1, pick.TierIndex);
|
|
Assert.Equal("scrap_efficiency", pick.BranchId);
|
|
Assert.Empty(envelope.PerkState.UnlockedPerkIds);
|
|
|
|
var get = await client.GetFromJsonAsync<PerkStateSnapshotResponse>($"/game/players/{DevPlayer}/perk-state");
|
|
Assert.NotNull(get);
|
|
var getTrack = Assert.Single(get!.BranchPicks);
|
|
Assert.Equal("scrap_efficiency", Assert.Single(getTrack.Picks).BranchId);
|
|
}
|
|
|
|
private static async Task GrantSalvageLevel2Async(HttpClient client)
|
|
{
|
|
var grant = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/skill-progression",
|
|
SalvageGrant(100));
|
|
Assert.Equal(HttpStatusCode.OK, grant.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldDenyUnknownTrack_WhenSkillIdInvalid()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await GrantSalvageLevel2Async(client);
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("refine", 1, "scrap_efficiency"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Selected);
|
|
Assert.Equal(PerkUnlockReasonCodes.UnknownTrack, envelope.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldDenyUnknownTier_WhenTierIndexInvalid()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await GrantSalvageLevel2Async(client);
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 99, "scrap_efficiency"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Selected);
|
|
Assert.Equal(PerkUnlockReasonCodes.UnknownTier, envelope.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldDenyUnknownBranch_WhenBranchIdInvalid()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await GrantSalvageLevel2Async(client);
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 1, "not_a_branch"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Selected);
|
|
Assert.Equal(PerkUnlockReasonCodes.UnknownBranch, envelope.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldDenyBranchAlreadyChosen_OnSecondPick()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await GrantSalvageLevel2Async(client);
|
|
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 1, "scrap_efficiency"))).StatusCode);
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 1, "bulk_haul"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Selected);
|
|
Assert.Equal(PerkUnlockReasonCodes.BranchAlreadyChosen, envelope.ReasonCode);
|
|
Assert.Equal("scrap_efficiency", Assert.Single(Assert.Single(envelope.PerkState.BranchPicks).Picks).BranchId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostPerkState_ShouldEmitUnlockedEvents_WhenTier2PerkUnlocksAtLevel4()
|
|
{
|
|
// Arrange — 100 + 400 XP → level 4 before tier-1 pick; path-auto tier 2 on same POST
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/skill-progression",
|
|
SalvageGrant(100))).StatusCode);
|
|
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/skill-progression",
|
|
SalvageGrant(400))).StatusCode);
|
|
|
|
// Act
|
|
var post = await client.PostAsJsonAsync(
|
|
$"/game/players/{DevPlayer}/perk-state",
|
|
SelectBranch("salvage", 1, "scrap_efficiency"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
|
var envelope = await post.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Selected);
|
|
var unlocked = Assert.Single(envelope.UnlockedEvents);
|
|
Assert.Equal("salvage_scrap_efficiency_1", unlocked.PerkId);
|
|
Assert.Equal("salvage", unlocked.SkillId);
|
|
Assert.Equal(2, unlocked.TierIndex);
|
|
Assert.Equal("scrap_efficiency", unlocked.BranchId);
|
|
Assert.Equal("level_up", unlocked.Source);
|
|
Assert.Contains("salvage_scrap_efficiency_1", envelope.PerkState.UnlockedPerkIds);
|
|
}
|
|
}
|