191 lines
7.2 KiB
C#
191 lines
7.2 KiB
C#
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using NeonSprawl.Server.Game.Skills;
|
|
using Xunit;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Skills;
|
|
|
|
public sealed class SkillProgressionGrantApiTests
|
|
{
|
|
private static SkillProgressionGrantRequest Grant(
|
|
string skillId,
|
|
int schemaVersion,
|
|
int amount = 50,
|
|
string sourceKind = "activity") =>
|
|
new SkillProgressionGrantRequest
|
|
{
|
|
SchemaVersion = schemaVersion,
|
|
SkillId = skillId,
|
|
Amount = amount,
|
|
SourceKind = sourceKind,
|
|
};
|
|
|
|
private static SkillProgressionGrantRequest ValidGrant(string skillId, int amount = 50, string sourceKind = "activity") =>
|
|
Grant(skillId, SkillProgressionGrantRequest.CurrentSchemaVersion, amount, sourceKind);
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldReturnBadRequest_WhenSchemaVersionMismatch()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
var request = Grant("salvage", SkillProgressionGrantRequest.CurrentSchemaVersion + 42, amount: 1);
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/skill-progression", request);
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldReturnNotFound_WhenPlayerUnknown()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
"/game/players/missing-player/skill-progression",
|
|
ValidGrant("salvage"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldDenyUnknownSkill_WithReasonUnknownSkill_AndEchoProgression()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
ValidGrant("not-a-prototype-skill"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Granted);
|
|
Assert.Equal(SkillProgressionSnapshotApi.ReasonUnknownSkill, envelope.ReasonCode);
|
|
Assert.NotNull(envelope.Progression);
|
|
var salvage = envelope.Progression!.Skills!.Single(static s => s.Id == "salvage");
|
|
Assert.Equal(0, salvage.Xp);
|
|
Assert.Equal(1, salvage.Level);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldDenyTrainerOnSalvage_WithReasonSourceKindNotAllowed()
|
|
{
|
|
// Arrange — prototype_skills: salvage allowedXpSourceKinds = activity, mission_reward only
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
ValidGrant("salvage", amount: 10, sourceKind: "trainer"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Granted);
|
|
Assert.Equal(SkillProgressionSnapshotApi.ReasonSourceKindNotAllowed, envelope.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldDenyNonPositiveAmount_WithReasonInvalidAmount()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var response = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
ValidGrant("salvage", amount: 0));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var envelope = await response.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Granted);
|
|
Assert.Equal(SkillProgressionSnapshotApi.ReasonInvalidAmount, envelope.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldApplyGrant_AndGetMatches_WhenTrainerAllowedForRefine()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var post = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
ValidGrant("refine", amount: 25, sourceKind: "trainer"));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
|
var envelope = await post.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Granted);
|
|
Assert.Empty(envelope.LevelUps);
|
|
Assert.Single(envelope.Progression!.Skills!, static s => s.Id == "refine" && s.Xp == 25 && s.Level == 1);
|
|
|
|
var get = await client.GetFromJsonAsync<SkillProgressionSnapshotResponse>(
|
|
"/game/players/dev-local-1/skill-progression");
|
|
Assert.NotNull(get);
|
|
var refine = Assert.Single(get!.Skills!, static s => s.Id == "refine");
|
|
Assert.Equal(25, refine.Xp);
|
|
Assert.Equal(1, refine.Level);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldReportLevelUps_WhenThresholdCrossed()
|
|
{
|
|
// Arrange — content curve (prototype_level_curve.json): +100 xp from 0 → level 2
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var post = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
ValidGrant("intrusion", amount: 100, sourceKind: "activity"));
|
|
|
|
// Assert
|
|
var envelope = await post.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Granted);
|
|
var up = Assert.Single(envelope.LevelUps!);
|
|
Assert.Equal("intrusion", up.SkillId);
|
|
Assert.Equal(1, up.PreviousLevel);
|
|
Assert.Equal(2, up.NewLevel);
|
|
Assert.Single(envelope.Progression!.Skills!, static s => s.Id == "intrusion" && s.Xp == 100 && s.Level == 2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostSkillProgression_ShouldAcceptSourceKind_WithIgnoreCase_OnAllowedList()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
|
|
// Act
|
|
var post = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
ValidGrant("salvage", amount: 5, sourceKind: "ACTIVITY"));
|
|
|
|
// Assert
|
|
var envelope = await post.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Granted);
|
|
}
|
|
}
|