Files
the-living-world/tests/TheLivingWorld.Tests/WorldSimulationHostTests.cs
T

116 lines
3.9 KiB
C#

using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using TheLivingWorld.Api.Simulation;
using TheLivingWorld.Api.Storage;
using TheLivingWorld.Core.Contracts;
using TheLivingWorld.Core.Simulation;
namespace TheLivingWorld.Tests;
public sealed class WorldSimulationHostTests : IDisposable
{
private readonly string _root = Path.Combine(Path.GetTempPath(), $"tlw-sim-{Guid.NewGuid():n}");
private readonly WorldStore _store;
private readonly WorldSimulationHost _host;
public WorldSimulationHostTests()
{
_store = new WorldStore(
Options.Create(new WorldStorageOptions { RootDirectory = _root }),
NullLogger<WorldStore>.Instance);
_host = new WorldSimulationHost(_store, NullLogger<WorldSimulationHost>.Instance);
}
[Fact]
public async Task Attach_hydrates_legacy_worlds_without_catch_up_from_created_at()
{
var legacy = new WorldSummaryDto
{
Id = "legacy-11111111",
Name = "Legacy",
Latitude = 31.9,
Longitude = -100.5,
SizeMeters = 10_000,
Status = WorldStatus.Ready,
CreatedAt = DateTimeOffset.UtcNow - TimeSpan.FromDays(30),
};
await _store.SaveSummaryAsync(legacy);
// Mimic host startup hydration for worlds that lack a clock.
var seeded = legacy with
{
Clock = WorldSimulation.DefaultClock(),
LastTickedAt = DateTimeOffset.UtcNow,
};
await _store.SaveSummaryAsync(seeded);
_host.Attach(seeded with { LastTickedAt = null }); // Attach without prior tick → no catch-up
// Force attach path used for legacy: Create with catchUp false via AttachSeeded behaviour —
// Attach with LastTickedAt null means catchUp is false.
var clock = _host.TryGetClock(legacy.Id);
Assert.NotNull(clock);
Assert.Equal(GameTime.DefaultStart, clock.GameTime);
}
[Fact]
public async Task UpdateClock_changes_pause_and_Detach_stops_simulation()
{
var summary = new WorldSummaryDto
{
Id = "ready-22222222",
Name = "Ready",
Latitude = 31.9,
Longitude = -100.5,
SizeMeters = 10_000,
Status = WorldStatus.Ready,
CreatedAt = DateTimeOffset.UtcNow,
Clock = WorldSimulation.DefaultClock(),
LastTickedAt = DateTimeOffset.UtcNow,
};
await _store.SaveSummaryAsync(summary);
_host.Attach(summary);
var paused = _host.UpdateClock(summary.Id, new UpdateClockRequest { Paused = true });
Assert.True(paused.Paused);
var scaled = _host.UpdateClock(summary.Id, new UpdateClockRequest { TimeScale = 3 });
Assert.Equal(3, scaled.TimeScale);
Assert.True(scaled.Paused);
_host.Detach(summary.Id);
Assert.False(_host.IsAttached(summary.Id));
Assert.Null(_host.TryGetClock(summary.Id));
}
[Fact]
public void Overlay_strips_last_ticked_at_for_api()
{
var summary = new WorldSummaryDto
{
Id = "ready-33333333",
Name = "Ready",
Latitude = 31.9,
Longitude = -100.5,
SizeMeters = 10_000,
Status = WorldStatus.Ready,
CreatedAt = DateTimeOffset.UtcNow,
Clock = WorldSimulation.DefaultClock(),
LastTickedAt = DateTimeOffset.UtcNow,
};
_host.Attach(summary);
var overlaid = _host.Overlay(summary);
Assert.NotNull(overlaid.Clock);
Assert.Null(overlaid.LastTickedAt);
}
public void Dispose()
{
foreach (var id in new[] { "legacy-11111111", "ready-22222222", "ready-33333333" })
_host.Detach(id);
if (Directory.Exists(_root)) Directory.Delete(_root, recursive: true);
GC.SuppressFinalize(this);
}
}