namespace RpgRoller.Tests; public sealed class CampaignApiTests : ApiTestBase { public CampaignApiTests(WebApplicationFactory factory) : base(factory) { } [Fact] public async Task CampaignCharacterAndSkillFlow_EnforcesRulesetValidation() { using var factory = CreateFactory(6, 6, 6); using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false }); await RegisterAsync(gmClient, "gm", "Password123", "Game Master"); await LoginAsync(gmClient, "gm", "Password123"); var campaign = await PostAsync(gmClient, "/api/campaigns", new("Alpha Campaign", "dnd5e")); var gmCharacter = await PostAsync(gmClient, "/api/characters", new("Arin", campaign.Id)); var activateResponse = await gmClient.PostAsync($"/api/characters/{gmCharacter.Id}/activate", null); Assert.Equal(HttpStatusCode.OK, activateResponse.StatusCode); var createdSkill = await PostAsync(gmClient, $"/api/characters/{gmCharacter.Id}/skills", new("Arcana", "2d12+2", 0, false)); Assert.Equal("2d12+2", createdSkill.DiceRollDefinition); Assert.Equal(0, createdSkill.WildDice); Assert.False(createdSkill.AllowFumble); var updatedSkill = await PutAsync(gmClient, $"/api/skills/{createdSkill.Id}", new("Arcana Mastery", "2d12+3", 0, false)); Assert.Equal("Arcana Mastery", updatedSkill.Name); Assert.Equal("2d12+3", updatedSkill.DiceRollDefinition); Assert.Equal(0, updatedSkill.WildDice); Assert.False(updatedSkill.AllowFumble); var invalidSkill = await gmClient.PostAsJsonAsync($"/api/characters/{gmCharacter.Id}/skills", new CreateSkillRequest("Broken", "5D+4", 0, false)); Assert.Equal(HttpStatusCode.BadRequest, invalidSkill.StatusCode); var details = await GetAsync(gmClient, $"/api/campaigns/{campaign.Id}"); Assert.Equal(campaign.Id, details.Id); Assert.Equal(1, details.Characters); var currentCampaignCharacters = await GetAsync>(gmClient, "/api/characters/current-campaign"); Assert.Single(currentCampaignCharacters); Assert.Equal(gmCharacter.Id, currentCampaignCharacters[0].Id); var otherCampaign = await PostAsync(gmClient, "/api/campaigns", new("Beta Campaign", "d6")); var updatedCharacter = await PutAsync(gmClient, $"/api/characters/{gmCharacter.Id}", new("Arin Updated", otherCampaign.Id)); Assert.Equal("Arin Updated", updatedCharacter.Name); Assert.Equal(otherCampaign.Id, updatedCharacter.CampaignId); } [Fact] public async Task SkillGroupsAndOwnerTransfer_WorkThroughApi() { using var factory = CreateFactory(6, 4, 5, 3); using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false }); using var ownerClient = factory.CreateClient(new() { AllowAutoRedirect = false }); using var receiverClient = factory.CreateClient(new() { AllowAutoRedirect = false }); await RegisterAsync(gmClient, "gm2", "Password123", "GM"); await LoginAsync(gmClient, "gm2", "Password123"); await RegisterAsync(ownerClient, "owner2", "Password123", "Owner"); await LoginAsync(ownerClient, "owner2", "Password123"); await RegisterAsync(receiverClient, "receiver2", "Password123", "Receiver"); await LoginAsync(receiverClient, "receiver2", "Password123"); var campaign = await PostAsync(gmClient, "/api/campaigns", new("Grouped Campaign", "d6")); var character = await PostAsync(ownerClient, "/api/characters", new("Grouped Hero", campaign.Id)); var createdGroup = await PostAsync(ownerClient, $"/api/characters/{character.Id}/skill-groups", new("Combat", "2D+1", 1, true)); var renamedGroup = await PutAsync(gmClient, $"/api/skill-groups/{createdGroup.Id}", new("Battle", "3D+2", 2, false)); Assert.Equal("Battle", renamedGroup.Name); Assert.Equal("3D+2", renamedGroup.DiceRollDefinition); Assert.Equal(2, renamedGroup.WildDice); Assert.False(renamedGroup.AllowFumble); var groupedSkill = await PostAsync(ownerClient, $"/api/characters/{character.Id}/skills", new("Strike", "2D+1", 1, true, renamedGroup.Id)); Assert.Equal(renamedGroup.Id, groupedSkill.SkillGroupId); var ungroupedSkill = await PutAsync(ownerClient, $"/api/skills/{groupedSkill.Id}", new("Strike", "2D+1", 1, true, null)); Assert.Null(ungroupedSkill.SkillGroupId); var groupedAgainSkill = await PutAsync(ownerClient, $"/api/skills/{groupedSkill.Id}", new("Strike", "2D+1", 1, true, renamedGroup.Id)); Assert.Equal(renamedGroup.Id, groupedAgainSkill.SkillGroupId); var deleteSkill = await ownerClient.DeleteAsync($"/api/skills/{groupedAgainSkill.Id}"); Assert.Equal(HttpStatusCode.OK, deleteSkill.StatusCode); var deleteGroup = await ownerClient.DeleteAsync($"/api/skill-groups/{renamedGroup.Id}"); Assert.Equal(HttpStatusCode.OK, deleteGroup.StatusCode); var transferResult = await PutAsync(gmClient, $"/api/characters/{character.Id}", new("Grouped Hero", campaign.Id, "receiver2")); Assert.Equal("Grouped Hero", transferResult.Name); var ownerActivate = await ownerClient.PostAsync($"/api/characters/{character.Id}/activate", null); Assert.Equal(HttpStatusCode.BadRequest, ownerActivate.StatusCode); var receiverActivate = await receiverClient.PostAsync($"/api/characters/{character.Id}/activate", null); Assert.Equal(HttpStatusCode.OK, receiverActivate.StatusCode); } }