diff --git a/bruno/neon-sprawl-server/skill-progression/Post skill progression grant.bru b/bruno/neon-sprawl-server/skill-progression/Post skill progression grant.bru index 4ccbead..9595d4a 100644 --- a/bruno/neon-sprawl-server/skill-progression/Post skill progression grant.bru +++ b/bruno/neon-sprawl-server/skill-progression/Post skill progression grant.bru @@ -8,6 +8,7 @@ docs { NEO-38: single skill XP grant + allowlist + level-up metadata; see server README. NEO-40: same contract; server adds comment-only telemetry hook markers (no request/response change). NEO-47: on level-up, server re-evaluates mastery perk unlocks internally (no JSON change until NEO-48 perk-state HTTP). + Success responses include reasonCode: null (stable since NEO-38); perk-state POST omits reasonCode on success instead. E2.M2 tracking row in docs/decomposition/modules/documentation_and_implementation_alignment.md lists NEO-40 landed with plan + manual QA links. } @@ -32,6 +33,7 @@ tests { const body = res.getBody(); expect(body.schemaVersion).to.equal(1); expect(body.granted).to.equal(true); + expect(body.reasonCode).to.equal(null); expect(body.progression).to.be.an("object"); expect(body.progression.schemaVersion).to.equal(1); expect(body.progression.playerId).to.equal("dev-local-1"); diff --git a/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantApiTests.cs b/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantApiTests.cs index 57657a9..3d142c9 100644 --- a/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantApiTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantApiTests.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Net; using System.Net.Http.Json; +using System.Text.Json; using NeonSprawl.Server.Game.Skills; using Xunit; @@ -120,6 +121,26 @@ public sealed class SkillProgressionGrantApiTests Assert.Equal(SkillProgressionSnapshotApi.ReasonInvalidAmount, envelope.ReasonCode); } + [Fact] + public async Task PostSkillProgression_ShouldSerializeReasonCodeNull_OnSuccess() + { + // 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: 10)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + Assert.True(doc.RootElement.TryGetProperty("reasonCode", out var reasonCode)); + Assert.Equal(JsonValueKind.Null, reasonCode.ValueKind); + } + [Fact] public async Task PostSkillProgression_ShouldApplyGrant_AndGetMatches_WhenTrainerAllowedForRefine() { diff --git a/server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantDtos.cs b/server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantDtos.cs index d3ba3e9..b853314 100644 --- a/server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantDtos.cs +++ b/server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantDtos.cs @@ -34,7 +34,6 @@ public sealed class SkillProgressionGrantResponse /// Set when is false; stable snake_case values (see README). [JsonPropertyName("reasonCode")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ReasonCode { get; init; } [JsonPropertyName("progression")]