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:
@@ -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}}}"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
@@ -40,10 +40,10 @@ namespace PolyTrader.Tests
|
||||
var posRepo = new FakePositionRepository();
|
||||
var marketRepo = new FakeMarketRepository();
|
||||
var accountRepo = new FakeAccountRepository();
|
||||
var threema = new ThreemaService(logger, new JobManager());
|
||||
var notify = new FakeNotificationSink();
|
||||
var journal = new FakeDecisionJournal();
|
||||
var orderEvents = new FakeOrderEventLog();
|
||||
var ladder = new SellLadderService(copy, state, clob, logger, threema, posRepo, orderEvents);
|
||||
var ladder = new SellLadderService(copy, state, clob, logger, notify, posRepo, orderEvents);
|
||||
|
||||
var signalCh = Channel.CreateUnbounded<CopySignal>();
|
||||
var closedCh = Channel.CreateUnbounded<ClosedTrade>();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PolyTrader.Core.Notifications;
|
||||
|
||||
namespace PolyTrader.Tests.Fakes
|
||||
{
|
||||
/// <summary>
|
||||
/// In-Memory-Stub des Benachrichtigungsausgangs – Tests können asserten, WAS gemeldet wurde
|
||||
/// (zuvor bekamen die Tests eine echte ThreemaService-Instanz, die nichts prüfbar machte).
|
||||
/// </summary>
|
||||
public sealed class FakeNotificationSink : INotificationSink
|
||||
{
|
||||
public List<(string Message, NotificationSeverity Severity)> Sent { get; } = new();
|
||||
|
||||
public Task SendAsync(string message, NotificationSeverity severity = NotificationSeverity.Info,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
Sent.Add((message, severity));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using PolyTrader.Tests.Fakes;
|
||||
@@ -25,9 +25,9 @@ namespace PolyTrader.Tests
|
||||
var state = new TradingState();
|
||||
clob ??= new FakeClobClient();
|
||||
var logger = new TerminalLogger();
|
||||
var threema = new ThreemaService(logger, new JobManager());
|
||||
var notify = new FakeNotificationSink();
|
||||
var repo = new FakePositionRepository();
|
||||
var svc = new SellLadderService(copy, state, clob, logger, threema, repo, new FakeOrderEventLog());
|
||||
var svc = new SellLadderService(copy, state, clob, logger, notify, repo, new FakeOrderEventLog());
|
||||
return (svc, copy, state, clob, repo);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace PolyTrader.Tests
|
||||
var (svc, copy, state, clob, _) = Build();
|
||||
var (account, pos) = LiveAccountWithPosition(state);
|
||||
var ladder = FloorLadder();
|
||||
ladder.FloorNotified = true; // Threema-Notify überspringen
|
||||
ladder.FloorNotified = true; // Floor-Benachrichtigung überspringen
|
||||
copy.ExitLadders["1_" + Tok] = ladder;
|
||||
clob.OpenOrdersByAsset[Tok] = new() { ("oid-1", "SELL", 0.40m) };
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
@@ -29,8 +29,8 @@ namespace PolyTrader.Tests
|
||||
var posRepo = new FakePositionRepository();
|
||||
var marketRepo = new FakeMarketRepository();
|
||||
var tradeLog = new FakeCopyTradeLogRepository();
|
||||
var threema = new ThreemaService(logger, new JobManager());
|
||||
var ladder = new SellLadderService(copy, state, clob, logger, threema, posRepo, new FakeOrderEventLog());
|
||||
var notify = new FakeNotificationSink();
|
||||
var ladder = new SellLadderService(copy, state, clob, logger, notify, posRepo, new FakeOrderEventLog());
|
||||
var signalCh = Channel.CreateUnbounded<CopySignal>();
|
||||
var closedCh = Channel.CreateUnbounded<ClosedTrade>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user