Files
PolyTraderSharp/tests/PolyTrader.Tests/DbContextModelTests.cs
T
RichardandClaude Opus 4.8 38f609ed31 CopyTrading Slice 1: Settings-Fundament + detaillierte Erklärungen
Umsetzung des Fable-Plans, Start mit dem risikoarmen Settings-Fundament
(kein Hot-Path/CLOB-Verhalten geändert):

- Alle Copytrading-Settings (alt + neu) haben jetzt [Description]/[DisplayName] ->
  im PropertyGrid als Erklärung sichtbar (Richards Vorgabe). Test erzwingt, dass
  jede einstellbare Option eine Beschreibung trägt.
- ProfitTarget-Default -> 9999 (praktisch deaktiviert); Migration setzt zusätzlich
  bestehende Zeilen per SQL auf 9999 (Entscheidung 2). Take-Profit-LOGIK folgt.
- Neue Account-Settings (noch nicht verdrahtet, für Phase 0.1/1.3/2):
  SellFloorPct (15), MaxSpreadPct (5), MinSellRatioPct (10).
- Neue Master-Trader-Settings: AutoPauseEnabled (Default AN, per Master abschaltbar,
  Entscheidung 3) + MakerEntry (Default aus, Entscheidung 4).
- EF-Migration AddCopytradingBehaviorSettings (neue Spalten + sinnvolle Defaults
  fuer Bestandszeilen) erstellt und auf MySQL angewendet.
- Erklärung dokumentiert einen gefundenen Skalen-Verdacht: PreRedeemLimit wird
  gegen den 0-1-Preis verglichen; Alt-Werte 99.5 triggern nie (-> ggf. 0.995).

Tests: 86 gruen. Build/Smoke gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 09:44:50 +02:00

138 lines
5.7 KiB
C#

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
{
/// <summary>
/// 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.
/// </summary>
public class DbContextModelTests
{
private static CoreDbContext CoreCtx() =>
new InMemoryContextFactory<CoreDbContext>(o => new CoreDbContext(o)).CreateDbContext();
private static CopyTradingDbContext CtCtx() =>
new InMemoryContextFactory<CopyTradingDbContext>(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))]
[InlineData(nameof(CopyTradingAccountSettings.SellFloorPct))]
[InlineData(nameof(CopyTradingAccountSettings.MaxSpreadPct))]
[InlineData(nameof(CopyTradingAccountSettings.MinSellRatioPct))]
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);
}
}
}