Files
PolyTraderSharp/tests/PolyTrader.Tests/BlockchainSubscriptionTests.cs
T
RichardandClaude Opus 4.8 6a8ecbc80a MaxBuyPrice-Skala korrigiert + Blockchain-Abo-Logik extrahiert & getestet
- Fix (Richard): MaxBuyPrice-Skalen-Korrektur analog PreRedeemLimit
  (Migration FixMaxBuyPriceScale: Werte > 1 wie 98.0/99.0 -> /100 = 0.98/0.99;
  auf MySQL angewendet). Cap greift bei den betroffenen Accounts jetzt wieder.
  Beschreibung des Feldes um den Skalen-Hinweis ergaenzt.

- Testnetz erweitert: reine Klasse BlockchainSubscription (Logic/) fuer das
  Alchemy-Abo (PadAddress + Filter-Batching in 3er-Chunks, Buy=Topic3/Sell=Topic2
  je Chunk). AlchemyWebsocketService ruft sie jetzt (verhaltensneutral;
  Konstanten + lokale Methoden entfernt). BlockchainSubscriptionTests: 13 Faelle
  (Padding, Chunk-Anzahl 0/1/3/4/6/7, Topic-Zuordnung, CTF-Adresse/Topics).

Gesamt 137 Tests gruen. Build/Smoke gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 10:54:46 +02:00

98 lines
3.6 KiB
C#

using System.Collections.Generic;
using System.Linq;
using PolyTrader.Modules.CopyTrading.Logic;
using PolyTraderSharp.Models;
using Xunit;
namespace PolyTrader.Tests
{
/// <summary>
/// Sichert den Aufbau des Alchemy-Blockchain-Abos ab: falsche Filter/Padding bedeuten
/// verpasste (oder falsch zugeordnete) Master-Trades.
/// </summary>
public class BlockchainSubscriptionTests
{
private static List<TrackedTrader> Traders(int n) =>
Enumerable.Range(1, n).Select(i => new TrackedTrader { Id = i, WalletAddress = $"0x{i:x40}" }).ToList();
// ---------------- PadAddress ----------------
[Fact]
public void PadAddress_strips_prefix_lowercases_and_pads_to_32_bytes()
{
var r = BlockchainSubscription.PadAddress("0x628914CF1e96A9D1Ab8F0489A9f64be5633bac41");
Assert.Equal("0x000000000000000000000000628914cf1e96a9d1ab8f0489a9f64be5633bac41", r);
Assert.Equal(66, r.Length); // "0x" + 64 Hex-Zeichen
}
[Fact]
public void PadAddress_pads_short_input_with_leading_zeros()
{
Assert.Equal("0x" + new string('0', 61) + "abc", BlockchainSubscription.PadAddress("0xABC"));
}
// ---------------- Build: Chunk-Anzahl ----------------
[Theory]
[InlineData(0, 0)]
[InlineData(1, 2)] // 1 Chunk -> Buy + Sell
[InlineData(3, 2)] // 1 Chunk (voll)
[InlineData(4, 4)] // 2 Chunks (3 + 1)
[InlineData(6, 4)] // 2 Chunks (3 + 3)
[InlineData(7, 6)] // 3 Chunks (3 + 3 + 1)
public void Build_creates_two_filters_per_chunk(int traderCount, int expectedFilters)
{
Assert.Equal(expectedFilters, BlockchainSubscription.Build(Traders(traderCount)).Filters.Count);
}
// ---------------- Build: Filter-Inhalt ----------------
[Fact]
public void Build_pairs_buy_topic3_and_sell_topic2()
{
var sub = BlockchainSubscription.Build(Traders(1));
var buy = sub.Filters[0];
var sell = sub.Filters[1];
Assert.NotNull(buy.Topic3);
Assert.Null(buy.Topic2);
Assert.NotNull(sell.Topic2);
Assert.Null(sell.Topic3);
}
[Fact]
public void Build_sets_ctf_address_and_transfer_topics()
{
var buy = BlockchainSubscription.Build(Traders(1)).Filters[0];
Assert.Equal(BlockchainSubscription.CtfContractAddress, buy.Address);
Assert.Equal(
new[] { BlockchainSubscription.TransferSingleTopic, BlockchainSubscription.TransferBatchTopic },
buy.Topic0.ToArray());
}
[Fact]
public void Build_chunks_addresses_by_three()
{
var sub = BlockchainSubscription.Build(Traders(4)); // 3 + 1
Assert.Equal(3, sub.Filters[0].Topic3!.Count); // Chunk 1 (Buy)
Assert.Equal(3, sub.Filters[1].Topic2!.Count); // Chunk 1 (Sell)
Assert.Single(sub.Filters[2].Topic3!); // Chunk 2 (Buy)
Assert.Single(sub.Filters[3].Topic2!); // Chunk 2 (Sell)
}
[Fact]
public void Build_uses_padded_addresses()
{
var addr = BlockchainSubscription.Build(Traders(1)).Filters[0].Topic3![0];
Assert.Equal(66, addr.Length);
Assert.StartsWith("0x", addr);
}
[Fact]
public void Build_empty_returns_no_filters()
{
Assert.Empty(BlockchainSubscription.Build(Traders(0)).Filters);
}
}
}