using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using Microsoft.EntityFrameworkCore; namespace ClaudeDo.Data.Tests; public sealed class SshWorkspaceTests : IDisposable { private readonly string _dbPath; private readonly DbContextOptions _options; public SshWorkspaceTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_ssh_{Guid.NewGuid():N}.db"); _options = new DbContextOptionsBuilder().UseSqlite($"Data Source={_dbPath}").Options; using var ctx = new ClaudeDoDbContext(_options); ctx.Database.EnsureCreated(); } public void Dispose() { foreach (var suffix in new[] { "", "-wal", "-shm" }) try { File.Delete(_dbPath + suffix); } catch { } } [Theory] [InlineData("VPS", "vps", "/home/mika/app", null)] [InlineData("VPS", "mika@vps.example.com", "~/app", null)] [InlineData("VPS", "vps", "~", null)] [InlineData("", "vps", "/x", "name")] [InlineData("VPS", "", "/x", "host")] [InlineData("VPS", "-oProxyCommand=calc", "/x", "host")] [InlineData("VPS", "vps host", "/x", "host")] [InlineData("VPS", "vps", "relative/path", "path")] [InlineData("VPS", "vps", "/it's", "path")] [InlineData("VPS", "vps", "/a\nb", "path")] public void Validate_ReturnsFieldOfFirstProblem(string name, string host, string path, string? expected) => Assert.Equal(expected, SshWorkspace.Validate(new SshWorkspace("id", name, host, path))); [Theory] [InlineData("/home/mika/app", "'/home/mika/app'")] [InlineData("~", "~")] [InlineData("~/app dir", "~/'app dir'")] public void QuoteRemotePath_KeepsTildeExpandable(string path, string expected) => Assert.Equal(expected, SshWorkspace.QuoteRemotePath(path)); [Fact] public async Task SetThenGet_RoundTrips_AndSurvivesGeneralSettingsUpdate() { var ws = new SshWorkspace("w1", "VPS", "vps", "/srv/app"); await using (var ctx = new ClaudeDoDbContext(_options)) await new AppSettingsRepository(ctx).SetSshWorkspacesAsync([ws]); // A general settings save copies named fields only; it must not clobber the workspaces. await using (var ctx = new ClaudeDoDbContext(_options)) await new AppSettingsRepository(ctx).UpdateAsync(new AppSettingsEntity()); await using (var ctx = new ClaudeDoDbContext(_options)) Assert.Equal([ws], await new AppSettingsRepository(ctx).GetSshWorkspacesAsync()); } [Fact] public async Task Get_WithCorruptJson_ReturnsEmpty() { await using (var ctx = new ClaudeDoDbContext(_options)) { await new AppSettingsRepository(ctx).GetAsync(); await ctx.Database.ExecuteSqlRawAsync("UPDATE app_settings SET ssh_workspaces = 'not json'"); } await using (var ctx = new ClaudeDoDbContext(_options)) Assert.Empty(await new AppSettingsRepository(ctx).GetSshWorkspacesAsync()); } }