feat: implement Phase 1 — solution skeleton and domain model
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.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using FluentAssertions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.Domain.Tests.ValueObjects;
|
||||
|
||||
public sealed class BetScopeTests
|
||||
{
|
||||
[Fact]
|
||||
public void MatchScope_Singleton_IsTheSameInstance()
|
||||
{
|
||||
MatchScope.Instance.Should().BeSameAs(MatchScope.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PeriodScope_CreatesInstance_WhenNumberIsPositive()
|
||||
{
|
||||
var scope = new PeriodScope(1);
|
||||
scope.Number.Should().Be(1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(int.MinValue)]
|
||||
public void PeriodScope_ThrowsArgumentOutOfRangeException_WhenNumberIsZeroOrNegative(int number)
|
||||
{
|
||||
var act = () => new PeriodScope(number);
|
||||
act.Should().Throw<ArgumentOutOfRangeException>()
|
||||
.WithParameterName("Number");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BetScope_PatternMatching_WorksCorrectly()
|
||||
{
|
||||
BetScope match = MatchScope.Instance;
|
||||
BetScope period = new PeriodScope(2);
|
||||
|
||||
var matchResult = match switch
|
||||
{
|
||||
MatchScope => "match",
|
||||
PeriodScope(var n) => $"period-{n}",
|
||||
_ => "other",
|
||||
};
|
||||
|
||||
var periodResult = period switch
|
||||
{
|
||||
MatchScope => "match",
|
||||
PeriodScope(var n) => $"period-{n}",
|
||||
_ => "other",
|
||||
};
|
||||
|
||||
matchResult.Should().Be("match");
|
||||
periodResult.Should().Be("period-2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PeriodScope_Equality_IsValueBased()
|
||||
{
|
||||
var a = new PeriodScope(1);
|
||||
var b = new PeriodScope(1);
|
||||
a.Should().Be(b);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchScope_And_PeriodScope_AreNotEqual()
|
||||
{
|
||||
BetScope match = MatchScope.Instance;
|
||||
BetScope period = new PeriodScope(1);
|
||||
match.Should().NotBe(period);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using FluentAssertions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.Domain.Tests.ValueObjects;
|
||||
|
||||
public sealed class EventIdTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_CreatesInstance_WhenValueIsNonEmpty()
|
||||
{
|
||||
var id = new EventId("26456117");
|
||||
id.Value.Should().Be("26456117");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("\t")]
|
||||
public void Constructor_ThrowsArgumentException_WhenValueIsEmptyOrWhitespace(string value)
|
||||
{
|
||||
var act = () => new EventId(value);
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithParameterName("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsArgumentException_WhenValueIsNull()
|
||||
{
|
||||
var act = () => new EventId(null!);
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithParameterName("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_ReturnsValue()
|
||||
{
|
||||
var id = new EventId("12345");
|
||||
id.ToString().Should().Be("12345");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equality_IsValueBased()
|
||||
{
|
||||
var a = new EventId("26456117");
|
||||
var b = new EventId("26456117");
|
||||
a.Should().Be(b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FluentAssertions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.Domain.Tests.ValueObjects;
|
||||
|
||||
public sealed class OddsRateTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("1.01")]
|
||||
[InlineData("1.65")]
|
||||
[InlineData("10.5")]
|
||||
[InlineData("100.0")]
|
||||
public void Constructor_CreatesInstance_WhenValueIsGreaterThanOne(string rawValue)
|
||||
{
|
||||
var value = decimal.Parse(rawValue);
|
||||
var rate = new OddsRate(value);
|
||||
rate.Value.Should().Be(value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1.0")]
|
||||
[InlineData("0.99")]
|
||||
[InlineData("0.0")]
|
||||
[InlineData("-1.5")]
|
||||
public void Constructor_ThrowsArgumentOutOfRangeException_WhenValueIsOneOrLess(string rawValue)
|
||||
{
|
||||
var value = decimal.Parse(rawValue);
|
||||
var act = () => new OddsRate(value);
|
||||
act.Should().Throw<ArgumentOutOfRangeException>()
|
||||
.WithParameterName("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equality_IsValueBased()
|
||||
{
|
||||
var a = new OddsRate(1.65m);
|
||||
var b = new OddsRate(1.65m);
|
||||
a.Should().Be(b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using FluentAssertions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.Domain.Tests.ValueObjects;
|
||||
|
||||
public sealed class OddsValueTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("3.5")]
|
||||
[InlineData("213.5")]
|
||||
[InlineData("-1.5")]
|
||||
[InlineData("-0.5")]
|
||||
[InlineData("0.5")]
|
||||
public void Constructor_CreatesInstance_WhenValueIsNonZero(string rawValue)
|
||||
{
|
||||
var value = decimal.Parse(rawValue);
|
||||
var oddsValue = new OddsValue(value);
|
||||
oddsValue.Value.Should().Be(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsArgumentOutOfRangeException_WhenValueIsZero()
|
||||
{
|
||||
var act = () => new OddsValue(0m);
|
||||
act.Should().Throw<ArgumentOutOfRangeException>()
|
||||
.WithParameterName("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equality_IsValueBased()
|
||||
{
|
||||
var a = new OddsValue(3.5m);
|
||||
var b = new OddsValue(3.5m);
|
||||
a.Should().Be(b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using FluentAssertions;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
|
||||
namespace Marathon.Domain.Tests.ValueObjects;
|
||||
|
||||
public sealed class SportCodeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_CreatesInstance_WhenValueIsPositive()
|
||||
{
|
||||
var code = new SportCode(6);
|
||||
code.Value.Should().Be(6);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(int.MinValue)]
|
||||
public void Constructor_ThrowsArgumentOutOfRangeException_WhenValueIsZeroOrNegative(int value)
|
||||
{
|
||||
var act = () => new SportCode(value);
|
||||
act.Should().Throw<ArgumentOutOfRangeException>()
|
||||
.WithParameterName("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_ReturnsStringRepresentationOfValue()
|
||||
{
|
||||
var code = new SportCode(22723);
|
||||
code.ToString().Should().Be("22723");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equality_IsValueBased()
|
||||
{
|
||||
var a = new SportCode(6);
|
||||
var b = new SportCode(6);
|
||||
a.Should().Be(b);
|
||||
a.Should().NotBeSameAs(b);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user