using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Ui.ViewModels.Modals.Settings; using Microsoft.EntityFrameworkCore; using Xunit; namespace ClaudeDo.Ui.Tests.ViewModels; public sealed class TicketSettingsTabViewModelTests : IDisposable { private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_ticketsettings_{Guid.NewGuid():N}.db"); public TicketSettingsTabViewModelTests() { using var ctx = NewContext(); ctx.Database.EnsureCreated(); ctx.Lists.Add(new ListEntity { Id = "l1", Name = "Hub", CreatedAt = DateTime.UtcNow, SortOrder = 0 }); ctx.Lists.Add(new ListEntity { Id = "l2", Name = "Inbox", CreatedAt = DateTime.UtcNow, SortOrder = 1 }); ctx.SaveChanges(); } public void Dispose() { foreach (var suffix in new[] { "", "-wal", "-shm" }) try { File.Delete(_dbPath + suffix); } catch { } } private ClaudeDoDbContext NewContext() => new(new DbContextOptionsBuilder() .UseSqlite($"Data Source={_dbPath}").Options); private sealed class Worker : StubWorkerClient { public TicketSettingsDto? Settings { get; set; } public List SavedFallbacks { get; } = new(); public override Task GetTicketSettingsAsync() => Task.FromResult(Settings); public override Task SetTicketFallbackListAsync(string? listId) { SavedFallbacks.Add(listId); return Task.CompletedTask; } public List SavedAzdoTokens { get; } = new(); public AzureDevOpsConnectionDto? AzdoConnection { get; set; } public override Task GetAzureDevOpsSettingsAsync() => Task.FromResult(new(false)); public override Task SetAzureDevOpsTokenAsync(string token) { SavedAzdoTokens.Add(token); return Task.CompletedTask; } public override Task TestAzureDevOpsConnectionAsync() => Task.FromResult(AzdoConnection); } [Fact] public async Task Save_writes_a_typed_azdo_token_once_and_never_keeps_it_in_the_field() { var worker = new Worker { Settings = new TicketSettingsDto("http://api", true, null) }; var vm = new TicketSettingsTabViewModel(worker, new TestDbFactory(NewContext)); await vm.LoadAsync(); Assert.False(vm.AzdoTokenSet); vm.AzdoToken = "azdo-pat"; await vm.SaveAsync(); await vm.SaveAsync(); Assert.Equal(["azdo-pat"], worker.SavedAzdoTokens); Assert.Equal("", vm.AzdoToken); Assert.True(vm.AzdoTokenSet); } [Fact] public async Task Azdo_test_reports_the_server_error_verbatim() { var worker = new Worker { Settings = new TicketSettingsDto("http://api", true, null), AzdoConnection = new AzureDevOpsConnectionDto(false, null, null, "Azure DevOps 401: PAT abgelaufen"), }; var vm = new TicketSettingsTabViewModel(worker, new TestDbFactory(NewContext)); await vm.LoadAsync(); await vm.TestAzdoConnectionCommand.ExecuteAsync(null); Assert.Equal("Azure DevOps 401: PAT abgelaufen", vm.AzdoStatusMessage); } [Fact] public async Task Load_offers_none_plus_every_list_and_selects_the_stored_fallback() { var worker = new Worker { Settings = new TicketSettingsDto("http://api", true, "l2") }; var vm = new TicketSettingsTabViewModel(worker, new TestDbFactory(NewContext)); await vm.LoadAsync(); Assert.Equal([null, "l1", "l2"], vm.FallbackLists.Select(o => o.Id)); Assert.Equal("l2", vm.SelectedFallbackList?.Id); } [Fact] public async Task Save_writes_the_selected_fallback_and_none_as_null() { var worker = new Worker { Settings = new TicketSettingsDto("http://api", true, "l2") }; var vm = new TicketSettingsTabViewModel(worker, new TestDbFactory(NewContext)); await vm.LoadAsync(); vm.SelectedFallbackList = vm.FallbackLists[0]; await vm.SaveAsync(); Assert.Equal([null], worker.SavedFallbacks); } [Fact] public async Task Save_does_not_touch_the_fallback_when_the_load_failed() { var worker = new Worker { Settings = null }; var vm = new TicketSettingsTabViewModel(worker, new TestDbFactory(NewContext)); await vm.LoadAsync(); await vm.SaveAsync(); Assert.Empty(worker.SavedFallbacks); } }