Add payload serializer guardrails
This commit is contained in:
27
RpgRoller.Tests/Api/ResponseCompressionApiTests.cs
Normal file
27
RpgRoller.Tests/Api/ResponseCompressionApiTests.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class ResponseCompressionApiTests : ApiTestBase
|
||||
{
|
||||
public ResponseCompressionApiTests(WebApplicationFactory<Program> factory) : base(factory)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthenticatedJsonResponses_EnableGzipCompression()
|
||||
{
|
||||
using var factory = CreateFactory();
|
||||
using var client = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
|
||||
await RegisterAsync(client, "gm-compress", "Password123", "GM");
|
||||
await LoginAsync(client, "gm-compress", "Password123");
|
||||
_ = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("Compressed", "d6"));
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/api/campaigns");
|
||||
request.Headers.Add("Accept-Encoding", "gzip");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Contains("gzip", response.Content.Headers.ContentEncoding);
|
||||
}
|
||||
}
|
||||
127
RpgRoller.Tests/PayloadBudgetTests.cs
Normal file
127
RpgRoller.Tests/PayloadBudgetTests.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Text.Json;
|
||||
using RpgRoller.Contracts;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class PayloadBudgetTests
|
||||
{
|
||||
[Fact]
|
||||
public void CharacterSheetPayload_StaysWithinBudget()
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
|
||||
service.Register("gm-sheet", "Password123", "GM");
|
||||
service.Register("owner-sheet", "Password123", "Owner");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm-sheet", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-sheet", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Payload Sheet", "d6"));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Payload Hero", campaign.Id));
|
||||
|
||||
var groupIds = new List<Guid>
|
||||
{
|
||||
ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, character.Id, "Combat", "2D+1", 1, true)).Id,
|
||||
ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, character.Id, "Social", "2D+1", 1, true)).Id,
|
||||
ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, character.Id, "Knowledge", "2D+1", 1, true)).Id,
|
||||
ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, character.Id, "Survival", "2D+1", 1, true)).Id
|
||||
};
|
||||
|
||||
for (var i = 0; i < 18; i++)
|
||||
{
|
||||
Guid? skillGroupId = i < 16 ? groupIds[i % groupIds.Count] : null;
|
||||
_ = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, $"Skill {i:D2}", "2D+1", 1, true, skillGroupId));
|
||||
}
|
||||
|
||||
var sheet = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, character.Id));
|
||||
AssertPayloadWithinBudget(sheet, 12 * 1024, "initial character sheet");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CampaignLogInitialPagePayload_StaysWithinBudget()
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness(CreateScriptedRolls(220));
|
||||
var service = harness.Service;
|
||||
|
||||
service.Register("gm-log-budget", "Password123", "GM");
|
||||
service.Register("owner-log-budget", "Password123", "Owner");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm-log-budget", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-log-budget", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Payload Log", "d6"));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Log Hero", campaign.Id));
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, "Stealth", "2D+1", 1, true));
|
||||
|
||||
for (var i = 0; i < 25; i++)
|
||||
_ = ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public"));
|
||||
|
||||
var page = ServiceTestSupport.GetValue(service.GetCampaignLogPage(gmSession, campaign.Id, limit: 25));
|
||||
AssertPayloadWithinBudget(page, 8 * 1024, "initial log page");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CampaignLogIncrementalPayload_StaysWithinBudget()
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness(CreateScriptedRolls(240));
|
||||
var service = harness.Service;
|
||||
|
||||
service.Register("gm-log-delta", "Password123", "GM");
|
||||
service.Register("owner-log-delta", "Password123", "Owner");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm-log-delta", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-log-delta", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Payload Delta", "d6"));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Delta Hero", campaign.Id));
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, "Stealth", "2D+1", 1, true));
|
||||
|
||||
for (var i = 0; i < 25; i++)
|
||||
_ = ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public"));
|
||||
|
||||
var initialPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(gmSession, campaign.Id, limit: 25));
|
||||
_ = ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public"));
|
||||
|
||||
var incrementalPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(gmSession, campaign.Id, initialPage.Cursor, 25));
|
||||
AssertPayloadWithinBudget(incrementalPage, 2 * 1024, "incremental log update");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RollResultPayload_StaysWithinJsInteropBudget()
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness(CreateScriptedRolls(40));
|
||||
var service = harness.Service;
|
||||
|
||||
service.Register("gm-roll-budget", "Password123", "GM");
|
||||
service.Register("owner-roll-budget", "Password123", "Owner");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm-roll-budget", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-roll-budget", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Payload Roll", "d6"));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Roll Hero", campaign.Id));
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, "Wild Roll", "6D+3", 3, true));
|
||||
|
||||
var roll = ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public"));
|
||||
AssertPayloadWithinBudget(roll, 16 * 1024, "roll mutation response");
|
||||
}
|
||||
|
||||
private static void AssertPayloadWithinBudget<T>(T payload, int maxBytes, string label)
|
||||
{
|
||||
var byteCount = JsonSerializer.SerializeToUtf8Bytes(payload, SerializerOptions).Length;
|
||||
Assert.True(byteCount <= maxBytes, $"{label} payload was {byteCount} bytes, above the {maxBytes}-byte budget.");
|
||||
}
|
||||
|
||||
private static int[] CreateScriptedRolls(int count)
|
||||
{
|
||||
var values = new[] { 6, 5, 4, 3, 2, 1 };
|
||||
var scriptedRolls = new int[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
scriptedRolls[i] = values[i % values.Length];
|
||||
|
||||
return scriptedRolls;
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = RpgRollerJson.CreateSerializerOptions();
|
||||
}
|
||||
Reference in New Issue
Block a user