Add order references to test case builder

This allows assertions to be made more easily against orders in a test case.
This commit is contained in:
Jaculabilis 2022-03-22 20:13:49 -07:00
parent be3f6a527f
commit 00cac2cb89
4 changed files with 198 additions and 16 deletions

View File

@ -0,0 +1,69 @@
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Orders;
using NUnit.Framework;
namespace MultiversalDiplomacyTests;
/// <summary>
/// An object that provides a view into an order's fate during a test case.
/// </summary>
public class OrderReference<OrderType> where OrderType : Order
{
private TestCaseBuilder Builder { get; }
/// <summary>
/// The order.
/// </summary>
public OrderType Order { get; }
/// <summary>
/// The validation result for the order. Throws if validation has not occurred.
/// </summary>
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();
}
}
/// <summary>
/// The order that replaced this order, if any. Throws if validation has not occurred.
/// </summary>
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;
}
}

View File

@ -6,6 +6,9 @@ namespace MultiversalDiplomacyTests;
public class TestAdjudicator : IPhaseAdjudicator
{
public static Func<World, List<Order>, List<OrderValidation>> RubberStamp =
(world, orders) => orders.Select(o => o.Validate(ValidationReason.Valid)).ToList();
private Func<World, List<Order>, List<OrderValidation>> ValidateOrdersCallback;
public TestAdjudicator(

View File

@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using MultiversalDiplomacy.Adjudicate;
using MultiversalDiplomacy.Model;
using MultiversalDiplomacy.Orders;
@ -44,12 +45,12 @@ public class TestCaseBuilder
/// <summary>
/// Give the unit a hold order.
/// </summary>
public IPowerContext Holds();
public IOrderDefinedContext<HoldOrder> Holds();
/// <summary>
/// Give the unit a move order.
/// </summary>
public IPowerContext MovesTo(string provinceName, string? coast = null);
public IOrderDefinedContext<MoveOrder> MovesTo(string provinceName, string? coast = null);
/// <summary>
/// Give the unit a convoy order.
@ -89,7 +90,7 @@ public class TestCaseBuilder
/// <summary>
/// Define the destination of the convoy order.
/// </summary>
public IPowerContext To(string provinceName);
public IOrderDefinedContext<ConvoyOrder> To(string provinceName);
}
/// <summary>
@ -119,18 +120,47 @@ public class TestCaseBuilder
/// <summary>
/// Give the unit an order to support the target's hold order.
/// </summary>
public IPowerContext Hold();
public IOrderDefinedContext<SupportHoldOrder> Hold();
/// <summary>
/// Give the unit an order to support the target's move order.
/// </summary>
public IPowerContext MoveTo(string provinceName, string? coast = null);
public IOrderDefinedContext<SupportMoveOrder> MoveTo(string provinceName, string? coast = null);
}
/// <summary>
/// Context for additional operations on a defined order or defining another order. This
/// context mimics the <see cref="IPowerContext"/> with additional functionality related to
/// the order that was just defined.
/// </summary>
public interface IOrderDefinedContext<OrderType> where OrderType : Order
{
/// <summary>
/// Get the context for defining the orders for another power.
/// </summary>
public IPowerContext this[string powerName] { get; }
/// <summary>
/// Define an order for a new army in a province.
/// </summary>
public IUnitContext Army(string provinceName);
/// <summary>
/// Define an order for a new fleet in a province, optionally on a specific coast.
/// </summary>
public IUnitContext Fleet(string provinceName, string? coast = null);
/// <summary>
/// Save a reference to the order just defined.
/// </summary>
public IOrderDefinedContext<OrderType> GetReference(out OrderReference<OrderType> order);
}
public World World { get; private set; }
public ReadOnlyCollection<Order> Orders { get; }
private List<Order> OrderList;
private Season Season;
public List<OrderValidation>? ValidationResults { get; private set; }
/// <summary>
/// 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<Order>();
this.OrderList = new();
this.Orders = new(this.OrderList);
this.Season = season ?? this.World.Seasons.First();
this.ValidationResults = null;
}
/// <summary>
@ -188,6 +219,12 @@ public class TestCaseBuilder
return newUnit;
}
public List<OrderValidation> 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
/// <summary>
/// Order a unit to hold.
/// </summary>
public IPowerContext Holds()
public IOrderDefinedContext<HoldOrder> Holds()
{
HoldOrder order = new HoldOrder(this.PowerContext.Power, this.Unit);
this.Builder.OrderList.Add(order);
return this.PowerContext;
return new OrderDefinedContext<HoldOrder>(this, order);
}
/// <summary>
/// Order a unit to move to a destination.
/// </summary>
public IPowerContext MovesTo(string provinceName, string? coast = null)
public IOrderDefinedContext<MoveOrder> 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<MoveOrder>(this, moveOrder);
}
public IConvoyContext Convoys
@ -326,7 +363,7 @@ public class TestCaseBuilder
this.Target = target;
}
public IPowerContext To(string provinceName)
public IOrderDefinedContext<ConvoyOrder> 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<ConvoyOrder>(this.UnitContext, order);
}
}
@ -394,17 +431,17 @@ public class TestCaseBuilder
this.Target = target;
}
public IPowerContext Hold()
public IOrderDefinedContext<SupportHoldOrder> Hold()
{
SupportHoldOrder order = new SupportHoldOrder(
this.PowerContext.Power,
this.UnitContext.Unit,
this.Target);
this.Builder.OrderList.Add(order);
return this.PowerContext;
return new OrderDefinedContext<SupportHoldOrder>(this.UnitContext, order);
}
public IPowerContext MoveTo(string provinceName, string? coast = null)
public IOrderDefinedContext<SupportMoveOrder> 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<SupportMoveOrder>(this.UnitContext, order);
}
}
private class OrderDefinedContext<OrderType> : IOrderDefinedContext<OrderType> 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<OrderType> GetReference(out OrderReference<OrderType> order)
{
order = new OrderReference<OrderType>(this.Builder, this.Order);
return this;
}
}
}

View File

@ -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<OrderValidation>(
() => orderMun.Validation,
Throws.Exception,
"Validation property should be inaccessible before validation actually happens");
setup.ValidateOrders(rubberStamp);
Assert.That<OrderValidation>(
() => 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");
}
}