using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using Microsoft.EntityFrameworkCore; namespace ClaudeDo.Data.Tests; public sealed class PowerUseTests : IDisposable { private readonly string _dbPath; private readonly ClaudeDoDbContext _ctx; private const string ListId = "l1"; public PowerUseTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_poweruse_{Guid.NewGuid():N}.db"); var options = new DbContextOptionsBuilder() .UseSqlite($"Data Source={_dbPath}") .Options; _ctx = new ClaudeDoDbContext(options); _ctx.Database.EnsureCreated(); _ctx.Lists.Add(new ListEntity { Id = ListId, Name = "Test", CreatedAt = DateTime.UtcNow }); _ctx.SaveChanges(); } public void Dispose() { _ctx.Dispose(); foreach (var suffix in new[] { "", "-wal", "-shm" }) try { File.Delete(_dbPath + suffix); } catch { } } private ClaudeDoDbContext Reload() => new( new DbContextOptionsBuilder() .UseSqlite(_ctx.Database.GetConnectionString()).Options); [Fact] public async Task ListConfig_PowerUseAllowed_DefaultsToFalse() { await new ListRepository(_ctx).SetConfigAsync(new ListConfigEntity { ListId = ListId, Model = "sonnet" }); using var reload = Reload(); var cfg = await new ListRepository(reload).GetConfigAsync(ListId); Assert.False(cfg!.PowerUseAllowed); } [Fact] public async Task ListConfig_PowerUseAllowed_RoundtripsTrue() { await new ListRepository(_ctx).SetConfigAsync(new ListConfigEntity { ListId = ListId, PowerUseAllowed = true }); using var reload = Reload(); var cfg = await new ListRepository(reload).GetConfigAsync(ListId); Assert.True(cfg!.PowerUseAllowed); } /// SetConfigAsync copies the incoming entity field by field onto the existing row. A field /// forgotten in that copy block reads as "silently reset on the next unrelated save", which is /// exactly how the ticket-project link broke before. Pin it for PowerUseAllowed. [Fact] public async Task ListConfig_UpdatingAnotherField_DoesNotResetPowerUseAllowed() { var repo = new ListRepository(_ctx); await repo.SetConfigAsync(new ListConfigEntity { ListId = ListId, PowerUseAllowed = true }); await repo.SetConfigAsync(new ListConfigEntity { ListId = ListId, PowerUseAllowed = true, VerifyCommand = "dotnet build" }); using var reload = Reload(); var cfg = await new ListRepository(reload).GetConfigAsync(ListId); Assert.True(cfg!.PowerUseAllowed); Assert.Equal("dotnet build", cfg.VerifyCommand); } [Fact] public async Task AppSettings_PowerUseDefaults_AreOffAndFourHandoffs() { var settings = await new AppSettingsRepository(_ctx).GetAsync(); Assert.False(settings.PowerUseEnabled); Assert.Equal(4, settings.ListHandlerMaxHandoffs); } [Fact] public async Task AppSettings_PowerUseFields_Roundtrip() { await new AppSettingsRepository(_ctx).GetAsync(); var row = await _ctx.AppSettings.FirstAsync(s => s.Id == AppSettingsEntity.SingletonId); row.PowerUseEnabled = true; row.ListHandlerMaxHandoffs = 40; await _ctx.SaveChangesAsync(); using var reload = Reload(); var settings = await new AppSettingsRepository(reload).GetAsync(); Assert.True(settings.PowerUseEnabled); Assert.Equal(40, settings.ListHandlerMaxHandoffs); } [Fact] public async Task AppSettings_WindowDefaults_AreEmptyAnd95Percent() { var settings = await new AppSettingsRepository(_ctx).GetAsync(); Assert.Null(settings.PowerUseWindowStart); Assert.Null(settings.PowerUseWindowEnd); Assert.Equal(95, settings.PowerUseHandoffPct); } /// UpdateAsync copies the incoming entity field by field onto the singleton row — a field /// missing from that block never persists, however correctly the hub filled the entity. That /// was true for PowerUseEnabled, ListHandlerMaxHandoffs and ModelPresets until 2026-09-02, so /// pin the whole PowerUse group through the real write path. [Fact] public async Task AppSettings_UpdateAsync_PersistsThePowerUseGroup() { var repo = new AppSettingsRepository(_ctx); var current = await repo.GetAsync(); current.PowerUseEnabled = true; current.PowerUseWindowStart = "22:00"; current.PowerUseWindowEnd = "07:00"; current.PowerUseHandoffPct = 88; current.ListHandlerMaxHandoffs = 40; await repo.UpdateAsync(current); using var reload = Reload(); var settings = await new AppSettingsRepository(reload).GetAsync(); Assert.True(settings.PowerUseEnabled); Assert.Equal("22:00", settings.PowerUseWindowStart); Assert.Equal("07:00", settings.PowerUseWindowEnd); Assert.Equal(88, settings.PowerUseHandoffPct); Assert.Equal(40, settings.ListHandlerMaxHandoffs); } [Fact] public async Task AppSettings_UpdateAsync_NormalizesAndClamps() { var repo = new AppSettingsRepository(_ctx); var current = await repo.GetAsync(); current.PowerUseWindowStart = "nonsense"; current.PowerUseWindowEnd = " 07:00 "; current.PowerUseHandoffPct = 5; await repo.UpdateAsync(current); using var reload = Reload(); var settings = await new AppSettingsRepository(reload).GetAsync(); Assert.Null(settings.PowerUseWindowStart); Assert.Equal("07:00", settings.PowerUseWindowEnd); Assert.Equal(50, settings.PowerUseHandoffPct); } }