using System.Security.AccessControl; using System.Security.Principal; using ClaudeDo.Installer.Checks; using ClaudeDo.Installer.Core; namespace ClaudeDo.Installer.Tests.Checks; public sealed class WriteAccessCheckTests : IDisposable { private readonly List _cleanupDirs = new(); private string TempDir() { var dir = Path.Combine(Path.GetTempPath(), $"cdwriteaccess_{Guid.NewGuid():N}"); Directory.CreateDirectory(dir); _cleanupDirs.Add(dir); return dir; } public void Dispose() { foreach (var dir in _cleanupDirs) { try { Directory.Delete(dir, recursive: true); } catch { } } } [Fact] public async Task Writable_directory_reports_ok() { var dir = TempDir(); var check = new WriteAccessCheck(); var result = await check.RunAsync(new InstallContext { InstallDirectory = dir }, CancellationToken.None); Assert.Equal(CheckStatus.Ok, result.Status); } [Fact] public async Task NonExistent_path_under_writable_parent_reports_ok() { var parent = TempDir(); var target = Path.Combine(parent, "does", "not", "exist"); var check = new WriteAccessCheck(); var result = await check.RunAsync(new InstallContext { InstallDirectory = target }, CancellationToken.None); Assert.Equal(CheckStatus.Ok, result.Status); } [Fact] public async Task Unwritable_directory_reports_failed_with_no_leftover_file() { var dir = TempDir(); var dirInfo = new DirectoryInfo(dir); var identity = WindowsIdentity.GetCurrent().User!; var denyRule = new FileSystemAccessRule(identity, FileSystemRights.CreateFiles | FileSystemRights.WriteData, AccessControlType.Deny); var acl = dirInfo.GetAccessControl(); acl.AddAccessRule(denyRule); dirInfo.SetAccessControl(acl); try { var check = new WriteAccessCheck(); var result = await check.RunAsync(new InstallContext { InstallDirectory = dir }, CancellationToken.None); Assert.Equal(CheckStatus.Failed, result.Status); Assert.Equal(CheckSeverity.Error, result.Severity); Assert.Contains(dir, result.Message); Assert.Empty(Directory.GetFiles(dir)); } finally { acl.RemoveAccessRule(denyRule); dirInfo.SetAccessControl(acl); } } }