Kultur-Bug behoben + Threema entfernt (INotificationSink)

- TraderMonitorService las API-Preise kulturabhaengig: unter de-DE wurde aus
  "0.53" der Wert 53 (Faktor-100-Fehler im Einstandspreis). Nutzt jetzt den
  bereits vorhandenen invarianten Helper ParseDecimal.
- Gleiche Fehlerklasse in PolymarketClobClient (6x) und MasterTraderAnalyticsJob
  vorsorglich auf InvariantCulture gestellt.
- Neuer Regressionstest ApiNumberParsingTests (10 Faelle unter erzwungener de-DE-Kultur).
- Threema komplett entfernt (Entscheidung Richard): ThreemaService, vendorte
  Bibliothek libs/Threema-MsgApi-Net-Core, ServerSettings-Block, DI-Verdrahtung.
- Ersetzt durch neutrale INotificationSink (No-Throw-Vertrag) + LogNotificationSink
  als Uebergang; RocketChat/Telegram folgen spaeter.
- Entfernt nebenbei libsodium 1.0.16, die einzige Registry-Nutzung im Build,
  den HttpListener-Webhook und System.Web.HttpUtility (alles Linux-Hindernisse).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-06 12:01:13 +02:00
co-authored by Claude Opus 5
parent ab0a494f29
commit fd6d62618f
56 changed files with 794 additions and 3734 deletions
@@ -0,0 +1,64 @@
using System.Globalization;
using System.Text.Json;
using System.Threading;
using PolyTraderSharp.Services;
using Xunit;
namespace PolyTrader.Tests
{
/// <summary>
/// Regressionsschutz für den Kultur-Bug in der Aktivitäts-Auswertung: Polymarket liefert
/// <c>price</c>/<c>size</c> je nach Endpunkt als JSON-Zahl ODER als String ("0.53"). Wurde der
/// String kulturabhängig geparst, las de-DE den Punkt als Tausendertrennzeichen aus 0,53 wurde
/// 53 (Faktor-100-Fehler im Einstandspreis der Master-Trader-Auswertung).
///
/// Die Tests setzen die Thread-Kultur bewusst auf de-DE, damit sie auch auf einer invarianten
/// Umgebung (z.B. Linux-CI ohne LANG) fehlschlagen würden, sobald jemand die Invariant-Angabe
/// wieder entfernt.
/// </summary>
public class ApiNumberParsingTests
{
/// <summary>Führt die Prüfung unter erzwungener de-DE-Kultur aus und stellt sie danach wieder her.</summary>
private static void UnderGermanCulture(System.Action assertions)
{
var previous = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");
try { assertions(); }
finally { Thread.CurrentThread.CurrentCulture = previous; }
}
private static JsonElement Prop(string json) =>
JsonDocument.Parse(json).RootElement.GetProperty("v");
[Theory]
[InlineData("\"0.53\"", 0.53)] // der eigentliche Bug: wurde unter de-DE zu 53
[InlineData("\"0.045\"", 0.045)]
[InlineData("\"1234.5\"", 1234.5)] // ohne Invariant unter de-DE: 12345
[InlineData("\"0\"", 0)]
public void ParseDecimal_reads_string_values_invariantly(string jsonValue, double expected)
{
UnderGermanCulture(() =>
Assert.Equal((decimal)expected, TraderMonitorService.ParseDecimal(Prop($"{{\"v\":{jsonValue}}}"))));
}
[Theory]
[InlineData("0.53", 0.53)]
[InlineData("1234.5", 1234.5)]
public void ParseDecimal_reads_numeric_values(string jsonValue, double expected)
{
UnderGermanCulture(() =>
Assert.Equal((decimal)expected, TraderMonitorService.ParseDecimal(Prop($"{{\"v\":{jsonValue}}}"))));
}
[Theory]
[InlineData("null")]
[InlineData("\"\"")]
[InlineData("\"n/a\"")]
[InlineData("true")]
public void ParseDecimal_returns_zero_for_unusable_values(string jsonValue)
{
UnderGermanCulture(() =>
Assert.Equal(0m, TraderMonitorService.ParseDecimal(Prop($"{{\"v\":{jsonValue}}}"))));
}
}
}