using System.Globalization;
using FluentAssertions;
using IBKRTrader.Core.Analytics;
using IBKRTrader.Core.Persistence.Entities;
using IBKRTrader.Core.Trading;
namespace IBKRTrader.Tests.Trading;
///
/// Zahlen und Zeitpunkte, die in Logs, Dossiers oder in die Datenbank gehen, dürfen nicht an der
/// Kultur des Hosts hängen. Wir betreiben Instanzen in EU und US und vergleichen deren Ausgaben
/// miteinander – "373,67" auf der einen und "373.67" auf der anderen wäre dasselbe Ereignis in
/// zwei Schreibweisen.
///
/// Die Tests setzen bewusst eine Kultur mit Dezimalkomma und prüfen, dass sich davon nichts
/// beeindrucken lässt. Der laufende Prozess stellt zusätzlich die invariante Kultur ein
/// (AppHostBuilder), aber genau darauf soll sich der einzelne Codepfad nicht verlassen.
///
[Trait("cat", "unit")]
public class CultureIndependenceTests
{
/// Führt die Prüfung unter einer Kultur mit Dezimalkomma aus.
private static void UnderGermanCulture(Action assert)
{
var previous = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
assert();
}
finally
{
CultureInfo.CurrentCulture = previous;
}
}
private static BrokerPosition Position() => new()
{
Symbol = "GOOG",
SecType = "STK",
Currency = "USD",
ConId = 208813720,
Quantity = 10,
AvgCost = 373.67m
};
[Fact]
public void BrokerPosition_ToString_UsesInvariantNumbers()
{
UnderGermanCulture(() =>
Position().ToString().Should().Be("GOOG (STK) 10 @ 373.67 USD"));
}
[Fact]
public void BrokerExecution_ToString_UsesInvariantNumbers()
{
var execution = new BrokerExecution
{
ExecId = "0001",
Time = new DateTime(2026, 9, 3, 18, 26, 13, DateTimeKind.Utc),
Symbol = "AAPL",
SecType = "STK",
Side = TradeSide.Buy,
Quantity = 5,
Price = 327.27m
};
UnderGermanCulture(() =>
execution.ToString().Should().Be("2026-09-03 18:26:13 Buy 5x AAPL @ 327.27"));
}
[Fact]
public void Dossier_WritesInvariantNumbers()
{
var dossier = new TradeDossier(
SignalId: "sig-1",
Decisions: [],
OrderEvents: [],
Trades:
[
new CoreTrade
{
Module = "CT",
Symbol = "AAPL",
Action = "BUY",
Quantity = 3,
Price = 327.27m,
TotalValue = 981.81m,
Status = "Filled",
TradedAt = new DateTime(2026, 9, 3, 18, 26, 13, DateTimeKind.Utc)
}
],
LogLines: []);
UnderGermanCulture(() =>
{
var markdown = DossierBuilder.ToMarkdown(dossier);
markdown.Should().Contain("327.27").And.Contain("981.81");
markdown.Should().NotContain("327,27");
});
}
// ─── Dateinamen (#489) ────────────────────────────────────────────────
///
/// Führt die Prüfung unter einer Kultur mit anderem Kalender aus. Das Dezimalkomma ist
/// nur die halbe Gefahr: fa-IR rechnet im Sonnen-Hidschra-Kalender, th-TH im buddhistischen.
///
private static void UnderCulture(string name, Action assert)
{
var previous = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo(name);
assert();
}
finally
{
CultureInfo.CurrentCulture = previous;
}
}
[Theory]
[InlineData("fa-IR")] // Sonnen-Hidschra: Jahr 1405 statt 2026
[InlineData("th-TH")] // buddhistisch: Jahr 2569 statt 2026
[InlineData("de-DE")]
public void LogFileNames_DoNotDependOnTheHostCalendar(string culture)
{
// Am 2026-09-06 lagen auf der Test-VM neben 2026-09-06.jsonl auch 1405-06-12.jsonl und
// 2569-09-03.jsonl. Eine interpolierte Zeichenkette formatiert mit CurrentCulture, und die
// bringt ihren Kalender mit: Der Supervisor sucht dann eine Datei, die es nicht gibt, und
// die Tagesrotation legt jeden Tag eine neue Reihe an.
var day = new DateTime(2026, 9, 6, 14, 3, 0, DateTimeKind.Utc);
UnderCulture(culture, () =>
{
FormattableString.Invariant($"{day:yyyy-MM-dd}.jsonl").Should().Be("2026-09-06.jsonl");
FormattableString.Invariant($"Info-{day:dd-MM-yy}.txt").Should().Be("Info-06-09-26.txt");
});
}
[Fact]
public void TheNaiveInterpolation_IsWhatWentWrong()
{
// Der Gegenbeweis, damit dieser Test nicht versehentlich trivial wird: Ohne den
// ausdruecklichen Formatanbieter geht es tatsaechlich schief. Faellt diese Zusicherung
// eines Tages, ist die Gefahr weg - dann darf der Test verschwinden, nicht vorher.
var day = new DateTime(2026, 9, 6, 0, 0, 0, DateTimeKind.Utc);
UnderCulture("fa-IR", () => $"{day:yyyy-MM-dd}".Should().NotBe("2026-09-06"));
}
}