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
This commit is contained in:
Joseph Doherty
2026-01-19 17:33:39 -05:00
parent 54d4fd0eb6
commit c055bc6c78
3 changed files with 99 additions and 0 deletions
@@ -0,0 +1,40 @@
namespace JdeScoping.ConfigManager.Services;
/// <summary>
/// Real file system implementation.
/// </summary>
public class FileSystem : IFileSystem
{
public bool FileExists(string path) => File.Exists(path);
public bool DirectoryExists(string path) => Directory.Exists(path);
public async Task<string> 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<string[]> 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);
}
@@ -0,0 +1,19 @@
namespace JdeScoping.ConfigManager.Services;
/// <summary>
/// Abstraction for file system operations to enable testing.
/// </summary>
public interface IFileSystem
{
bool FileExists(string path);
bool DirectoryExists(string path);
Task<string> ReadAllTextAsync(string path, CancellationToken ct = default);
Task WriteAllTextAsync(string path, string content, CancellationToken ct = default);
Task<string[]> 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);
}
@@ -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();
}
}