From 00cac2cb89a4a444edfdb6885d496a1c0f9af566 Mon Sep 17 00:00:00 2001 From: Jaculabilis Date: Tue, 22 Mar 2022 20:13:49 -0700 Subject: [PATCH] Add order references to test case builder This allows assertions to be made more easily against orders in a test case. --- MultiversalDiplomacyTests/OrderReference.cs | 69 +++++++++++++ MultiversalDiplomacyTests/TestAdjudicator.cs | 3 + MultiversalDiplomacyTests/TestCaseBuilder.cs | 98 ++++++++++++++++--- .../TestCaseBuilderTest.cs | 44 +++++++++ 4 files changed, 198 insertions(+), 16 deletions(-) create mode 100644 MultiversalDiplomacyTests/OrderReference.cs diff --git a/MultiversalDiplomacyTests/OrderReference.cs b/MultiversalDiplomacyTests/OrderReference.cs new file mode 100644 index 0000000..ca18d3c --- /dev/null +++ b/MultiversalDiplomacyTests/OrderReference.cs @@ -0,0 +1,69 @@ +using MultiversalDiplomacy.Adjudicate; +using MultiversalDiplomacy.Orders; + +using NUnit.Framework; + +namespace MultiversalDiplomacyTests; + +/// +/// An object that provides a view into an order's fate during a test case. +/// +public class OrderReference where OrderType : Order +{ + private TestCaseBuilder Builder { get; } + + /// + /// The order. + /// + public OrderType Order { get; } + + /// + /// The validation result for the order. Throws if validation has not occurred. + /// + public OrderValidation Validation + { + get + { + if (this.Builder.ValidationResults == null) + { + throw new InvalidOperationException("Validation has not been done yet"); + } + var orderValidation = this.Builder.ValidationResults.Where(v => this.Order == v.Order); + if (!orderValidation.Any()) + { + throw new AssertionException($"Missing validation for {this.Order}"); + } + return orderValidation.Single(); + } + } + + /// + /// The order that replaced this order, if any. Throws if validation has not occurred. + /// + public OrderValidation? Replacement + { + get + { + if (this.Builder.ValidationResults == null) + { + throw new InvalidOperationException("Validation has not been done yet"); + } + if (this.Order is UnitOrder unitOrder) + { + var replacementOrder = this.Builder.ValidationResults.Where( + v => v.Order is UnitOrder uo && uo != unitOrder && uo.Unit == unitOrder.Unit); + if (replacementOrder.Any()) + { + return replacementOrder.Single(); + } + } + return null; + } + } + + public OrderReference(TestCaseBuilder builder, OrderType order) + { + this.Builder = builder; + this.Order = order; + } +} \ No newline at end of file diff --git a/MultiversalDiplomacyTests/TestAdjudicator.cs b/MultiversalDiplomacyTests/TestAdjudicator.cs index c03a053..9f608c2 100644 --- a/MultiversalDiplomacyTests/TestAdjudicator.cs +++ b/MultiversalDiplomacyTests/TestAdjudicator.cs @@ -6,6 +6,9 @@ namespace MultiversalDiplomacyTests; public class TestAdjudicator : IPhaseAdjudicator { + public static Func, List> RubberStamp = + (world, orders) => orders.Select(o => o.Validate(ValidationReason.Valid)).ToList(); + private Func, List> ValidateOrdersCallback; public TestAdjudicator( diff --git a/MultiversalDiplomacyTests/TestCaseBuilder.cs b/MultiversalDiplomacyTests/TestCaseBuilder.cs index cd42f3d..cb8579e 100644 --- a/MultiversalDiplomacyTests/TestCaseBuilder.cs +++ b/MultiversalDiplomacyTests/TestCaseBuilder.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; +using MultiversalDiplomacy.Adjudicate; using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Orders; @@ -44,12 +45,12 @@ public class TestCaseBuilder /// /// Give the unit a hold order. /// - public IPowerContext Holds(); + public IOrderDefinedContext Holds(); /// /// Give the unit a move order. /// - public IPowerContext MovesTo(string provinceName, string? coast = null); + public IOrderDefinedContext MovesTo(string provinceName, string? coast = null); /// /// Give the unit a convoy order. @@ -89,7 +90,7 @@ public class TestCaseBuilder /// /// Define the destination of the convoy order. /// - public IPowerContext To(string provinceName); + public IOrderDefinedContext To(string provinceName); } /// @@ -119,18 +120,47 @@ public class TestCaseBuilder /// /// Give the unit an order to support the target's hold order. /// - public IPowerContext Hold(); + public IOrderDefinedContext Hold(); /// /// Give the unit an order to support the target's move order. /// - public IPowerContext MoveTo(string provinceName, string? coast = null); + public IOrderDefinedContext MoveTo(string provinceName, string? coast = null); + } + + /// + /// Context for additional operations on a defined order or defining another order. This + /// context mimics the with additional functionality related to + /// the order that was just defined. + /// + public interface IOrderDefinedContext where OrderType : Order + { + /// + /// Get the context for defining the orders for another power. + /// + public IPowerContext this[string powerName] { get; } + + /// + /// Define an order for a new army in a province. + /// + public IUnitContext Army(string provinceName); + + /// + /// Define an order for a new fleet in a province, optionally on a specific coast. + /// + public IUnitContext Fleet(string provinceName, string? coast = null); + + /// + /// Save a reference to the order just defined. + /// + public IOrderDefinedContext GetReference(out OrderReference order); } public World World { get; private set; } public ReadOnlyCollection Orders { get; } private List OrderList; private Season Season; + public List? ValidationResults { get; private set; } /// /// Create a test case builder that will operate on a world. @@ -138,9 +168,10 @@ public class TestCaseBuilder public TestCaseBuilder(World world, Season? season = null) { this.World = world; - this.OrderList = new List(); + this.OrderList = new(); this.Orders = new(this.OrderList); this.Season = season ?? this.World.Seasons.First(); + this.ValidationResults = null; } /// @@ -188,6 +219,12 @@ public class TestCaseBuilder return newUnit; } + public List ValidateOrders(IPhaseAdjudicator adjudicator) + { + this.ValidationResults = adjudicator.ValidateOrders(this.World, this.Orders.ToList()); + return this.ValidationResults; + } + private class PowerContext : IPowerContext { public TestCaseBuilder Builder; @@ -241,17 +278,17 @@ public class TestCaseBuilder /// /// Order a unit to hold. /// - public IPowerContext Holds() + public IOrderDefinedContext Holds() { HoldOrder order = new HoldOrder(this.PowerContext.Power, this.Unit); this.Builder.OrderList.Add(order); - return this.PowerContext; + return new OrderDefinedContext(this, order); } /// /// Order a unit to move to a destination. /// - public IPowerContext MovesTo(string provinceName, string? coast = null) + public IOrderDefinedContext MovesTo(string provinceName, string? coast = null) { Location destination = this.Unit.Type == UnitType.Army ? this.Builder.World.GetLand(provinceName) @@ -262,7 +299,7 @@ public class TestCaseBuilder this.Builder.Season, destination); this.Builder.OrderList.Add(moveOrder); - return this.PowerContext; + return new OrderDefinedContext(this, moveOrder); } public IConvoyContext Convoys @@ -326,7 +363,7 @@ public class TestCaseBuilder this.Target = target; } - public IPowerContext To(string provinceName) + public IOrderDefinedContext To(string provinceName) { Location location = this.Builder.World.GetLand(provinceName); ConvoyOrder order = new ConvoyOrder( @@ -336,7 +373,7 @@ public class TestCaseBuilder this.Builder.Season, location); this.Builder.OrderList.Add(order); - return this.PowerContext; + return new OrderDefinedContext(this.UnitContext, order); } } @@ -394,17 +431,17 @@ public class TestCaseBuilder this.Target = target; } - public IPowerContext Hold() + public IOrderDefinedContext Hold() { SupportHoldOrder order = new SupportHoldOrder( this.PowerContext.Power, this.UnitContext.Unit, this.Target); this.Builder.OrderList.Add(order); - return this.PowerContext; + return new OrderDefinedContext(this.UnitContext, order); } - public IPowerContext MoveTo(string provinceName, string? coast = null) + public IOrderDefinedContext MoveTo(string provinceName, string? coast = null) { Location destination = this.Target.Type == UnitType.Army ? this.Builder.World.GetLand(provinceName) @@ -416,7 +453,36 @@ public class TestCaseBuilder this.Builder.Season, destination); this.Builder.OrderList.Add(order); - return this.PowerContext; + return new OrderDefinedContext(this.UnitContext, order); + } + } + + private class OrderDefinedContext : IOrderDefinedContext where OrderType : Order + { + public TestCaseBuilder Builder; + public PowerContext PowerContext; + public UnitContext UnitContext; + public OrderType Order; + + public OrderDefinedContext(UnitContext unitContext, OrderType order) + { + this.Builder = unitContext.Builder; + this.PowerContext = unitContext.PowerContext; + this.UnitContext = unitContext; + this.Order = order; + } + + public IPowerContext this[string powerName] => this.PowerContext[powerName]; + + public IUnitContext Army(string provinceName) => this.PowerContext.Army(provinceName); + + public IUnitContext Fleet(string provinceName, string? coast = null) + => this.PowerContext.Fleet(provinceName); + + public IOrderDefinedContext GetReference(out OrderReference order) + { + order = new OrderReference(this.Builder, this.Order); + return this; } } } diff --git a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs index 26be408..267acbd 100644 --- a/MultiversalDiplomacyTests/TestCaseBuilderTest.cs +++ b/MultiversalDiplomacyTests/TestCaseBuilderTest.cs @@ -1,3 +1,4 @@ +using MultiversalDiplomacy.Adjudicate; using MultiversalDiplomacy.Model; using MultiversalDiplomacy.Orders; @@ -112,4 +113,47 @@ class TestCaseBuilderTest Assert.That(orders.Where(OrderForProvince("London")), Is.Empty, "Unexpected order"); } + + [Test] + public void BuilderProvidesReferencesForValidation() + { + IPhaseAdjudicator rubberStamp = new TestAdjudicator(TestAdjudicator.RubberStamp); + + TestCaseBuilder setup = new TestCaseBuilder(World.WithStandardMap().WithInitialSeason()); + setup["Germany"] + .Army("Mun").Holds().GetReference(out var orderMun); + + Assert.That(orderMun, Is.Not.Null, "Expected order reference"); + Assert.That( + orderMun.Order.Power, + Is.EqualTo(setup.World.GetPower("Germany")), + "Wrong power"); + Assert.That( + orderMun.Order.Unit.Location, + Is.EqualTo(setup.World.GetLand("Mun")), + "Wrong unit"); + + Assert.That( + () => orderMun.Validation, + Throws.Exception, + "Validation property should be inaccessible before validation actually happens"); + setup.ValidateOrders(rubberStamp); + Assert.That( + () => orderMun.Validation, + Throws.Nothing, + "Validation property should be accessible after validation"); + + Assert.That( + orderMun.Validation.Order, + Is.EqualTo(orderMun.Order), + "Validation for wrong order"); + Assert.That( + orderMun.Validation.Valid, + Is.True, + "Unexpected validation result"); + Assert.That( + orderMun.Validation.Reason, + Is.EqualTo(ValidationReason.Valid), + "Unexpected validation reason"); + } }