Skip to content

Commit

Permalink
Improvements to TouramentManager.ExtensionMethods
Browse files Browse the repository at this point in the history
SetEntityExtensions:
* Add tie-break rule handling for match points to MatchEntityExtensions. Use existing entries in MatchRuleEntity for configuration.
* New Add() overloads to more easily add sets from a string (e.g. "25:20 25:19") - especialy for unit tests

League.Models.MatchViewModels.EnterResultViewModel:
* Remove code duplication in method MapEntityToFormFields: Sets are now added using a SetEntityExtensions method

Refactoring:
* Replace class PointResultNullable with modified existing class PointResult
* Move methods CalculateSetPoints and Overrule from SetEntity to SetEntityExtensions
* Methods ToShortTimeString and ToLongTimeString of TimeSpanExtensions are implemented using TimeOnly
* Rename (unused) CloneHelperExtensions to EntityCoreExtensions

Unit tests:
* Add package FluentAssertions v6.11.0 to unit tests of solution
* Added more unit tests to classes in TournamentManager.ExtensionMethods
  • Loading branch information
axunonb committed Aug 4, 2023
1 parent 9a9f995 commit ef5e9a4
Show file tree
Hide file tree
Showing 22 changed files with 681 additions and 320 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
1 change: 1 addition & 0 deletions Axuno.Tools.Tests/Axuno.Tools.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
2 changes: 2 additions & 0 deletions League.Demo/League.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<Product>League.Demo</Product>
<TargetFramework>net6.0</TargetFramework>
<NeutralLanguage>en</NeutralLanguage>
<!-- With dotnet, add parameter: -p:SatelliteResourceLanguages="""en;de""" -->
<SatelliteResourceLanguages>en;de</SatelliteResourceLanguages>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions League.Tests/League.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.14" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0">
Expand Down
20 changes: 6 additions & 14 deletions League/Models/MatchViewModels/EnterResultViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using TournamentManager.DAL;
using TournamentManager.DAL.EntityClasses;
using TournamentManager.ExtensionMethods;
using TournamentManager.Match;
using TournamentManager.ModelValidators;

namespace League.Models.MatchViewModels;
Expand Down Expand Up @@ -71,12 +70,12 @@ public void MapEntityToFormFields()
Match.Sets.Sort((int)SetFieldIndex.SequenceNo, ListSortDirection.Ascending);
foreach (var set in Match.Sets)
{
Sets.Add(new PointResultNullable(set.HomeBallPoints, set.GuestBallPoints));
Sets.Add(new PointResult(set.HomeBallPoints, set.GuestBallPoints));
}

while (Sets.Count < _maxNumberOfSets)
{
Sets.Add(new PointResultNullable(null, null));
Sets.Add(new PointResult(default, default(int?)));
}

Id = Match.Id;
Expand All @@ -99,18 +98,11 @@ public void MapFormFieldsToEntity()

// Add sets to entity
Match.Sets.Clear(true);
for (var i = 0; i < Sets.Count; i++)
{
// sets where home and guest ball points are NULL, will be ignored
if (Sets[i].Home.HasValue || Sets[i].Guest.HasValue)
{
// home or guest NULL values are invalidated with -1
Match.Sets.Add(new SetEntity { MatchId = Match.Id, SequenceNo = i + 1, HomeBallPoints = Sets[i].Home ?? -1, GuestBallPoints = Sets[i].Guest ?? -1 });
}
}
// sets where home or guest ball points are NULL will be ignored
Match.Sets.Add(Match.Id, Sets);

// point calculation must run before validation because of tie-break handling
Match.Sets.CalculateSetPoints(Round!.SetRule, Round.MatchRule);
_ = Match.Sets.CalculateSetPoints(Round!.SetRule, Round.MatchRule);
Match.Remarks = Remarks;
Match.ChangeSerial++;
Match.IsComplete = true;
Expand Down Expand Up @@ -147,7 +139,7 @@ public void MapFormFieldsToEntity()
[MaxLength(2000)]
public string? Remarks { get; set; }

public List<PointResultNullable> Sets { get; set; } = new();
public List<PointResult> Sets { get; set; } = new();

#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ public void SetVenueId(long? venueId)
{
if (venueId == VenueId) return;

if (!OrigVenueId.HasValue)
OrigVenueId = VenueId;
OrigVenueId ??= VenueId;

VenueId = venueId;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,6 @@ namespace TournamentManager.DAL.EntityClasses
{
public partial class SetEntity
{
/// <summary>
/// Assigns &quot;set points&quot; to the <see cref="SetEntity"/>.
/// The <see cref="SetEntity.IsOverruled"/> property will be set to <see langword="false"/>
/// </summary>
/// <param name="setRule">The <see cref="SetRuleEntity"/> with the rules to apply.</param>
public void CalculateSetPoints(SetRuleEntity setRule)
{
IsOverruled = false;
if (HomeBallPoints > GuestBallPoints)
{
HomeSetPoints = setRule.PointsSetWon;
GuestSetPoints = setRule.PointsSetLost;
}
else if (HomeBallPoints < GuestBallPoints)
{
HomeSetPoints = setRule.PointsSetLost;
GuestSetPoints = setRule.PointsSetWon;
}
else
{
HomeSetPoints = GuestSetPoints = setRule.PointsSetTie;
}
}

/// <summary>
/// Sets home/guest ball points and home/guest set points.
/// The <see cref="SetEntity.IsOverruled"/> property will be set to <see langword="true"/>,
/// while <see cref="SetEntity.IsTieBreak"/> will be set to <see langword="false"/>.
/// </summary>
/// <param name="homeBallPoints"></param>
/// <param name="guestBallPoints"></param>
/// <param name="homeSetPoints"></param>
/// <param name="guestSetPoints"></param>
public void Overrule(int homeBallPoints, int guestBallPoints, int homeSetPoints, int guestSetPoints)
{
HomeBallPoints = homeBallPoints;
GuestBallPoints = guestBallPoints;
HomeSetPoints = homeSetPoints;
GuestSetPoints = guestSetPoints;
IsTieBreak = false;
IsOverruled = true;
}

protected override void OnBeforeEntitySave()
{
var now = DateTime.UtcNow;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using FluentAssertions;
using TournamentManager.DAL.EntityClasses;
using TournamentManager.ExtensionMethods;
using TournamentManager.ModelValidators;
using TournamentManager.MultiTenancy;

namespace TournamentManager.Tests.ExtensionMethods;

[TestFixture]
public class MatchEntityExtensionTests
{
[Test]
public void Calc_With_No_Sets_Should_Not_Throw()
{
var matchRule = GetMatchRule_NoTieBreakRule();
var setRule = GetSetRule();
var match = new MatchEntity(1234);

Assert.That(del: () => _ = match.Sets.CalculateSetPoints(setRule, matchRule), Throws.Nothing);
}

[TestCase("25:1 25:2 25:3", 3, 0)]
[TestCase("25:1 25:2 1:25 1:25 15:1", 3, 0)]
[TestCase("1:25 2:25 2:25", 0, 3)]
[TestCase("1:25 2:25 25:13 25:1 1:15", 0, 3)]
[TestCase("1:25 25:1", 1, 1)]
public void Calc_MatchPoints_No_TieBreakRule(string setResults, int expectedHome, int expectedGuest)
{
// Note: Test cases are not validated against the rules here, but they are valid.
// Validation is tested in ModelValidator tests

var matchRule = GetMatchRule_NoTieBreakRule();
var setRule = GetSetRule();
var match = new MatchEntity(1234);
match.Sets.Add(match.Id, setResults);
_ = match.Sets.CalculateSetPoints(setRule, matchRule);
_ = match.CalculateMatchPoints(matchRule);

var pointResult = new PointResult(match.HomePoints, match.GuestPoints);
var expectedResult = new PointResult(expectedHome, expectedGuest);

pointResult.Should().BeEquivalentTo(expectedResult);
}

[TestCase("25:1 25:2 25:3", 3, 0)]
[TestCase("25:1 25:2 1:25 1:25 15:1", 2, 1)]
[TestCase("1:25 2:25 2:25", 0, 3)]
[TestCase("1:25 2:25 25:13 25:1 1:15", 1, 2)]
[TestCase("1:25 25:1", 1, 1)]
public void Calc_MatchPoints_With_TieBreakRule(string setResults, int expectedHome, int expectedGuest)
{
// Note: Test cases are not validated against the rules here, but they are valid.
// Validation is tested in ModelValidator tests

var matchRule = GetMatchRule_TieBreakRule();
var setRule = GetSetRule();
var match = new MatchEntity(1234);
match.Sets.Add(match.Id, setResults);
_ = match.Sets.CalculateSetPoints(setRule, matchRule);
_ = match.CalculateMatchPoints(matchRule);

var pointResult = new PointResult(match.HomePoints, match.GuestPoints);
var expectedResult = new PointResult(expectedHome, expectedGuest);

pointResult.Should().BeEquivalentTo(expectedResult);
}

[Test]
public void Calc_MatchPoints_With_TieBreakRule_Throws()
{
var matchRule = GetMatchRule_TieBreakRule();
var setRule = GetSetRule();
var match = new MatchEntity(1234);
match.Sets.Add(match.Id, "25:1 25:2 1:25 1:25 15:1");
_ = match.Sets.CalculateSetPoints(setRule, matchRule);

// This will trigger an exception, because the set points are tie
var lastSet = match.Sets.Last();
lastSet.HomeSetPoints = lastSet.GuestSetPoints = 0;

Assert.That(del: () => _ = match.CalculateMatchPoints(matchRule), Throws.InvalidOperationException);
}

private static MatchRuleEntity GetMatchRule_NoTieBreakRule()
{
return new MatchRuleEntity {
BestOf = true,
NumOfSets = 3,
PointsMatchWon = 3,
PointsMatchLost = 0,
PointsMatchTie = 1
};
}

private static MatchRuleEntity GetMatchRule_TieBreakRule()
{
return new MatchRuleEntity {
BestOf = true,
NumOfSets = 3,
PointsMatchWon = 3,
PointsMatchLost = 0,
PointsMatchWonAfterTieBreak = 2,
PointsMatchLostAfterTieBreak = 1,
PointsMatchTie = 1
};
}

private static SetRuleEntity GetSetRule()
{
return new SetRuleEntity {
NumOfPointsToWinRegular = 25,
NumOfPointsToWinTiebreak = 15,
PointsDiffToWinRegular = 2,
PointsDiffToWinTiebreak = 2,
PointsSetWon = 1,
PointsSetLost = 0,
PointsSetTie = 0
};
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using TournamentManager.DAL.EntityClasses;

namespace TournamentManager.Tests.DAL_CustomExtensions;
namespace TournamentManager.Tests.ExtensionMethods;

[TestFixture]
public class MatchRuleEntityExtensionTests
Expand All @@ -11,7 +14,7 @@ public class MatchRuleEntityExtensionTests
[TestCase(true, 2, 3)]
public void Calculate_Max_Number_Of_Sets(bool isBestOf, int numOfSets, int expected)
{
var rule = new MatchRuleEntity {BestOf = isBestOf, NumOfSets = numOfSets};
var rule = new MatchRuleEntity { BestOf = isBestOf, NumOfSets = numOfSets };
Assert.AreEqual(expected, rule.MaxNumOfSets());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
using TournamentManager.DAL.EntityClasses;
using TournamentManager.ExtensionMethods;

namespace TournamentManager.Tests.DAL_CustomExtensions;
namespace TournamentManager.Tests.ExtensionMethods;

[TestFixture]
public class SetEntityExtensionTests
{
[TestCase(25, 1, 3,1)]
[TestCase(25, 1, 3, 1)]
[TestCase(1, 25, 1, 3)]
[TestCase(25, 25, 2, 2)]
public void Calculate_Set_Points(int homeBallPts, int guestBallPts, int expectedHomeSetPts, int expectedGuestSetPts)
{
var set = new SetEntity {HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts};
var setRule = new SetRuleEntity { PointsSetWon = 3, PointsSetLost = 1, PointsSetTie = 2};
var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts };
var setRule = new SetRuleEntity { PointsSetWon = 3, PointsSetLost = 1, PointsSetTie = 2 };
set.CalculateSetPoints(setRule);
Assert.Multiple(() =>
{
Expand All @@ -30,7 +30,7 @@ public void Calculate_Set_Points(int homeBallPts, int guestBallPts, int expected
[TestCase(1, 25, 1, 2)]
public void Overrule_Set_Points(int homeBallPts, int guestBallPts, int homeSetPts, int guestSetPts)
{
var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts, IsTieBreak = true, IsOverruled = false};
var set = new SetEntity { HomeBallPoints = homeBallPts, GuestBallPoints = guestBallPts, IsTieBreak = true, IsOverruled = false };
set.Overrule(homeBallPts, guestBallPts, homeSetPts, guestSetPts);
Assert.Multiple(() =>
{
Expand All @@ -40,4 +40,4 @@ public void Overrule_Set_Points(int homeBallPts, int guestBallPts, int homeSetPt
Assert.IsTrue(set.IsOverruled);
});
}
}
}
Loading

0 comments on commit ef5e9a4

Please sign in to comment.