using FluentAssertions; using Marathon.Domain.Backtesting; namespace Marathon.Domain.Tests.Backtesting; public sealed class SavedStrategyTests { private static BacktestStrategy AnyStrategy() => BacktestStrategy.Default; [Fact] public void Create_AssignsIdentity_AndRecentTimestamp() { var s = SavedStrategy.Create("My preset", AnyStrategy()); s.Id.Should().NotBe(Guid.Empty); s.Name.Should().Be("My preset"); s.Strategy.Should().Be(AnyStrategy()); // Offset-agnostic instant comparison — robust to however MoscowTime is sourced. s.CreatedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); } [Theory] [InlineData(" Spaced ", "Spaced")] [InlineData("\tTabbed\n", "Tabbed")] public void Constructor_TrimsName(string input, string expected) { SavedStrategy.Create(input, AnyStrategy()).Name.Should().Be(expected); } [Theory] [InlineData("")] [InlineData(" ")] public void Constructor_Throws_When_NameBlank(string blank) { var act = () => SavedStrategy.Create(blank, AnyStrategy()); act.Should().Throw(); } [Fact] public void Constructor_Throws_When_NameExceedsMax() { var tooLong = new string('x', SavedStrategy.MaxNameLength + 1); var act = () => SavedStrategy.Create(tooLong, AnyStrategy()); act.Should().Throw(); } [Fact] public void Constructor_Accepts_NameAtMaxLength() { var maxName = new string('x', SavedStrategy.MaxNameLength); SavedStrategy.Create(maxName, AnyStrategy()).Name.Should().HaveLength(SavedStrategy.MaxNameLength); } [Fact] public void With_SwapsStrategy_KeepingIdentityAndName() { var s = SavedStrategy.Create("Preset", AnyStrategy()); var tweaked = new BacktestStrategy(1000m, 0.6m, StakeRule.Flat, 50m, 0.02m, 0.25m); var updated = s with { Strategy = tweaked }; updated.Id.Should().Be(s.Id); updated.Name.Should().Be("Preset"); updated.CreatedAt.Should().Be(s.CreatedAt); updated.Strategy.MinScore.Should().Be(0.6m); } }