using FluentAssertions; using Marathon.Domain.Backtesting; using Marathon.Infrastructure.Persistence.Repositories; using Microsoft.EntityFrameworkCore; namespace Marathon.Infrastructure.Tests.Persistence; /// /// Round-trip + query tests for . Uses the /// in-memory SQLite fixture so the table + unique name index declared in the /// configuration are exercised on every test. /// public sealed class SavedStrategyRoundTripTests : IDisposable { private readonly InMemoryDbFixture _fixture; private readonly SavedStrategyRepository _repo; public SavedStrategyRoundTripTests() { _fixture = new InMemoryDbFixture(); _repo = new SavedStrategyRepository(_fixture.DbContext); } public void Dispose() => _fixture.Dispose(); private static BacktestStrategy Strategy( decimal minScore = 0.45m, StakeRule rule = StakeRule.Kelly) => new( StartingBankroll: 2000m, MinScore: minScore, StakeRule: rule, FlatStake: 75m, PercentOfBankroll: 0.03m, KellyFraction: 0.5m); [Fact] public async Task RoundTrip_PreservesAllFields() { var saved = SavedStrategy.Create("Quarter Kelly", Strategy()); await _repo.AddAsync(saved); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); var got = await _repo.GetAsync(saved.Id); got.Should().NotBeNull(); got!.Id.Should().Be(saved.Id); got.Name.Should().Be("Quarter Kelly"); got.Strategy.StartingBankroll.Should().Be(2000m); got.Strategy.MinScore.Should().Be(0.45m); got.Strategy.StakeRule.Should().Be(StakeRule.Kelly); got.Strategy.FlatStake.Should().Be(75m); got.Strategy.PercentOfBankroll.Should().Be(0.03m); got.Strategy.KellyFraction.Should().Be(0.5m); got.CreatedAt.Should().Be(saved.CreatedAt); } [Fact] public async Task GetByNameAsync_MatchesTrimmed_AndReturnsNullWhenMissing() { var saved = SavedStrategy.Create("Aggro", Strategy()); await _repo.AddAsync(saved); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); (await _repo.GetByNameAsync(" Aggro "))!.Id.Should().Be(saved.Id); (await _repo.GetByNameAsync("nope")).Should().BeNull(); } [Fact] public async Task Name_IsCaseInsensitive_ForLookupAndUniqueness() { await _repo.AddAsync(SavedStrategy.Create("Kelly", Strategy())); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); // Lookup folds case (NOCASE column collation). (await _repo.GetByNameAsync("kelly")).Should().NotBeNull(); // And a case-variant is rejected as a duplicate by the unique index. await _repo.AddAsync(SavedStrategy.Create("KELLY", Strategy())); var act = async () => await _repo.SaveChangesAsync(); await act.Should().ThrowAsync(); } [Fact] public async Task ListAsync_OrdersByName() { await _repo.AddAsync(SavedStrategy.Create("Zeta", Strategy())); await _repo.AddAsync(SavedStrategy.Create("Alpha", Strategy())); await _repo.AddAsync(SavedStrategy.Create("Mu", Strategy())); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); var list = await _repo.ListAsync(); list.Select(s => s.Name).Should().ContainInOrder("Alpha", "Mu", "Zeta"); } [Fact] public async Task UpdateAsync_PersistsStrategyChange() { var saved = SavedStrategy.Create("Tweak me", Strategy(minScore: 0.45m, rule: StakeRule.Kelly)); await _repo.AddAsync(saved); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); var updated = saved with { Strategy = Strategy(minScore: 0.7m, rule: StakeRule.Flat) }; await _repo.UpdateAsync(updated); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); var got = await _repo.GetAsync(saved.Id); got!.Strategy.MinScore.Should().Be(0.7m); got.Strategy.StakeRule.Should().Be(StakeRule.Flat); got.Name.Should().Be("Tweak me"); } [Fact] public async Task DeleteAsync_Removes() { var saved = SavedStrategy.Create("Trash", Strategy()); await _repo.AddAsync(saved); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); await _repo.DeleteAsync(saved.Id); await _repo.SaveChangesAsync(); (await _repo.GetAsync(saved.Id)).Should().BeNull(); } [Fact] public async Task UniqueNameIndex_RejectsDuplicateName() { await _repo.AddAsync(SavedStrategy.Create("dupe", Strategy())); await _repo.SaveChangesAsync(); _fixture.DbContext.ChangeTracker.Clear(); await _repo.AddAsync(SavedStrategy.Create("dupe", Strategy())); var act = async () => await _repo.SaveChangesAsync(); await act.Should().ThrowAsync(); } }