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();
}
}