From c055bc6c7853efbc984418dd70469653872dbce1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 19 Jan 2026 17:33:39 -0500 Subject: [PATCH] feat(configmanager): add IFileSystem abstraction Add file system abstraction to enable testability for file operations. - IFileSystem interface with common file operations - FileSystem implementation wrapping System.IO - Unit tests for FileExists functionality --- .../Services/FileSystem.cs | 40 +++++++++++++++++++ .../Services/IFileSystem.cs | 19 +++++++++ .../Services/FileSystemTests.cs | 40 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 NEW/src/Utils/JdeScoping.ConfigManager/Services/FileSystem.cs create mode 100644 NEW/src/Utils/JdeScoping.ConfigManager/Services/IFileSystem.cs create mode 100644 NEW/tests/JdeScoping.ConfigManager.Tests/Services/FileSystemTests.cs diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/FileSystem.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/FileSystem.cs new file mode 100644 index 0000000..00f5de1 --- /dev/null +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/FileSystem.cs @@ -0,0 +1,40 @@ +namespace JdeScoping.ConfigManager.Services; + +/// +/// Real file system implementation. +/// +public class FileSystem : IFileSystem +{ + public bool FileExists(string path) => File.Exists(path); + + public bool DirectoryExists(string path) => Directory.Exists(path); + + public async Task ReadAllTextAsync(string path, CancellationToken ct = default) + => await File.ReadAllTextAsync(path, ct); + + public async Task WriteAllTextAsync(string path, string content, CancellationToken ct = default) + => await File.WriteAllTextAsync(path, content, ct); + + public Task GetFilesAsync(string directory, string pattern, CancellationToken ct = default) + => Task.FromResult(Directory.GetFiles(directory, pattern)); + + public async Task CopyFileAsync(string source, string destination, CancellationToken ct = default) + { + var content = await File.ReadAllBytesAsync(source, ct); + await File.WriteAllBytesAsync(destination, content, ct); + } + + public Task DeleteFileAsync(string path, CancellationToken ct = default) + { + File.Delete(path); + return Task.CompletedTask; + } + + public string GetDirectoryName(string path) => Path.GetDirectoryName(path) ?? string.Empty; + + public string GetFileName(string path) => Path.GetFileName(path); + + public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path); + + public string Combine(params string[] paths) => Path.Combine(paths); +} diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/IFileSystem.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IFileSystem.cs new file mode 100644 index 0000000..b5d265f --- /dev/null +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IFileSystem.cs @@ -0,0 +1,19 @@ +namespace JdeScoping.ConfigManager.Services; + +/// +/// Abstraction for file system operations to enable testing. +/// +public interface IFileSystem +{ + bool FileExists(string path); + bool DirectoryExists(string path); + Task ReadAllTextAsync(string path, CancellationToken ct = default); + Task WriteAllTextAsync(string path, string content, CancellationToken ct = default); + Task GetFilesAsync(string directory, string pattern, CancellationToken ct = default); + Task CopyFileAsync(string source, string destination, CancellationToken ct = default); + Task DeleteFileAsync(string path, CancellationToken ct = default); + string GetDirectoryName(string path); + string GetFileName(string path); + string GetFileNameWithoutExtension(string path); + string Combine(params string[] paths); +} diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/Services/FileSystemTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/FileSystemTests.cs new file mode 100644 index 0000000..64a33ea --- /dev/null +++ b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/FileSystemTests.cs @@ -0,0 +1,40 @@ +using JdeScoping.ConfigManager.Services; + +namespace JdeScoping.ConfigManager.Tests.Services; + +public class FileSystemTests +{ + [Fact] + public void FileExists_WithExistingFile_ReturnsTrue() + { + // Arrange + var sut = new FileSystem(); + var tempFile = Path.GetTempFileName(); + + try + { + // Act + var result = sut.FileExists(tempFile); + + // Assert + result.ShouldBeTrue(); + } + finally + { + File.Delete(tempFile); + } + } + + [Fact] + public void FileExists_WithNonExistingFile_ReturnsFalse() + { + // Arrange + var sut = new FileSystem(); + + // Act + var result = sut.FileExists("/nonexistent/path/file.txt"); + + // Assert + result.ShouldBeFalse(); + } +}