Files
maraphon-app/tests/Marathon.Domain.Tests/Entities/EventTests.cs
T
alexei.dolgolyov 61114ea31b 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.
2026-05-05 01:20:28 +03:00

131 lines
4.5 KiB
C#

using FluentAssertions;
using Marathon.Domain.Entities;
using Marathon.Domain.ValueObjects;
namespace Marathon.Domain.Tests.Entities;
public sealed class EventTests
{
private static readonly EventId SampleId = new("26456117");
private static readonly SportCode SampleSport = new(6);
private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);
private static readonly DateTimeOffset ValidScheduledAt =
new(2026, 5, 10, 18, 0, 0, MoscowOffset);
private static Event CreateValidEvent(DateTimeOffset? scheduledAt = null) =>
new(
SampleId,
SampleSport,
"RU",
"nba-league-1",
"Play-Offs",
scheduledAt ?? ValidScheduledAt,
"Арсенал",
"Атлетико");
[Fact]
public void Constructor_CreatesEvent_WhenAllParametersAreValid()
{
var evt = CreateValidEvent();
evt.Id.Should().Be(SampleId);
evt.Sport.Should().Be(SampleSport);
evt.ScheduledAt.Offset.Should().Be(MoscowOffset);
}
[Fact]
public void Constructor_ThrowsArgumentException_WhenScheduledAtIsNotMoscowTime()
{
// UTC+0 — wrong offset
var utcTime = new DateTimeOffset(2026, 5, 10, 15, 0, 0, TimeSpan.Zero);
var act = () => CreateValidEvent(utcTime);
act.Should().Throw<ArgumentException>()
.WithParameterName("ScheduledAt");
}
[Fact]
public void Constructor_ThrowsArgumentException_WhenScheduledAtIsOtherOffset()
{
// UTC+2 — wrong offset (CEST, for example)
var cestTime = new DateTimeOffset(2026, 5, 10, 17, 0, 0, TimeSpan.FromHours(2));
var act = () => CreateValidEvent(cestTime);
act.Should().Throw<ArgumentException>()
.WithParameterName("ScheduledAt");
}
[Fact]
public void ScheduledAt_Offset_IsMoscowTime()
{
var evt = CreateValidEvent();
evt.ScheduledAt.Offset.Should().Be(TimeSpan.FromHours(3));
}
[Fact]
public void Constructor_ThrowsArgumentNullException_WhenIdIsNull()
{
var act = () => new Event(null!, SampleSport, "RU", "l1", "cat", ValidScheduledAt, "A", "B");
act.Should().Throw<ArgumentNullException>().WithParameterName("Id");
}
[Fact]
public void Constructor_ThrowsArgumentNullException_WhenSportIsNull()
{
var act = () => new Event(SampleId, null!, "RU", "l1", "cat", ValidScheduledAt, "A", "B");
act.Should().Throw<ArgumentNullException>().WithParameterName("Sport");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void Constructor_ThrowsArgumentException_WhenCountryCodeIsEmptyOrWhitespace(string code)
{
var act = () => new Event(SampleId, SampleSport, code, "l1", "cat", ValidScheduledAt, "A", "B");
act.Should().Throw<ArgumentException>().WithParameterName("CountryCode");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void Constructor_ThrowsArgumentException_WhenLeagueIdIsEmptyOrWhitespace(string leagueId)
{
var act = () => new Event(SampleId, SampleSport, "RU", leagueId, "cat", ValidScheduledAt, "A", "B");
act.Should().Throw<ArgumentException>().WithParameterName("LeagueId");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void Constructor_ThrowsArgumentException_WhenSide1NameIsEmptyOrWhitespace(string name)
{
var act = () => new Event(SampleId, SampleSport, "RU", "l1", "cat", ValidScheduledAt, name, "B");
act.Should().Throw<ArgumentException>().WithParameterName("Side1Name");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void Constructor_ThrowsArgumentException_WhenSide2NameIsEmptyOrWhitespace(string name)
{
var act = () => new Event(SampleId, SampleSport, "RU", "l1", "cat", ValidScheduledAt, "A", name);
act.Should().Throw<ArgumentException>().WithParameterName("Side2Name");
}
[Fact]
public void Category_CanBeEmptyString()
{
// Category is optional (deep breadcrumbs may not exist)
var evt = new Event(SampleId, SampleSport, "RU", "l1", string.Empty, ValidScheduledAt, "A", "B");
evt.Category.Should().Be(string.Empty);
}
[Fact]
public void Event_IsImmutable_NoSettablePublicProperties()
{
var eventType = typeof(Event);
var settableProperties = eventType.GetProperties()
.Where(p => p.CanWrite && p.GetSetMethod(nonPublic: false) is not null)
.ToList();
settableProperties.Should().BeEmpty("Event must be immutable.");
}
}