diff --git a/src/Persistence/BasicModel/GameConfiguration.Generated.cs b/src/Persistence/BasicModel/GameConfiguration.Generated.cs index 9460cd4..9562054 100644 --- a/src/Persistence/BasicModel/GameConfiguration.Generated.cs +++ b/src/Persistence/BasicModel/GameConfiguration.Generated.cs @@ -484,6 +484,24 @@ public partial class GameConfiguration : MUnique.OpenMU.DataModel.Configuration. set => base.DuelConfiguration = value; } + /// + /// Gets the raw object of . + /// + [System.Text.Json.Serialization.JsonPropertyName("castleSiegeConfiguration")] + public CastleSiegeConfiguration RawCastleSiegeConfiguration + { + get => base.CastleSiegeConfiguration as CastleSiegeConfiguration; + set => base.CastleSiegeConfiguration = value; + } + + /// + [System.Text.Json.Serialization.JsonIgnore] + public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration CastleSiegeConfiguration + { + get => base.CastleSiegeConfiguration; + set => base.CastleSiegeConfiguration = value; + } + /// public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration) { diff --git a/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj b/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj index 4d32df0..1711a6d 100644 --- a/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj +++ b/src/Persistence/EntityFramework/MUnique.OpenMU.Persistence.EntityFramework.csproj @@ -48,8 +48,15 @@ + - + diff --git a/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs b/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs index cd714d0..8962b54 100644 --- a/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs +++ b/src/Persistence/EntityFramework/Model/ExtendedTypeContext.Generated.cs @@ -27,6 +27,9 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); @@ -41,6 +44,11 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); @@ -118,6 +126,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext modelBuilder.Entity().HasMany(entity => entity.RawCharacters).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawEquippedItems).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawNpcStates).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawAttributes).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawLetters).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawLearnedSkills).WithOne().OnDelete(DeleteBehavior.Cascade); @@ -131,6 +140,17 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext modelBuilder.Entity().HasOne(entity => entity.RawLeftGoal).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasOne(entity => entity.RawRightGoal).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasOne(entity => entity.RawMagicEffectDefinition).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawStateSchedule).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawNpcDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawGateDefenseUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawGateLifeUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawStatueDefenseUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawStatueLifeUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawStatueRegenUpgrades).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawAttackMachineZones).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(entity => entity.RawDefenseMachineZones).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(entity => entity.RawDefenseRespawnArea).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(entity => entity.RawAttackRespawnArea).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawStatAttributes).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawAttributeCombinations).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade); @@ -159,6 +179,7 @@ public class ExtendedTypeContext : Microsoft.EntityFrameworkCore.DbContext modelBuilder.Entity().HasMany(entity => entity.RawGlobalBaseAttributeValues).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawPlugInConfigurations).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawMiniGameDefinitions).WithOne().OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(entity => entity.RawCastleSiegeConfiguration).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawMonsterSpawns).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(entity => entity.RawEnterGates).WithOne().OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasOne(entity => entity.RawBattleZone).WithOne().OnDelete(DeleteBehavior.Cascade); diff --git a/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs b/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs index ec2d587..525e1eb 100644 --- a/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs +++ b/src/Persistence/EntityFramework/Model/GameConfiguration.Generated.cs @@ -243,6 +243,32 @@ internal partial class GameConfiguration : MUnique.OpenMU.DataModel.Configuratio } } + /// + /// Gets or sets the identifier of . + /// + public Guid? CastleSiegeConfigurationId { get; set; } + + /// + /// Gets the raw object of . + /// + [ForeignKey(nameof(CastleSiegeConfigurationId))] + public CastleSiegeConfiguration RawCastleSiegeConfiguration + { + get => base.CastleSiegeConfiguration as CastleSiegeConfiguration; + set => base.CastleSiegeConfiguration = value; + } + + /// + [NotMapped] + public override MUnique.OpenMU.DataModel.Configuration.CastleSiegeConfiguration CastleSiegeConfiguration + { + get => base.CastleSiegeConfiguration;set + { + base.CastleSiegeConfiguration = value; + this.CastleSiegeConfigurationId = this.RawCastleSiegeConfiguration?.Id; + } + } + /// public override MUnique.OpenMU.DataModel.Configuration.GameConfiguration Clone(MUnique.OpenMU.DataModel.Configuration.GameConfiguration gameConfiguration) { diff --git a/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs b/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs index 589e0a3..49d3cb3 100644 --- a/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs +++ b/src/Persistence/EntityFramework/Model/MapsterConfigurator.Generated.cs @@ -44,6 +44,15 @@ public static class MapsterConfigurator Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() .Include(); + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() .Include(); @@ -86,6 +95,21 @@ public static class MapsterConfigurator Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() .Include(); + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() + .Include(); + Mapster.TypeAdapterConfig.GlobalSettings.NewConfig() .Include(); diff --git a/src/Persistence/MUnique.OpenMU.Persistence.csproj b/src/Persistence/MUnique.OpenMU.Persistence.csproj index 3763d8f..2a2d4d2 100644 --- a/src/Persistence/MUnique.OpenMU.Persistence.csproj +++ b/src/Persistence/MUnique.OpenMU.Persistence.csproj @@ -41,8 +41,15 @@ + - + diff --git a/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs b/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs index 06f67a4..d447393 100644 --- a/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs +++ b/tests/MUnique.OpenMU.Network.Packets.Tests/ClientToServerPacketTests.cs @@ -3904,6 +3904,24 @@ public class PacketStructureTests "Packet length mismatch: declared length does not match calculated size"); } + /// + /// Tests the packet size calculation for HeykelSavasiTeamSelect. + /// + [Test] + public void HeykelSavasiTeamSelect_PacketSizeValidation() + { + // Fixed-length packet validation + const int expectedLength = 4; + var actualLength = HeykelSavasiTeamSelectRef.Length; + + Assert.That(actualLength, Is.EqualTo(expectedLength), + "Packet length mismatch: declared length does not match calculated size"); + + // Validate field 'Team' boundary + Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'Team' exceeds packet boundary"); + } + /// /// Tests the packet size calculation for ChatCommandListRequest. /// diff --git a/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs b/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs index 42a5689..31b3147 100644 --- a/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs +++ b/tests/MUnique.OpenMU.Network.Packets.Tests/ServerToClientPacketTests.cs @@ -6415,10 +6415,102 @@ public class PacketStructureTests } /// - /// Tests the packet size calculation for ChatCommandInfo. + /// Tests the packet size calculation for HeykelSavasiOpenTeamPanel. /// [Test] - public void ChatCommandInfo_PacketSizeValidation() + public void HeykelSavasiOpenTeamPanel_PacketSizeValidation() + { + // Fixed-length packet validation + const int expectedLength = 5; + var actualLength = HeykelSavasiOpenTeamPanelRef.Length; + + Assert.That(actualLength, Is.EqualTo(expectedLength), + "Packet length mismatch: declared length does not match calculated size"); + + // Validate field 'RedCount' boundary + Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'RedCount' exceeds packet boundary"); + + // Validate field 'BlueCount' boundary + Assert.That(4 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'BlueCount' exceeds packet boundary"); + } + + /// + /// Tests the packet size calculation for HeykelSavasiHudState. + /// + [Test] + public void HeykelSavasiHudState_PacketSizeValidation() + { + // Fixed-length packet validation + const int expectedLength = 11; + var actualLength = HeykelSavasiHudStateRef.Length; + + Assert.That(actualLength, Is.EqualTo(expectedLength), + "Packet length mismatch: declared length does not match calculated size"); + + // Validate field 'Phase' boundary + Assert.That(3 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'Phase' exceeds packet boundary"); + + // Validate field 'MyTeam' boundary + Assert.That(4 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'MyTeam' exceeds packet boundary"); + + // Validate field 'RedCount' boundary + Assert.That(5 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'RedCount' exceeds packet boundary"); + + // Validate field 'BlueCount' boundary + Assert.That(6 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'BlueCount' exceeds packet boundary"); + + // Validate field 'RedProgress' boundary + Assert.That(7 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'RedProgress' exceeds packet boundary"); + + // Validate field 'BlueProgress' boundary + Assert.That(8 + 1, Is.LessThanOrEqualTo(expectedLength), + "Field 'BlueProgress' exceeds packet boundary"); + + // Validate field 'RemainingSeconds' boundary + Assert.That(9 + 2, Is.LessThanOrEqualTo(expectedLength), + "Field 'RemainingSeconds' exceeds packet boundary"); + } + + /// + /// Tests the packet size calculation for HeykelSavasiTeamRoster. + /// + [Test] + public void HeykelSavasiTeamRoster_PacketSizeValidation() + { + // Basic packet validation + // Validate header type and field boundaries + + // Field 'Count' starts at index 3 with size 1 + Assert.That(3, Is.GreaterThanOrEqualTo(0), + "Field 'Count' has invalid negative index"); + } + + /// + /// Tests the packet size calculation for HeykelSavasiScoreboard. + /// + [Test] + public void HeykelSavasiScoreboard_PacketSizeValidation() + { + // Basic packet validation + // Validate header type and field boundaries + + // Field 'Count' starts at index 3 with size 1 + Assert.That(3, Is.GreaterThanOrEqualTo(0), + "Field 'Count' has invalid negative index"); + } + + /// + /// Tests the packet size calculation for AvailableChatCommand. + /// + [Test] + public void AvailableChatCommand_PacketSizeValidation() { // Basic packet validation // Validate header type and field boundaries diff --git a/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs new file mode 100644 index 0000000..e16b7b1 --- /dev/null +++ b/tests/MUnique.OpenMU.Persistence.Initialization.Tests/TypedContextModelTests.cs @@ -0,0 +1,56 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Tests; + +using Microsoft.Extensions.Logging.Abstractions; +using MUnique.OpenMU.Persistence.EntityFramework; +using MUnique.OpenMU.PlugIns; + +/// +/// Tests that the typed contexts can build their entity model. A typed context keeps only the edited type +/// (plus its aggregate) and ignores every other type, so a type which is only mapped in the full context +/// slips through the build and blows up at runtime instead. The startup reads the plugin configurations +/// through such a context before anything else, so a broken model there means the server doesn't start. +/// No database is needed: building the model already runs the EF model validation. +/// +[TestFixture] +internal class TypedContextModelTests +{ + /// + /// Builds the model of the typed context which the startup uses to read the plugin configurations. + /// A failing connection is fine here (there may be no database); a failing model is not. + /// + [Test] + public void PlugInConfigurationContextBuildsModel() + { + var provider = new PersistenceContextProvider(new NullLoggerFactory(), null); + using var context = provider.CreateNewTypedContext(typeof(PlugInConfiguration), false); + + try + { + _ = context.GetAsync().AsTask().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + AssertNoModelError(ex); + } + } + + private static void AssertNoModelError(Exception exception) + { + for (var ex = exception; ex is not null; ex = ex.InnerException!) + { + if (ex is InvalidOperationException && ex.Message.Contains("requires a primary key")) + { + Assert.Fail($"The entity model of the typed context is broken: {ex.Message}"); + } + + if (ex.InnerException is null) + { + break; + } + } + } +}