Use in-memory runtime state with SQLite write-through

This commit is contained in:
2026-02-24 23:13:20 +01:00
parent f212636feb
commit 5c199b4468
4 changed files with 743 additions and 455 deletions

View File

@@ -238,26 +238,41 @@ public sealed class GameServiceTests
Assert.False(service.CreateSkill(ownerSession, Guid.NewGuid(), new CreateSkillRequest("Stealth", "2D+1")).Succeeded);
Assert.False(service.CreateSkill(otherSession, ownerCharacter.Id, new CreateSkillRequest("Stealth", "2D+1")).Succeeded);
var ownerUser = harness.Db.Users.Single(u => u.UsernameNormalized == "OWNER");
ownerUser.ActiveCharacterId = Guid.NewGuid();
harness.Db.SaveChanges();
using (var db = harness.CreateDbContext())
{
var ownerUser = db.Users.Single(u => u.UsernameNormalized == "OWNER");
ownerUser.ActiveCharacterId = Guid.NewGuid();
db.SaveChanges();
}
var staleMe = GetValue(service.GetMe(ownerSession));
using var staleMeHarness = CreateHarnessFromPath(harness.DbPath, 2, 3, 4);
var staleMeService = staleMeHarness.Service;
var staleMe = GetValue(staleMeService.GetMe(ownerSession));
Assert.Null(staleMe.ActiveCharacterId);
Assert.Null(staleMe.CurrentCampaignId);
Assert.True(service.ActivateCharacter(ownerSession, ownerCharacter.Id).Succeeded);
var activeMe = GetValue(service.GetMe(ownerSession));
Assert.True(staleMeService.ActivateCharacter(ownerSession, ownerCharacter.Id).Succeeded);
var activeMe = GetValue(staleMeService.GetMe(ownerSession));
Assert.Equal(ownerCharacter.Id, activeMe.ActiveCharacterId);
Assert.Equal(campaign.Id, activeMe.CurrentCampaignId);
var staleOwner = harness.Db.Users.Single(u => u.Id == ownerUser.Id);
staleOwner.ActiveCharacterId = Guid.NewGuid();
harness.Db.SaveChanges();
using (var db = harness.CreateDbContext())
{
var staleOwner = db.Users.Single(u => u.UsernameNormalized == "OWNER");
staleOwner.ActiveCharacterId = Guid.NewGuid();
db.SaveChanges();
}
var staleCurrentCampaign = service.GetCurrentCampaignCharacters(ownerSession);
using var staleCurrentHarness = CreateHarnessFromPath(harness.DbPath, 2, 3, 4);
var staleCurrentService = staleCurrentHarness.Service;
var staleCurrentCampaign = staleCurrentService.GetCurrentCampaignCharacters(ownerSession);
Assert.False(staleCurrentCampaign.Succeeded);
Assert.Null(harness.Db.Users.Single(u => u.Id == ownerUser.Id).ActiveCharacterId);
using (var db = harness.CreateDbContext())
{
Assert.Null(db.Users.Single(u => u.UsernameNormalized == "OWNER").ActiveCharacterId);
}
var skill = GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, new CreateSkillRequest("Stealth", "2D+1")));
Assert.False(service.UpdateSkill(ownerSession, skill.Id, new UpdateSkillRequest("", "2D+1")).Succeeded);
@@ -265,11 +280,15 @@ public sealed class GameServiceTests
Assert.False(service.UpdateSkill(ownerSession, skill.Id, new UpdateSkillRequest("Stealth", "bad")).Succeeded);
Assert.False(service.RollSkill(string.Empty, skill.Id, new RollSkillRequest("public")).Succeeded);
var mutableSkill = harness.Db.Skills.Single(s => s.Id == skill.Id);
mutableSkill.DiceRollDefinition = "bad";
harness.Db.SaveChanges();
using (var db = harness.CreateDbContext())
{
var mutableSkill = db.Skills.Single(s => s.Id == skill.Id);
mutableSkill.DiceRollDefinition = "bad";
db.SaveChanges();
}
Assert.False(service.RollSkill(ownerSession, skill.Id, new RollSkillRequest("public")).Succeeded);
using var invalidExpressionHarness = CreateHarnessFromPath(harness.DbPath, 2, 3, 4);
Assert.False(invalidExpressionHarness.Service.RollSkill(ownerSession, skill.Id, new RollSkillRequest("public")).Succeeded);
Assert.False(service.GetCampaignLog(string.Empty, campaign.Id).Succeeded);
}
@@ -346,15 +365,28 @@ public sealed class GameServiceTests
private static ServiceHarness CreateHarness(IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
{
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-servicetests-{Guid.NewGuid():N}.db");
return CreateHarnessFromPath(dbPath, passwordHasher, rollValues);
}
private static ServiceHarness CreateHarnessFromPath(string dbPath, params int[] rollValues)
{
return CreateHarnessFromPath(dbPath, new PasswordHasher<UserAccount>(), rollValues);
}
private static ServiceHarness CreateHarnessFromPath(string dbPath, IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
{
var options = new DbContextOptionsBuilder<RpgRollerDbContext>()
.UseSqlite($"Data Source={dbPath}")
.Options;
var db = new RpgRollerDbContext(options);
db.Database.EnsureCreated();
using (var db = new RpgRollerDbContext(options))
{
db.Database.EnsureCreated();
}
var service = new GameService(db, passwordHasher, new FixedDiceRoller(rollValues));
return new ServiceHarness(service, db);
var factory = new SqliteDbContextFactory(dbPath);
var service = new GameService(factory, passwordHasher, new FixedDiceRoller(rollValues));
return new ServiceHarness(service, factory, dbPath);
}
private static T GetValue<T>(ServiceResult<T> result)
@@ -382,20 +414,47 @@ public sealed class GameServiceTests
private sealed class ServiceHarness : IDisposable
{
private readonly RpgRollerDbContext m_Db;
private readonly SqliteDbContextFactory m_Factory;
public ServiceHarness(GameService service, RpgRollerDbContext db)
public ServiceHarness(GameService service, SqliteDbContextFactory factory, string dbPath)
{
Service = service;
m_Db = db;
m_Factory = factory;
DbPath = dbPath;
}
public GameService Service { get; }
public RpgRollerDbContext Db => m_Db;
public string DbPath { get; }
public void Dispose()
{
m_Factory.Dispose();
}
public RpgRollerDbContext CreateDbContext()
{
return m_Factory.CreateDbContext();
}
}
private sealed class SqliteDbContextFactory : IDbContextFactory<RpgRollerDbContext>, IDisposable
{
private readonly DbContextOptions<RpgRollerDbContext> m_Options;
public SqliteDbContextFactory(string dbPath)
{
m_Options = new DbContextOptionsBuilder<RpgRollerDbContext>()
.UseSqlite($"Data Source={dbPath}")
.Options;
}
public RpgRollerDbContext CreateDbContext()
{
return new RpgRollerDbContext(m_Options);
}
public void Dispose()
{
m_Db.Dispose();
}
}