61114ea31b
Creates the 9-project .NET 8 solution (5 src + 4 test) with Marathon.Domain fully implemented: value objects (SportCode, EventId, OddsRate, OddsValue, BetScope hierarchy), enums (Side, BetType, OddsSource, AnomalyKind), and entities (Sport, Country, League, Event, Bet, OddsSnapshot, EventResult, Anomaly) with all invariants enforced in constructors. 96 domain tests pass (FluentAssertions + xUnit). Directory.Build.props and Directory.Packages.props centralise build settings and NuGet versions. Both Marathon.sln and Marathon.slnx are committed; dotnet build Marathon.sln succeeds with 0 warnings/errors.
69 lines
2.2 KiB
C#
69 lines
2.2 KiB
C#
using FluentAssertions;
|
|
using Marathon.Domain.Entities;
|
|
using Marathon.Domain.Enums;
|
|
using Marathon.Domain.ValueObjects;
|
|
|
|
namespace Marathon.Domain.Tests.Entities;
|
|
|
|
public sealed class AnomalyTests
|
|
{
|
|
private static readonly Guid SampleId = Guid.NewGuid();
|
|
private static readonly EventId SampleEventId = new("26456117");
|
|
|
|
[Fact]
|
|
public void Constructor_CreatesAnomaly_WhenAllParametersAreValid()
|
|
{
|
|
var anomaly = new Anomaly(
|
|
SampleId,
|
|
SampleEventId,
|
|
DateTimeOffset.UtcNow,
|
|
AnomalyKind.SuspensionFlip,
|
|
0.85m,
|
|
"{}");
|
|
|
|
anomaly.Id.Should().Be(SampleId);
|
|
anomaly.Score.Should().Be(0.85m);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_ThrowsArgumentException_WhenIdIsEmptyGuid()
|
|
{
|
|
var act = () => new Anomaly(Guid.Empty, SampleEventId, DateTimeOffset.UtcNow,
|
|
AnomalyKind.SuspensionFlip, 0.5m, "{}");
|
|
act.Should().Throw<ArgumentException>().WithParameterName("Id");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-0.01)]
|
|
[InlineData(1.01)]
|
|
[InlineData(2.0)]
|
|
[InlineData(-1.0)]
|
|
public void Constructor_ThrowsArgumentOutOfRangeException_WhenScoreIsOutOfRange(double score)
|
|
{
|
|
var act = () => new Anomaly(SampleId, SampleEventId, DateTimeOffset.UtcNow,
|
|
AnomalyKind.SuspensionFlip, (decimal)score, "{}");
|
|
act.Should().Throw<ArgumentOutOfRangeException>().WithParameterName("Score");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0.0)]
|
|
[InlineData(0.5)]
|
|
[InlineData(1.0)]
|
|
public void Constructor_CreatesAnomaly_WhenScoreIsInValidRange(double score)
|
|
{
|
|
var act = () => new Anomaly(SampleId, SampleEventId, DateTimeOffset.UtcNow,
|
|
AnomalyKind.SuspensionFlip, (decimal)score, "{}");
|
|
act.Should().NotThrow();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void Constructor_ThrowsArgumentException_WhenEvidenceJsonIsEmptyOrWhitespace(string evidence)
|
|
{
|
|
var act = () => new Anomaly(SampleId, SampleEventId, DateTimeOffset.UtcNow,
|
|
AnomalyKind.SuspensionFlip, 0.5m, evidence);
|
|
act.Should().Throw<ArgumentException>().WithParameterName("EvidenceJson");
|
|
}
|
|
}
|