feat(engine): add FinlyticEngine microservice with trade lifecycle, AI reasoning gate, composite scoring, and unit tests
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user