using System; using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using PolyTrader.Core.Persistence.Ef; using PolyTrader.Modules.CopyTrading.Persistence.Ef; using PolyTrader.Tests.TestSupport; using PolyTraderSharp.Models; using Xunit; namespace PolyTrader.Tests { /// /// Feinkörnige Modell-Invarianten (Randfälle des Mappings): Dezimal-Präzision auf /// Geldfeldern, MaxLength auf Strings, manuell gesetzte Keys (ValueGeneratedNever), /// Value-Converter/Spaltentyp der JSON-Felder, Indizes. /// public class DbContextModelTests { private static CoreDbContext CoreCtx() => new InMemoryContextFactory(o => new CoreDbContext(o)).CreateDbContext(); private static CopyTradingDbContext CtCtx() => new InMemoryContextFactory(o => new CopyTradingDbContext(o)).CreateDbContext(); private static IProperty Prop(DbContext ctx, Type clr, string name) => ctx.Model.FindEntityType(clr)!.FindProperty(name)!; // GetColumnType() wirft unter dem InMemory-Provider (kein RelationalTypeMapping); // die HasColumnType-Annotation lässt sich aber providerunabhängig auslesen. private static string? ColumnType(IProperty p) => p.FindAnnotation("Relational:ColumnType")?.Value as string; [Theory] [InlineData(typeof(AccountState), nameof(AccountState.TotalBalance))] [InlineData(typeof(AccountState), nameof(AccountState.AvailableBalance))] [InlineData(typeof(Position), nameof(Position.EntryPrice))] [InlineData(typeof(TradeRecord), nameof(TradeRecord.RealizedPnl))] public void Core_money_fields_have_precision_18_6(Type clr, string name) { using var ctx = CoreCtx(); var p = Prop(ctx, clr, name); Assert.Equal(18, p.GetPrecision()); Assert.Equal(6, p.GetScale()); } [Theory] [InlineData(nameof(CopyTradingAccountSettings.PerMarketLimit))] [InlineData(nameof(CopyTradingAccountSettings.PerMasterLimit))] [InlineData(nameof(CopyTradingAccountSettings.perMaxTimeNone))] public void Settings_money_fields_have_precision_18_6(string name) { using var ctx = CtCtx(); var p = Prop(ctx, typeof(CopyTradingAccountSettings), name); Assert.Equal(18, p.GetPrecision()); Assert.Equal(6, p.GetScale()); } [Theory] [InlineData(typeof(AccountState), nameof(AccountState.WalletAddress), 128)] [InlineData(typeof(AccountState), nameof(AccountState.Name), 200)] [InlineData(typeof(MarketData), nameof(MarketData.Id), 120)] public void Core_string_fields_have_expected_max_length(Type clr, string name, int expected) { using var ctx = CoreCtx(); Assert.Equal(expected, Prop(ctx, clr, name).GetMaxLength()); } [Fact] public void TrackedTrader_category_has_max_length_64() { using var ctx = CtCtx(); Assert.Equal(64, Prop(ctx, typeof(TrackedTrader), nameof(TrackedTrader.Category)).GetMaxLength()); } [Theory] [InlineData(typeof(AccountState), nameof(AccountState.AccountId))] public void Core_manual_keys_are_not_store_generated(Type clr, string name) { using var ctx = CoreCtx(); Assert.Equal(ValueGenerated.Never, Prop(ctx, clr, name).ValueGenerated); } [Theory] [InlineData(typeof(TrackedTrader), nameof(TrackedTrader.Id))] [InlineData(typeof(ClosedTrade), nameof(ClosedTrade.TradeId))] [InlineData(typeof(CopyTradingAccountSettings), nameof(CopyTradingAccountSettings.AccountId))] public void Module_manual_keys_are_not_store_generated(Type clr, string name) { using var ctx = CtCtx(); Assert.Equal(ValueGenerated.Never, Prop(ctx, clr, name).ValueGenerated); } [Fact] public void AssignedAccountIds_has_value_converter_and_text_column() { using var ctx = CtCtx(); var p = Prop(ctx, typeof(TrackedTrader), nameof(TrackedTrader.AssignedAccountIds)); Assert.NotNull(p.GetValueConverter()); Assert.Equal("text", ColumnType(p)); } [Theory] [InlineData(nameof(MarketData.ClobTokenIds))] [InlineData(nameof(MarketData.Outcomes))] public void Market_json_columns_are_text(string name) { using var ctx = CoreCtx(); Assert.Equal("text", ColumnType(Prop(ctx, typeof(MarketData), name))); } [Fact] public void TradeRecord_is_indexed_on_closed_at() { using var ctx = CoreCtx(); var indexed = ctx.Model.FindEntityType(typeof(TradeRecord))! .GetIndexes() .SelectMany(i => i.Properties.Select(p => p.Name)); Assert.Contains(nameof(TradeRecord.ClosedAt), indexed); } [Fact] public void MasterTraderHistory_is_indexed_on_trader_and_closed_at() { using var ctx = CtCtx(); var indexed = ctx.Model.FindEntityType(typeof(MasterTraderHistoryRecord))! .GetIndexes() .SelectMany(i => i.Properties.Select(p => p.Name)) .ToList(); Assert.Contains(nameof(MasterTraderHistoryRecord.TraderId), indexed); Assert.Contains(nameof(MasterTraderHistoryRecord.ClosedAt), indexed); } } }