namespace RpgRoller.Tests; public sealed class ServiceSkillGroupAndOwnershipTests { [Fact] public void SkillGroups_CanBeManagedAndAssignedWithinCharacterScope() { using var harness = ServiceTestSupport.CreateHarness(4, 3, 2); var service = harness.Service; service.Register("gm", "Password123", "GM"); service.Register("owner", "Password123", "Owner"); service.Register("other", "Password123", "Other"); var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken; var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken; var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken; var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6")); var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Char", campaign.Id)); var otherCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(otherSession, "Other Char", campaign.Id)); var ownerGroup = ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, ownerCharacter.Id, "Combat", "2D+1", 1, true)); Assert.False(service.CreateSkillGroup(ownerSession, otherCharacter.Id, "Not Allowed", "2D+1", 1, true).Succeeded); Assert.False(service.UpdateSkillGroup(otherSession, ownerGroup.Id, "Renamed by Other", "2D+1", 1, true).Succeeded); var renamedGroup = ServiceTestSupport.GetValue(service.UpdateSkillGroup(gmSession, ownerGroup.Id, "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 skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, "Strike", "2D+1", 1, true, renamedGroup.Id)); Assert.Equal(renamedGroup.Id, skill.SkillGroupId); var otherGroup = ServiceTestSupport.GetValue(service.CreateSkillGroup(otherSession, otherCharacter.Id, "Other Group", "2D+1", 1, true)); Assert.False(service.UpdateSkill(ownerSession, skill.Id, "Strike", "2D+1", 1, true, otherGroup.Id).Succeeded); var ungroupedSkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, skill.Id, "Strike", "2D+1", 1, true, null)); Assert.Null(ungroupedSkill.SkillGroupId); var regroupedSkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, skill.Id, "Strike", "2D+1", 1, true, renamedGroup.Id)); Assert.Equal(renamedGroup.Id, regroupedSkill.SkillGroupId); var deletedGroup = ServiceTestSupport.GetValue(service.DeleteSkillGroup(ownerSession, renamedGroup.Id)); Assert.True(deletedGroup); var ownerSheetAfterGroupDelete = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id)); var otherSheetAfterGroupDelete = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id)); Assert.DoesNotContain(ownerSheetAfterGroupDelete.SkillGroups, group => group.Id == renamedGroup.Id); Assert.Contains(otherSheetAfterGroupDelete.SkillGroups, group => group.Id == otherGroup.Id); Assert.Null(ownerSheetAfterGroupDelete.Skills.Single(s => s.Id == regroupedSkill.Id).SkillGroupId); var deletedSkill = ServiceTestSupport.GetValue(service.DeleteSkill(ownerSession, regroupedSkill.Id)); Assert.True(deletedSkill); var ownerView = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id)); var otherView = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id)); Assert.DoesNotContain(ownerView.SkillGroups, group => group.Id == renamedGroup.Id); Assert.Contains(otherView.SkillGroups, group => group.Id == otherGroup.Id); Assert.DoesNotContain(ownerView.Skills, skillSummary => skillSummary.Id == regroupedSkill.Id); } [Fact] public void CharacterOwnerTransfer_RequiresGmPrivileges() { using var harness = ServiceTestSupport.CreateHarness(); var service = harness.Service; service.Register("gm", "Password123", "GM"); service.Register("owner", "Password123", "Owner"); service.Register("receiver", "Password123", "Receiver"); var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken; var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken; var receiverSession = ServiceTestSupport.GetValue(service.Login("receiver", "Password123")).SessionToken; var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6")); var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Transfer Me", campaign.Id)); Assert.True(service.ActivateCharacter(ownerSession, character.Id).Succeeded); var ownerTransferAttempt = service.UpdateCharacter(ownerSession, character.Id, "Transfer Me", campaign.Id, "receiver"); Assert.False(ownerTransferAttempt.Succeeded); var missingOwnerAttempt = service.UpdateCharacter(gmSession, character.Id, "Transfer Me", campaign.Id, "missing-user"); Assert.False(missingOwnerAttempt.Succeeded); var gmTransfer = ServiceTestSupport.GetValue(service.UpdateCharacter(gmSession, character.Id, "Transferred", campaign.Id, "receiver")); var receiver = service.GetUserBySession(receiverSession); Assert.NotNull(receiver); Assert.Equal(receiver!.Id, gmTransfer.OwnerUserId); var previousOwnerMe = ServiceTestSupport.GetValue(service.GetMe(ownerSession)); Assert.Null(previousOwnerMe.ActiveCharacterId); Assert.False(service.ActivateCharacter(ownerSession, character.Id).Succeeded); Assert.True(service.ActivateCharacter(receiverSession, character.Id).Succeeded); } [Fact] public void CharacterUnlink_AllowsOwnerGmAndAdmin() { using var harness = ServiceTestSupport.CreateHarness(); var service = harness.Service; service.Register("gm", "Password123", "GM"); service.Register("owner", "Password123", "Owner"); service.Register("outsider", "Password123", "Outsider"); service.Register("admin2", "Password123", "Admin Two"); var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken; var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken; var outsiderSession = ServiceTestSupport.GetValue(service.Login("outsider", "Password123")).SessionToken; var adminTwoSession = ServiceTestSupport.GetValue(service.Login("admin2", "Password123")).SessionToken; var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6")); var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Unlink Me", campaign.Id)); var outsiderUnlink = service.UpdateCharacter(outsiderSession, character.Id, "Unlink Me", null); Assert.False(outsiderUnlink.Succeeded); var ownerUnlink = ServiceTestSupport.GetValue(service.UpdateCharacter(ownerSession, character.Id, "Owner Unlink", null)); Assert.Null(ownerUnlink.CampaignId); var relinkByOwner = ServiceTestSupport.GetValue(service.UpdateCharacter(ownerSession, character.Id, "Relink", campaign.Id)); Assert.Equal(campaign.Id, relinkByOwner.CampaignId); var gmUnlink = ServiceTestSupport.GetValue(service.UpdateCharacter(gmSession, character.Id, "Gm Unlink", null)); Assert.Null(gmUnlink.CampaignId); var relinkByGm = ServiceTestSupport.GetValue(service.UpdateCharacter(gmSession, character.Id, "Relink Again", campaign.Id)); Assert.Equal(campaign.Id, relinkByGm.CampaignId); var adminTwo = service.GetUserBySession(adminTwoSession); Assert.NotNull(adminTwo); _ = ServiceTestSupport.GetValue(service.UpdateUserRoles(gmSession, adminTwo!.Id, [ "admin" ])); var adminUnlink = ServiceTestSupport.GetValue(service.UpdateCharacter(adminTwoSession, character.Id, "Admin Unlink", null)); Assert.Null(adminUnlink.CampaignId); } [Fact] public void CharacterDelete_AllowsOnlyOwnerOrAdmin() { using var harness = ServiceTestSupport.CreateHarness(); var service = harness.Service; service.Register("admin", "Password123", "Admin"); service.Register("gm", "Password123", "GM"); service.Register("owner", "Password123", "Owner"); service.Register("other", "Password123", "Other"); var adminSession = ServiceTestSupport.GetValue(service.Login("admin", "Password123")).SessionToken; var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken; var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken; var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken; var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6")); var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Character", campaign.Id)); var otherCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(otherSession, "Other Character", campaign.Id)); var gmDeleteAttempt = service.DeleteCharacter(gmSession, ownerCharacter.Id); Assert.False(gmDeleteAttempt.Succeeded); var otherDeleteAttempt = service.DeleteCharacter(otherSession, ownerCharacter.Id); Assert.False(otherDeleteAttempt.Succeeded); var ownerDelete = ServiceTestSupport.GetValue(service.DeleteCharacter(ownerSession, ownerCharacter.Id)); Assert.True(ownerDelete); var adminDelete = ServiceTestSupport.GetValue(service.DeleteCharacter(adminSession, otherCharacter.Id)); Assert.True(adminDelete); var campaignAfterDeletes = ServiceTestSupport.GetValue(service.GetCampaign(gmSession, campaign.Id)); Assert.Empty(campaignAfterDeletes.Characters); } [Fact] public void RolemasterSkillDefinitions_CanonicalizeAndKeepLegacyNegativeModifierRules() { using var harness = ServiceTestSupport.CreateHarness(); var service = harness.Service; service.Register("gm-rm", "Password123", "GM"); service.Register("owner-rm", "Password123", "Owner"); var gmSession = ServiceTestSupport.GetValue(service.Login("gm-rm", "Password123")).SessionToken; var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-rm", "Password123")).SessionToken; var rolemasterCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Shadow World", "rolemaster")); var dndCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Forgotten Realms", "dnd5e")); var rolemasterCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Harn", rolemasterCampaign.Id)); var dndCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Mage", dndCampaign.Id)); var negativeDndSkill = service.CreateSkill(ownerSession, dndCharacter.Id, "Invalid", "1d20-1", 0, false); Assert.False(negativeDndSkill.Succeeded); var rolemasterGroup = ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, rolemasterCharacter.Id, "Initiative", "2d10-15", 3, true)); Assert.Equal("2d10-15", rolemasterGroup.DiceRollDefinition); Assert.Equal(0, rolemasterGroup.WildDice); Assert.False(rolemasterGroup.AllowFumble); var percentileSkill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, rolemasterCharacter.Id, "Perception", "1d100-20", 4, true)); Assert.Equal("d100-20", percentileSkill.DiceRollDefinition); Assert.Equal(0, percentileSkill.WildDice); Assert.False(percentileSkill.AllowFumble); var openEndedSkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, percentileSkill.Id, "Perception", "1d100!+85", 5, true)); Assert.Equal("d100!+85", openEndedSkill.DiceRollDefinition); Assert.Equal(0, openEndedSkill.WildDice); Assert.False(openEndedSkill.AllowFumble); } }