tests/Application/InventoryLayoutServiceTests.cs

Unit tests for InventoryLayoutService. They verify transfer rules: source membership required, failed placement leaves source unchanged, and successful transfer updates source and target snapshots. Includes a fake IItemShapeCatalog and helpers to create items and empty inventories.

File Access
using Hexagon.V2.Application;
using Hexagon.V2.Domain;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Hexagon.V2.Tests.Application;

[TestClass]
public sealed class InventoryLayoutServiceTests
{
	[TestMethod]
	public void TransferRequiresSourceMembership()
	{
		var catalog = new FakeCatalog();
		var item = CreateItem();
		catalog.Add( item, new ItemShape( 1, 1 ) );
		var service = new InventoryLayoutService( catalog );

		var result = service.Transfer( EmptyInventory(), EmptyInventory(), item, 0, 0 );

		Assert.IsTrue( result.Failed );
	}

	[TestMethod]
	public void FailedTargetPlacementLeavesSourceUnchanged()
	{
		var catalog = new FakeCatalog();
		var item = CreateItem();
		var blocker = CreateItem();
		catalog.Add( item, new ItemShape( 1, 1 ) );
		catalog.Add( blocker, new ItemShape( 1, 1 ) );
		var service = new InventoryLayoutService( catalog );
		var source = EmptyInventory() with { Placements = [new InventoryPlacement( item.Id, 0, 0 )] };
		var target = EmptyInventory() with { Placements = [new InventoryPlacement( blocker.Id, 0, 0 )] };

		var result = service.Transfer( source, target, item, 0, 0 );

		Assert.IsTrue( result.Failed );
		Assert.IsNotNull( source.Find( item.Id ) );
	}

	[TestMethod]
	public void SuccessfulTransferChangesBothSnapshots()
	{
		var catalog = new FakeCatalog();
		var item = CreateItem();
		catalog.Add( item, new ItemShape( 1, 1 ) );
		var service = new InventoryLayoutService( catalog );
		var source = EmptyInventory() with { Placements = [new InventoryPlacement( item.Id, 0, 0 )] };
		var target = EmptyInventory();

		var result = service.Transfer( source, target, item, 1, 2 );

		Assert.IsTrue( result.Succeeded );
		Assert.IsNull( result.Value.Source.Find( item.Id ) );
		Assert.AreEqual( new InventoryPlacement( item.Id, 1, 2 ), result.Value.Target.Find( item.Id ) );
	}

	private static ItemRecord CreateItem() => new()
	{
		Id = ItemId.New(),
		Definition = new DefinitionId( "test.item" )
	};

	private static InventoryRecord EmptyInventory() => new()
	{
		Id = InventoryId.New(),
		Owner = InventoryOwner.Character( CharacterId.New() ),
		Width = 4,
		Height = 4
	};

	private sealed class FakeCatalog : IItemShapeCatalog
	{
		private readonly Dictionary<DefinitionId, ItemShape> _definitions = new();
		private readonly Dictionary<ItemId, ItemShape> _items = new();

		public void Add( ItemRecord item, ItemShape shape )
		{
			_definitions[item.Definition] = shape;
			_items[item.Id] = shape;
		}

		public bool TryGetShape( DefinitionId definitionId, out ItemShape shape ) =>
			_definitions.TryGetValue( definitionId, out shape );

		public bool TryGetShape( ItemId itemId, out ItemShape shape ) =>
			_items.TryGetValue( itemId, out shape );
	}
}