Load campaign logs incrementally

This commit is contained in:
2026-04-02 00:03:54 +02:00
parent 6ea91ee565
commit e42c0fb9ba
12 changed files with 216 additions and 14 deletions

View File

@@ -309,4 +309,46 @@ public sealed class CampaignApiTests : ApiTestBase
Assert.False(string.IsNullOrWhiteSpace(entry.RollerDisplayName));
});
}
[Fact]
public async Task CampaignLogPage_ReturnsInitialAndIncrementalResults()
{
using var factory = CreateFactory();
using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false });
using var playerClient = factory.CreateClient(new() { AllowAutoRedirect = false });
await RegisterAsync(gmClient, "gm-log-page", "Password123", "GM");
await LoginAsync(gmClient, "gm-log-page", "Password123");
await RegisterAsync(playerClient, "player-log-page", "Password123", "Player");
await LoginAsync(playerClient, "player-log-page", "Password123");
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Log Page", "d6"));
var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(playerClient, "/api/characters", new("Roller", campaign.Id));
var skill = await PostAsync<CreateSkillRequest, SkillSummary>(playerClient, $"/api/characters/{character.Id}/skills", new("Stealth", "2D+1", 1, true));
var rollIds = new List<Guid>();
for (var i = 0; i < 5; i++)
{
var roll = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("public"));
rollIds.Add(roll.RollId);
}
var initialPage = await GetAsync<CampaignLogPage>(gmClient, $"/api/campaigns/{campaign.Id}/log/page?limit=3");
Assert.Equal(3, initialPage.Entries.Count);
Assert.Equal(rollIds[2], initialPage.Entries[0].RollId);
Assert.Equal(rollIds[^1], initialPage.Entries[^1].RollId);
Assert.Equal(rollIds[^1], initialPage.Cursor);
Assert.True(initialPage.HasMore);
Assert.False(initialPage.ResetRequired);
var latestRoll = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("public"));
var incrementalPage = await GetAsync<CampaignLogPage>(gmClient, $"/api/campaigns/{campaign.Id}/log/page?afterRollId={initialPage.Cursor}&limit=3");
Assert.Single(incrementalPage.Entries);
Assert.Equal(latestRoll.RollId, incrementalPage.Entries[0].RollId);
Assert.Equal(latestRoll.RollId, incrementalPage.Cursor);
Assert.False(incrementalPage.HasMore);
Assert.False(incrementalPage.ResetRequired);
}
}

View File

@@ -50,6 +50,11 @@ public sealed class RollVisibilityApiTests : ApiTestBase
Assert.Equal("public", observerLog[0].Visibility);
Assert.NotEmpty(observerLog[0].Dice);
var observerLogPage = await GetAsync<CampaignLogPage>(observerClient, $"/api/campaigns/{campaign.Id}/log/page");
Assert.Single(observerLogPage.Entries);
Assert.Equal(publicRoll.RollId, observerLogPage.Entries[0].RollId);
Assert.Equal(publicRoll.RollId, observerLogPage.Cursor);
await RegisterAsync(outsiderClient, "outsider", "Password123", "Outsider");
await LoginAsync(outsiderClient, "outsider", "Password123");

View File

@@ -74,4 +74,72 @@ public sealed class ServiceSkillRollTests
Assert.False(missingState.Succeeded);
Assert.True(ServiceTestSupport.GetValue(state).LogVersion > 1);
}
[Fact]
public void CampaignLogPage_ReturnsInitialWindowAndIncrementalAppend()
{
using var harness = ServiceTestSupport.CreateHarness(6, 5, 4, 3, 2, 6, 5, 4, 3, 2, 6, 5);
var service = harness.Service;
service.Register("gm-page", "Password123", "GM");
service.Register("owner-page", "Password123", "Owner");
var gmSession = ServiceTestSupport.GetValue(service.Login("gm-page", "Password123")).SessionToken;
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-page", "Password123")).SessionToken;
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Paged", "d6"));
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Hero", campaign.Id));
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, "Stealth", "2D+1", 1, true));
var rollIds = new List<Guid>();
for (var i = 0; i < 5; i++)
rollIds.Add(ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public")).RollId);
var initialPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(gmSession, campaign.Id, limit: 3));
Assert.Equal(3, initialPage.Entries.Count);
Assert.Equal(rollIds[2], initialPage.Entries[0].RollId);
Assert.Equal(rollIds[^1], initialPage.Entries[^1].RollId);
Assert.Equal(rollIds[^1], initialPage.Cursor);
Assert.True(initialPage.HasMore);
Assert.False(initialPage.ResetRequired);
var latestRoll = ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public"));
var incrementalPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(gmSession, campaign.Id, initialPage.Cursor, 3));
Assert.Single(incrementalPage.Entries);
Assert.Equal(latestRoll.RollId, incrementalPage.Entries[0].RollId);
Assert.Equal(latestRoll.RollId, incrementalPage.Cursor);
Assert.False(incrementalPage.HasMore);
Assert.False(incrementalPage.ResetRequired);
}
[Fact]
public void CampaignLogPage_RequestsResetWhenIncrementalGapExceedsLimit()
{
using var harness = ServiceTestSupport.CreateHarness(6, 5, 4, 3, 2, 6, 5, 4, 3, 2, 6, 5);
var service = harness.Service;
service.Register("gm-gap", "Password123", "GM");
service.Register("owner-gap", "Password123", "Owner");
var gmSession = ServiceTestSupport.GetValue(service.Login("gm-gap", "Password123")).SessionToken;
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-gap", "Password123")).SessionToken;
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Gap", "d6"));
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Hero", campaign.Id));
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, "Stealth", "2D+1", 1, true));
var rollIds = new List<Guid>();
for (var i = 0; i < 6; i++)
rollIds.Add(ServiceTestSupport.GetValue(service.RollSkill(ownerSession, skill.Id, "public")).RollId);
var gapPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(gmSession, campaign.Id, rollIds[0], 3));
Assert.True(gapPage.ResetRequired);
Assert.True(gapPage.HasMore);
Assert.Equal(3, gapPage.Entries.Count);
Assert.Equal(rollIds[3], gapPage.Entries[0].RollId);
Assert.Equal(rollIds[^1], gapPage.Entries[^1].RollId);
Assert.Equal(rollIds[^1], gapPage.Cursor);
}
}

View File

@@ -96,6 +96,7 @@ public sealed class WorkspaceQueryServiceTests
public ServiceResult<CharacterSheet> GetCharacterSheet(string sessionToken, Guid characterId) => throw new NotSupportedException();
public ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility) => throw new NotSupportedException();
public ServiceResult<IReadOnlyList<CampaignLogEntry>> GetCampaignLog(string sessionToken, Guid campaignId) => throw new NotSupportedException();
public ServiceResult<CampaignLogPage> GetCampaignLogPage(string sessionToken, Guid campaignId, Guid? afterRollId = null, int? limit = null) => throw new NotSupportedException();
public ServiceResult<CampaignStateSnapshot> GetCampaignStateSnapshot(string sessionToken, Guid campaignId) => throw new NotSupportedException();
}
}