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> @
79 lines
2.9 KiB
C#
79 lines
2.9 KiB
C#
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();
|
|
}
|
|
}
|