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");
+ }
}