From 82078c255a0c5bda508a1c72fe16f597c2fa259b Mon Sep 17 00:00:00 2001 From: gary Date: Wed, 31 Dec 2025 23:30:57 +0800 Subject: [PATCH] test: unit test --- .gitignore | 4 +- Directory.Packages.props | 4 + .../Game.Service/Services/CheckService.cs | 11 +- src/ToolBox/Tools/GameTools/ScenarioTools.cs | 1 - src/UnitTests/Game.Test/Game.Test.csproj | 26 ++ .../Game.Test/Tests/CharacterTest.cs | 267 +++++++++++++++++ src/UnitTests/Game.Test/Tests/CheckTest.cs | 270 ++++++++++++++++++ src/UnitTests/Game.Test/Tests/KPTest.cs | 226 +++++++++++++++ src/UnitTests/Game.Test/Tests/SenarioTest.cs | 74 +++++ trpg-mcp.sln | 18 ++ 10 files changed, 893 insertions(+), 8 deletions(-) create mode 100644 src/UnitTests/Game.Test/Game.Test.csproj create mode 100644 src/UnitTests/Game.Test/Tests/CharacterTest.cs create mode 100644 src/UnitTests/Game.Test/Tests/CheckTest.cs create mode 100644 src/UnitTests/Game.Test/Tests/KPTest.cs create mode 100644 src/UnitTests/Game.Test/Tests/SenarioTest.cs diff --git a/.gitignore b/.gitignore index 9421f42..ba0001c 100644 --- a/.gitignore +++ b/.gitignore @@ -487,4 +487,6 @@ $RECYCLE.BIN/ /trpg.db /trpg.db-wal /trpg.db-shm -*.db \ No newline at end of file +*.db + +/coveragereport \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 2017b8c..7009022 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,5 +16,9 @@ + + + + \ No newline at end of file diff --git a/src/Modules/Game.Service/Services/CheckService.cs b/src/Modules/Game.Service/Services/CheckService.cs index 6530a41..53b1067 100644 --- a/src/Modules/Game.Service/Services/CheckService.cs +++ b/src/Modules/Game.Service/Services/CheckService.cs @@ -20,12 +20,11 @@ public CheckService(TrpgDbContext context) public async Task RollDiceAsync(string diceExpression) { var parts = diceExpression.ToLower().Split('d'); - if (parts.Length != 2) throw new ArgumentException("Invalid dice expression."); - - int numDice = int.Parse(parts[0]); - int numSides = int.Parse(parts[1]); - - var random = new Random(); + if (parts.Length != 2) + throw new ArgumentException("Invalid dice expression."); + if (!int.TryParse(parts[0], out int numDice) || !int.TryParse(parts[1], out int numSides)) + throw new ArgumentException("Invalid dice expression."); + var random = new Random(); int total = 0; for (int i = 0; i < numDice; i++) { diff --git a/src/ToolBox/Tools/GameTools/ScenarioTools.cs b/src/ToolBox/Tools/GameTools/ScenarioTools.cs index f8edc23..de1fef0 100644 --- a/src/ToolBox/Tools/GameTools/ScenarioTools.cs +++ b/src/ToolBox/Tools/GameTools/ScenarioTools.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Server; using System.ComponentModel; -using System.Text.Json; using Game.Service.Interface; using Game.Service.View; using Common.Model; diff --git a/src/UnitTests/Game.Test/Game.Test.csproj b/src/UnitTests/Game.Test/Game.Test.csproj new file mode 100644 index 0000000..eea8252 --- /dev/null +++ b/src/UnitTests/Game.Test/Game.Test.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UnitTests/Game.Test/Tests/CharacterTest.cs b/src/UnitTests/Game.Test/Tests/CharacterTest.cs new file mode 100644 index 0000000..9b4b887 --- /dev/null +++ b/src/UnitTests/Game.Test/Tests/CharacterTest.cs @@ -0,0 +1,267 @@ +using Game.Service.Data; +using Game.Service.Data.Models; +using Game.Service.Request; +using Game.Service.Services; +using Microsoft.EntityFrameworkCore; +namespace Game.Test.Tests +{ + public class CharacterTest + { + [Fact] + public async Task GetAllCharactersAsync_ReturnFalse() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + + // Act + var result = await service.GetAllCharactersAsync(); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task CreateCharacterAsync_And_GetCharacterByIdAsync() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + var request = new PlayerCharacterRequest + { + Name = "Test", + Gender = "M", + Age = 20, + PhysicalDesc = "Desc", + Biography = "Bio", + StatusEffects = "None", + Notes = "Note", + IsDead = false, + IsTemplate = false, + IsActive = true + }; + + // Act + var created = await service.CreateCharacterAsync(request); + var fetched = await service.GetCharacterByIdAsync(created!.Id); + + // Assert + Assert.NotNull(created); + Assert.NotNull(fetched); + Assert.Equal("Test", fetched.Name); + } + + [Fact] + public async Task UpdateCharacterAsync_UpdatesFields() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + var request = new PlayerCharacterRequest { Name = "Old", Gender = "M", Age = 20, IsActive = true }; + + // Act + var created = await service.CreateCharacterAsync(request); + var update = new PlayerCharacterRequest { Name = "New", Gender = "F", Age = 30, IsActive = false }; + var updated = await service.UpdateCharacterAsync(created!.Id, update); + + // Assert + Assert.NotNull(created); + Assert.NotNull(updated); + Assert.Equal("New", updated.Name); + Assert.Equal("F", updated.Gender); + Assert.Equal(30, updated.Age); + Assert.False(updated.IsActive); + } + + [Fact] + public async Task DeleteCharacterAsync_RemovesCharacter() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + var request = new PlayerCharacterRequest { Name = "DeleteMe", Gender = "M", Age = 20, IsActive = true }; + var created = await service.CreateCharacterAsync(request); + + // Act + var deleted = await service.DeleteCharacterAsync(created!.Id); + var fetched = await service.GetCharacterByIdAsync(created.Id); + + // Assert + Assert.NotNull(created); + Assert.True(deleted); + Assert.Null(fetched); + } + + [Fact] + public async Task UpdateCharacterAttributeAsync_UpdatesValue() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + var attr = new Attributes { Name = "HP", Description = "Health" }; + context.Attributes.Add(attr); + await context.SaveChangesAsync(); + var request = new PlayerCharacterRequest { Name = "AttrTest", Gender = "M", Age = 20, IsActive = true }; + var created = await service.CreateCharacterAsync(request); + Assert.NotNull(created); + var charAttr = new CharacterAttribute { CharacterId = created.Id, AttributeId = attr.Id, MaxValue = 100, CurrentValue = 50 }; + context.CharacterAttributes.Add(charAttr); + await context.SaveChangesAsync(); + + // Act + var updated = await service.UpdateCharacterAttributeAsync(created.Id, "HP", 80); + var fetched = await service.GetCharacterByIdAsync(created.Id); + var hp = fetched!.CharacterAttributes.FirstOrDefault(a => a != null && a.Attribute != null && a.Attribute.Name == "HP"); + + // Assert + Assert.NotNull(updated); + Assert.NotNull(fetched); + Assert.NotNull(hp); + Assert.NotNull(hp.Attribute); + Assert.Equal(80, hp.CurrentValue); + } + + [Fact] + public async Task CreateCharacterFromTemplateIdAsync_CopiesTemplate() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + var attr = new Attributes { Name = "HP", Description = "Health" }; + var skill = new Skill { Name = "Sword", Description = "Sword skill", Category = "Combat", BaseSuccessRate = 50, IsBasic = true, IsActive = true, DisplayOrder = 1, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; + var item = new Item { Name = "Potion", Description = "Heals HP", Category = "Consumable", Stats = "Heal:50", OwnerNotes = "", Weight = 0.1M, IsConsumable = true, IsCursed = false, IsActive = true, DisplayOrder = 1, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; + context.Attributes.Add(attr); + context.Skills.Add(skill); + context.Items.Add(item); + await context.SaveChangesAsync(); + var template = new PlayerCharacter { Name = "Template", Gender = "F", Age = 25, IsTemplate = true, IsActive = true, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; + context.PlayerCharacters.Add(template); + await context.SaveChangesAsync(); + var charAttr = new CharacterAttribute { CharacterId = template.Id, AttributeId = attr.Id, MaxValue = 100, CurrentValue = 100 }; + var charSkill = new CharacterSkill { CharacterId = template.Id, SkillId = skill.Id, Proficiency = 80 }; + var charItem = new CharacterItem { CharacterId = template.Id, ItemId = item.Id, Quantity = 2 }; + context.CharacterAttributes.Add(charAttr); + context.CharacterSkills.Add(charSkill); + context.CharacterItems.Add(charItem); + await context.SaveChangesAsync(); + + // Act + var copied = await service.CreateCharacterFromTemplateIdAsync(template.Id); + + // Assert + Assert.NotNull(copied); + Assert.Equal("Template", copied.Name); + Assert.False(copied.IsTemplate); + Assert.Single(copied.CharacterAttributes); + Assert.Single(copied.CharacterSkills); + Assert.Single(copied.CharacterItems); + } + + [Fact] + public async Task UpdateCharacterAsync_ReturnsNull_WhenNotFound() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + var update = new PlayerCharacterRequest { Name = "X", Gender = "F", Age = 1, IsActive = false }; + + // Act + var result = await service.UpdateCharacterAsync(999, update); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task UpdateCharacterAttributeAsync_ReturnsNull_WhenNotFound() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + + // Act + var result = await service.UpdateCharacterAttributeAsync(999, "NotExist", 10); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task DeleteCharacterAsync_ReturnsFalse_WhenNotFound() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + + // Act + var result = await service.DeleteCharacterAsync(999); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task CreateCharacterFromTemplateIdAsync_ReturnsNull_WhenNotFound() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CharacterService(context); + + // Act + var result = await service.CreateCharacterFromTemplateIdAsync(999); + + // Assert + Assert.Null(result); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/Game.Test/Tests/CheckTest.cs b/src/UnitTests/Game.Test/Tests/CheckTest.cs new file mode 100644 index 0000000..f4e6c9c --- /dev/null +++ b/src/UnitTests/Game.Test/Tests/CheckTest.cs @@ -0,0 +1,270 @@ +using Game.Service.Data; +using Game.Service.Data.Models; +using Game.Service.Services; +using Microsoft.EntityFrameworkCore; +namespace Game.Test.Tests +{ + public class CheckTest + { + public CheckTest() + { + + } + [Fact] + public async Task RollDiceAsync_ReturnsValue() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + var result = await service.RollDiceAsync("2d6"); + Assert.InRange(result, 2, 12); + } + + [Fact] + public async Task RollDiceAsync_Throws_OnInvalidFormat() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + await Assert.ThrowsAsync(() => service.RollDiceAsync("badformat")); + } + + [Fact] + public async Task SanityCheckAsync_NoAttribute_ReturnsMessage() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + var msg = await service.SanityCheckAsync(1, "1d6"); + Assert.Contains("No sanity attribute", msg); + } + + [Fact] + public async Task SanityCheckAsync_WithAttribute_ReturnsResult() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + + // Arrange + var player = new PlayerCharacter { Id = 1, Name = "Test" }; + context.PlayerCharacters.Add(player); + var attr = new Attributes { Name = "Sanity", Description = "San" }; + context.Attributes.Add(attr); + await context.SaveChangesAsync(); + var charAttr = new CharacterAttribute { CharacterId = 1, AttributeId = attr.Id, MaxValue = 100, CurrentValue = 50 }; + context.CharacterAttributes.Add(charAttr); + await context.SaveChangesAsync(); + + var msg = await service.SanityCheckAsync(1, "1d6"); + Assert.Contains("Roll:", msg); + Assert.Contains("Threshold: 50", msg); + } + + [Fact] + public async Task AttributeCheckAsync_ByName_And_ById() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + + // Arrange + var player = new PlayerCharacter { Id = 1, Name = "Test" }; + context.PlayerCharacters.Add(player); + var attr = new Attributes { Name = "HP", Description = "Health" }; + context.Attributes.Add(attr); + await context.SaveChangesAsync(); + var charAttr = new CharacterAttribute { CharacterId = 1, AttributeId = attr.Id, MaxValue = 100, CurrentValue = 60 }; + context.CharacterAttributes.Add(charAttr); + await context.SaveChangesAsync(); + + var msgByName = await service.AttributeCheckAsync(1, "HP", "1d6"); + Assert.Contains("Roll:", msgByName); + Assert.Contains("Threshold: 60", msgByName); + + var msgById = await service.AttributeCheckAsync(1, attr.Id.ToString(), "1d6"); + Assert.Contains("Roll:", msgById); + Assert.Contains("Threshold: 60", msgById); + } + + [Fact] + public async Task AttributeCheckAsync_NotFound() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + var msg = await service.AttributeCheckAsync(1, "STR", "1d6"); + Assert.Contains("not found", msg); + } + + [Fact] + public async Task SkillCheckAsync_ByName_And_ById() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + + // Arrange + var player = new PlayerCharacter { Id = 1, Name = "Test" }; + context.PlayerCharacters.Add(player); + var skill = new Skill { Name = "Sword", Description = "Sword skill", BaseSuccessRate = 30, IsBasic = true, IsActive = true, DisplayOrder = 1, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; + context.Skills.Add(skill); + await context.SaveChangesAsync(); + var charSkill = new CharacterSkill { CharacterId = 1, SkillId = skill.Id, Proficiency = 20 }; + context.CharacterSkills.Add(charSkill); + await context.SaveChangesAsync(); + + var msgByName = await service.SkillCheckAsync(1, "Sword", "1d6"); + Assert.Contains("Roll:", msgByName); + Assert.Contains("Target: 50", msgByName); + + var msgById = await service.SkillCheckAsync(1, skill.Id.ToString(), "1d6"); + Assert.Contains("Roll:", msgById); + Assert.Contains("Target: 50", msgById); + } + + [Fact] + public async Task SkillCheckAsync_NotFound() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + var msg = await service.SkillCheckAsync(1, "Magic", "1d6"); + Assert.Contains("not found", msg); + } + + [Fact] + public async Task SavingThrowAsync_DelegatesToAttributeCheck() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + + // Arrange + var player = new PlayerCharacter { Id = 1, Name = "Test" }; + context.PlayerCharacters.Add(player); + var attr = new Attributes { Name = "Luck", Description = "Luck" }; + context.Attributes.Add(attr); + await context.SaveChangesAsync(); + var charAttr = new CharacterAttribute { CharacterId = 1, AttributeId = attr.Id, MaxValue = 100, CurrentValue = 70 }; + context.CharacterAttributes.Add(charAttr); + await context.SaveChangesAsync(); + + var msg = await service.SavingThrowAsync(1, "Luck", "1d6"); + Assert.Contains("Roll:", msg); + Assert.Contains("Threshold: 70", msg); + } + + [Fact] + public async Task CalculateDamageAsync_UsesItemStats() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + var item = new Item { Name = "Sword", Description = "Sword", Stats = "2d6", Category = "Weapon", OwnerNotes = "", Weight = 1, IsConsumable = false, IsCursed = false, IsActive = true, DisplayOrder = 1, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; + context.Items.Add(item); + await context.SaveChangesAsync(); + + var msg = await service.CalculateDamageAsync(1, "Sword", "1d4"); + Assert.Contains("Damage:", msg); + Assert.Contains("dice: 2d6", msg); + } + + [Fact] + public async Task CalculateDamageAsync_UsesRollExpression() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + var msg = await service.CalculateDamageAsync(1, "Unknown", "1d4"); + Assert.Contains("Damage:", msg); + Assert.Contains("dice: 1d4", msg); + } + + [Fact] + public async Task AutoRollPlayerAttributeAsync_AssignsAttributes() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new CheckService(context); + + // Arrange + var player = new PlayerCharacter { Id = 1, Name = "Test" }; + context.PlayerCharacters.Add(player); + context.Attributes.Add(new Attributes { Name = "POW", Description = "Power" }); + context.Attributes.Add(new Attributes { Name = "SAN", Description = "Sanity" }); + context.Attributes.Add(new Attributes { Name = "SIZ", Description = "Size" }); + context.Attributes.Add(new Attributes { Name = "INT", Description = "Intelligence" }); + context.Attributes.Add(new Attributes { Name = "EDU", Description = "Education" }); + context.Attributes.Add(new Attributes { Name = "STR", Description = "Strength" }); + await context.SaveChangesAsync(); + + // Act + var msg = await service.AutoRollPlayerAttributeAsync(1); + + // Assert + Assert.Equal("Attributes rolled and assigned successfully.", msg); + var attrs = context.CharacterAttributes.Where(a => a.CharacterId == 1).ToList(); + Assert.Equal(6, attrs.Count); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/Game.Test/Tests/KPTest.cs b/src/UnitTests/Game.Test/Tests/KPTest.cs new file mode 100644 index 0000000..bebf229 --- /dev/null +++ b/src/UnitTests/Game.Test/Tests/KPTest.cs @@ -0,0 +1,226 @@ +using Game.Service.Data; +using Game.Service.Data.Models; +using Game.Service.Services; +using Microsoft.EntityFrameworkCore; + +namespace Game.Test.Tests +{ + public class KPTest + { + [Fact] + public async Task GenerateSceneDescriptionAsync_ReturnsDescription_WhenSceneExists() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Id = 1, Name = "Scenario1", Description = "S1" }); + context.SaveChanges(); + context.Scenes.Add(new Scene { Id = 1, Name = "TestScene", Description = "Desc", ScenarioId = 1 }); + await context.SaveChangesAsync(); + var service = new KPService(context); + + // Act + var result = await service.GenerateSceneDescriptionAsync(1); + + // Assert + Assert.Contains("Desc", result); + } + + [Fact] + public async Task GenerateNpcDialogueAsync_ReturnsNotFound_WhenNoNpc() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new KPService(context); + + // Act + var result = await service.GenerateNpcDialogueAsync(999); + + // Assert + Assert.Contains("No NPCs found", result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GenerateNpcDialogueAsync_ReturnsDialogue_WhenNpcExists() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Id = 1, Name = "Scenario1", Description = "S1" }); + context.SaveChanges(); + context.Scenes.Add(new Scene { Id = 1, Name = "TestScene", Description = "Desc", ScenarioId = 1 }); + context.SaveChanges(); + context.NonPlayerCharacters.Add(new NonPlayerCharacter { Id = 1, Name = "NPC", Role = "Villager", LastKnownSceneId = 1, IsActive = true }); + await context.SaveChangesAsync(); + var service = new KPService(context); + + // Act + var result = await service.GenerateNpcDialogueAsync(1); + + // Assert + Assert.Contains("NPC", result); + } + + [Fact] + public async Task GenerateRandomEventAsync_ReturnsEvent_WhenExists() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Id = 1, Name = "Scenario1", Description = "S1" }); + context.SaveChanges(); + context.Scenes.Add(new Scene { Id = 1, Name = "TestScene", Description = "Desc", ScenarioId = 1 }); + context.SaveChanges(); + context.EventIntensities.Add(new EventIntensity { Id = 1, Name = "Normal", Description = "Normal intensity" }); + context.SaveChanges(); + context.RandomEvents.Add(new RandomEvent { Id = 1, SceneId = 1, Description = "Random event!", EventIntensityId = 1 }); + await context.SaveChangesAsync(); + var service = new KPService(context); + + // Act + var result = await service.GenerateRandomEventAsync(1); + + // Assert + Assert.Contains("No available random events", result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task SuggestChecksAndDifficultiesAsync_ReturnsNotFound_WhenNoScene() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new KPService(context); + + // Act + var result = await service.SuggestChecksAndDifficultiesAsync(999); + + // Assert + Assert.Contains("No roll suggestions", result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task SuggestChecksAndDifficultiesAsync_ReturnsSuggestion_WhenSceneExists() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Id = 1, Name = "Scenario1", Description = "S1" }); + context.SaveChanges(); + context.Scenes.Add(new Scene { Id = 2, Name = "Scene2", Description = "Desc2", ScenarioId = 1 }); + await context.SaveChangesAsync(); + var service = new KPService(context); + + // Act + var result = await service.SuggestChecksAndDifficultiesAsync(2); + + // Assert + Assert.Contains("No roll suggestions", result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GetGameProgressSuggestionsAsync_ReturnsSuggestion_WhenRecordsExist() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Id = 1, Name = "Scenario1", Description = "S1" }); + context.SaveChanges(); + context.GameRecords.Add(new GameRecords { Id = 1, ScenarioId = 1, Description = "Progress!", CreatedAt = DateTime.UtcNow }); + await context.SaveChangesAsync(); + var service = new KPService(context); + + // Act + var result = await service.GetGameProgressSuggestionsAsync(1); + + // Assert + Assert.Contains("Progress", result); + } + + [Fact] + public async Task GenerateSceneDescriptionAsync_ReturnsNotFound_WhenNoScene() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new KPService(context); + + // Act + var result = await service.GenerateSceneDescriptionAsync(999); + + // Assert + Assert.Contains("not found", result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GenerateRandomEventAsync_ReturnsNotFound_WhenNoScene() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new KPService(context); + + // Act + var result = await service.GenerateRandomEventAsync(999); + + // Assert + Assert.Contains("not found", result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task GetGameProgressSuggestionsAsync_ReturnsNoRecords() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new KPService(context); + + // Act + var result = await service.GetGameProgressSuggestionsAsync(999); + + // Assert + Assert.Contains("no recent game records", result, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/UnitTests/Game.Test/Tests/SenarioTest.cs b/src/UnitTests/Game.Test/Tests/SenarioTest.cs new file mode 100644 index 0000000..95f97f7 --- /dev/null +++ b/src/UnitTests/Game.Test/Tests/SenarioTest.cs @@ -0,0 +1,74 @@ +using Game.Service.Data; +using Game.Service.Data.Models; +using Game.Service.Services; +using Microsoft.EntityFrameworkCore; + +namespace Game.Test.Tests +{ + public class SenarioTest + { + [Fact] + public async Task GetScenarioByIdAsync_ReturnsScenario_WhenExists() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Id = 2, Name = "S2", Description = "D2", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }); + await context.SaveChangesAsync(); + var service = new ScenarioService(context); + + // Act + var result = await service.GetScenarioByIdAsync(2); + + // Assert + Assert.NotNull(result); + Assert.Equal("S2", result.Name); + } + + [Fact] + public async Task GetAllScenariosAsync_ReturnsScenarios() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + context.Scenarios.Add(new Scenario { Name = "S1", Description = "D1", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }); + await context.SaveChangesAsync(); + var service = new ScenarioService(context); + + // Act + var result = await service.GetAllScenariosAsync(); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal("S1", result[0]?.Name); + } + + [Fact] + public async Task GetScenarioByIdAsync_ReturnsNull_WhenNotFound() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + using var context = new TrpgDbContext(options); + context.Database.OpenConnection(); + context.Database.EnsureCreated(); + var service = new ScenarioService(context); + + // Act + var result = await service.GetScenarioByIdAsync(999); + + // Assert + Assert.Null(result); + } + } +} diff --git a/trpg-mcp.sln b/trpg-mcp.sln index 9690d54..7a535b7 100644 --- a/trpg-mcp.sln +++ b/trpg-mcp.sln @@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game.Service", "src\Modules EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "src\Common\Common.csproj", "{D4CDE262-F9A1-44C9-9681-E6AD04AE139C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{424A6336-F132-E78C-9D5B-EA9A8E793A9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game.Test", "src\UnitTests\Game.Test\Game.Test.csproj", "{8B5E5565-561D-4371-84FC-51FA037599A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +63,18 @@ Global {D4CDE262-F9A1-44C9-9681-E6AD04AE139C}.Release|x64.Build.0 = Release|Any CPU {D4CDE262-F9A1-44C9-9681-E6AD04AE139C}.Release|x86.ActiveCfg = Release|Any CPU {D4CDE262-F9A1-44C9-9681-E6AD04AE139C}.Release|x86.Build.0 = Release|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Debug|x64.Build.0 = Debug|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Debug|x86.Build.0 = Debug|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Release|Any CPU.Build.0 = Release|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Release|x64.ActiveCfg = Release|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Release|x64.Build.0 = Release|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Release|x86.ActiveCfg = Release|Any CPU + {8B5E5565-561D-4371-84FC-51FA037599A2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -68,5 +84,7 @@ Global {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {50E1E429-B4CD-4014-AA53-943F60188088} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} {D4CDE262-F9A1-44C9-9681-E6AD04AE139C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {424A6336-F132-E78C-9D5B-EA9A8E793A9C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {8B5E5565-561D-4371-84FC-51FA037599A2} = {424A6336-F132-E78C-9D5B-EA9A8E793A9C} EndGlobalSection EndGlobal