77 lines
3.0 KiB
C#
77 lines
3.0 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using FinlyticEngine.Database;
|
|
using FinlyticEngine.Services.Trading;
|
|
using FinlyticEngine.Tests.TestSupport;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
|
|
namespace FinlyticEngine.Tests._Verify;
|
|
|
|
/// <summary>
|
|
/// ONE-OFF verification against a real, throwaway, locally-run PostgreSQL container (NOT the OmniDB
|
|
/// production database — a brand-new container started solely for this check, no compose.yaml/appsettings
|
|
/// connection strings involved) to settle whether the AddTradeFillAsync DbUpdateConcurrencyException
|
|
/// reproduced under EF InMemory/SQLite is a provider artifact or a genuine, provider-independent EF Core
|
|
/// change-tracking defect that would also occur in production. Deleted after the verdict is recorded.
|
|
/// </summary>
|
|
public class PostgresVerificationTests
|
|
{
|
|
private readonly ITestOutputHelper _output;
|
|
public PostgresVerificationTests(ITestOutputHelper output) => _output = output;
|
|
|
|
private const string ConnString = "Host=localhost;Port=55987;Database=finlytic_verify;Username=postgres;Password=test";
|
|
|
|
[Fact]
|
|
public async Task RealPostgres_AddTradeFillAsync_ExactProductionCallPath_OwnerSucceeds()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddDbContext<EngineDbContext>(o => o.UseNpgsql(ConnString));
|
|
await using var provider = services.BuildServiceProvider();
|
|
|
|
await using (var schemaDb = provider.GetRequiredService<EngineDbContext>())
|
|
{
|
|
await schemaDb.Database.EnsureDeletedAsync();
|
|
await schemaDb.Database.EnsureCreatedAsync();
|
|
}
|
|
|
|
var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
|
|
var owner = Guid.NewGuid();
|
|
var trade = TestData.ActiveTrade(owner);
|
|
|
|
using (var scope = scopeFactory.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<EngineDbContext>();
|
|
db.Trades.Add(trade);
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
var sut = new TradeLifecycleService(
|
|
scopeFactory,
|
|
new NeverInvokedCompositeOpportunityScorer(),
|
|
new NeverInvokedAiReasoningGateService(),
|
|
new NeverInvokedKnockOutDerivativeResolver(),
|
|
new FakeEngineRpcClient(),
|
|
new FakeSettingsService(),
|
|
new FakeFinlyticLogger<TradeLifecycleService>());
|
|
|
|
// This calls the REAL, unmodified TradeLifecycleService.AddTradeFillAsync exactly as production code
|
|
// does, against a real PostgreSQL instance.
|
|
Exception? caught = null;
|
|
try
|
|
{
|
|
var dto = await sut.AddTradeFillAsync(owner, trade.Id, 105m, 1m);
|
|
_output.WriteLine($"SUCCEEDED. Trade {dto.TradeId} now has {dto.Fills.Count} fill(s).");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
caught = ex;
|
|
_output.WriteLine($"THREW: {ex.GetType().FullName}: {ex.Message}");
|
|
}
|
|
|
|
Assert.Null(caught);
|
|
}
|
|
}
|