@
R3 Slice 4: IBKR-Marktdaten auf EF; Dapper vollstaendig entfernt - 3 IBKR-Entities (IBKRInstrument/IBKRMarketBar/IBKRExternalIdentifier) im CoreDbContext (core_ibkr_instruments/_market_data/_external_identifiers); Migration AddIbkr - IBKRMarketDataRepository von Dapper auf EF (IDbContextFactory<CoreDbContext>); Cross-Modul-Query (ct_trade) via Database.SqlQueryRaw - DatabaseService, IBKRMigrations, Dapper-Package entfernt; Laufzeit-Migrationen komplett weg (Schema extern via dotnet ef database update) - Tests: +4 IBKRMarketDataRepository (EF-InMemory) -> 43/43 gruen; Build + smoke-ui + App-Start ok R3 abgeschlossen: Persistenz vollstaendig auf EF Core. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
using FluentAssertions;
|
||||
using IBKRTrader.Core.IBKR;
|
||||
using IBKRTrader.Core.Logging;
|
||||
using IBKRTrader.Core.Persistence.Ef;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IBKRTrader.Tests.IBKR;
|
||||
|
||||
/// <summary>IBKR-Repo gegen EF-InMemory (Raw-SQL-Cross-Modul-Query wird hier nicht getestet).</summary>
|
||||
[Trait("cat", "unit")]
|
||||
public class IBKRMarketDataRepositoryTests
|
||||
{
|
||||
private sealed class Factory(DbContextOptions<CoreDbContext> o) : IDbContextFactory<CoreDbContext>
|
||||
{
|
||||
public CoreDbContext CreateDbContext() => new(o);
|
||||
}
|
||||
|
||||
private static IBKRMarketDataRepository CreateSut()
|
||||
{
|
||||
var opts = new DbContextOptionsBuilder<CoreDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
|
||||
return new IBKRMarketDataRepository(new Factory(opts), new LoggingService());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpsertInstrument_IsIdempotent_ByConid_AndReturnsId()
|
||||
{
|
||||
var repo = CreateSut();
|
||||
|
||||
var id1 = await repo.UpsertInstrumentAsync(new IBKRInstrument { IbkrConid = 111, Symbol = "AAPL" });
|
||||
var id2 = await repo.UpsertInstrumentAsync(new IBKRInstrument { IbkrConid = 111, Symbol = "AAPL2" });
|
||||
|
||||
id2.Should().Be(id1);
|
||||
(await repo.GetActiveInstrumentCountAsync()).Should().Be(1);
|
||||
(await repo.GetInstrumentByConidAsync(111))!.Symbol.Should().Be("AAPL2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExternalIdentifier_Dedup_AndLookup()
|
||||
{
|
||||
var repo = CreateSut();
|
||||
var id = await repo.UpsertInstrumentAsync(new IBKRInstrument { IbkrConid = 222, Symbol = "MSFT" });
|
||||
|
||||
await repo.UpsertExternalIdentifierAsync(id, "capitoltrades", "MSFT");
|
||||
await repo.UpsertExternalIdentifierAsync(id, "capitoltrades", "MSFT"); // Duplikat
|
||||
|
||||
var found = await repo.FindInstrumentByExternalTickerAsync("capitoltrades", "MSFT");
|
||||
found.Should().NotBeNull();
|
||||
found!.IbkrConid.Should().Be(222);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MarketData_Upsert_Count_AndLatest()
|
||||
{
|
||||
var repo = CreateSut();
|
||||
|
||||
await repo.UpsertMarketDataBatchAsync(new[]
|
||||
{
|
||||
new IBKRMarketBar { InstrumentId = 1, BarSize = "daily", Timestamp = new DateTime(2026, 1, 1), Close = 10m },
|
||||
new IBKRMarketBar { InstrumentId = 1, BarSize = "daily", Timestamp = new DateTime(2026, 1, 2), Close = 11m },
|
||||
});
|
||||
// Upsert desselben Keys aktualisiert (kein zweiter Eintrag).
|
||||
await repo.UpsertMarketDataBatchAsync(new[]
|
||||
{
|
||||
new IBKRMarketBar { InstrumentId = 1, BarSize = "daily", Timestamp = new DateTime(2026, 1, 2), Close = 12m },
|
||||
});
|
||||
|
||||
(await repo.GetBarCountAsync(1)).Should().Be(2);
|
||||
(await repo.GetLatestBarTimestampAsync(1)).Should().Be(new DateTime(2026, 1, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LatestBarTimestamp_IsNull_WhenNoData()
|
||||
{
|
||||
var repo = CreateSut();
|
||||
(await repo.GetLatestBarTimestampAsync(999)).Should().BeNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user