diff --git a/source/Core/CreativeCoders.Core/IO/DirectoryExtensions.cs b/source/Core/CreativeCoders.Core/IO/DirectoryExtensions.cs new file mode 100644 index 00000000..1345705e --- /dev/null +++ b/source/Core/CreativeCoders.Core/IO/DirectoryExtensions.cs @@ -0,0 +1,34 @@ +using System.IO.Abstractions; + +#nullable enable + +namespace CreativeCoders.Core.IO; + +public static class DirectoryExtensions +{ + public static void EnsureDirectoryExists(this IDirectory directory, string? directoryPath) + { + if (string.IsNullOrWhiteSpace(directoryPath)) + { + return; + } + + directory.CreateDirectory(directoryPath); + } + + public static void EnsureDirectoryForFileNameExists(this IDirectory directory, string? filePath) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + return; + } + + var directoryPath = directory.FileSystem.Path.GetDirectoryName(filePath); + if (string.IsNullOrWhiteSpace(directoryPath)) + { + return; + } + + directory.CreateDirectory(directoryPath); + } +} diff --git a/source/Core/CreativeCoders.Core/IO/IOServiceCollectionExtensions.cs b/source/Core/CreativeCoders.Core/IO/IOServiceCollectionExtensions.cs new file mode 100644 index 00000000..c592a0a4 --- /dev/null +++ b/source/Core/CreativeCoders.Core/IO/IOServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace CreativeCoders.Core.IO; + +[ExcludeFromCodeCoverage] +public static class IOServiceCollectionExtensions +{ + public static IServiceCollection AddFileSystem(this IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + + return services; + } +} diff --git a/source/Core/CreativeCoders.Core/Text/EnumerableStringExtensions.cs b/source/Core/CreativeCoders.Core/Text/EnumerableStringExtensions.cs index f3251fe8..695924c8 100644 --- a/source/Core/CreativeCoders.Core/Text/EnumerableStringExtensions.cs +++ b/source/Core/CreativeCoders.Core/Text/EnumerableStringExtensions.cs @@ -10,6 +10,7 @@ namespace CreativeCoders.Core.Text; public static class EnumerableStringExtensions { + [SuppressMessage("ReSharper", "NullableWarningSuppressionIsUsed")] public static Dictionary ToDictionary(this IEnumerable items, string separator, bool ignoreInvalidEntries = true) { @@ -27,7 +28,7 @@ public static Dictionary ToDictionary(this IEnumerable i return x != null; }) - .ToDictionary(x => x.Key, x => x.Value); + .ToDictionary(x => x!.Key, x => x!.Value); } [ExcludeFromCodeCoverage] diff --git a/tests/CreativeCoders.Core.UnitTests/IO/DirectoryExtensionsTests.cs b/tests/CreativeCoders.Core.UnitTests/IO/DirectoryExtensionsTests.cs new file mode 100644 index 00000000..d2da26bd --- /dev/null +++ b/tests/CreativeCoders.Core.UnitTests/IO/DirectoryExtensionsTests.cs @@ -0,0 +1,107 @@ +using System.IO.Abstractions; +using CreativeCoders.Core.IO; +using FakeItEasy; +using AwesomeAssertions; +using Xunit; + +#nullable enable +namespace CreativeCoders.Core.UnitTests.IO; + +public class DirectoryExtensionsTests +{ + [Fact] + public void EnsureDirectoryExists_ValidPath_CreatesDirectory() + { + // Arrange + var directory = A.Fake(); + const string path = "/test/path"; + + // Act + directory.EnsureDirectoryExists(path); + + // Assert + A.CallTo(() => directory.CreateDirectory(path)) + .MustHaveHappenedOnceExactly(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void EnsureDirectoryExists_NullOrEmptyPath_DoesNotCreateDirectory(string? path) + { + // Arrange + var directory = A.Fake(); + + // Act + directory.EnsureDirectoryExists(path); + + // Assert + A.CallTo(() => directory.CreateDirectory(A._)) + .MustNotHaveHappened(); + } + + [Fact] + public void EnsureDirectoryForFileNameExists_ValidFilePath_CreatesDirectory() + { + // Arrange + var directory = A.Fake(); + var fileSystem = A.Fake(); + var pathMock = A.Fake(); + + A.CallTo(() => directory.FileSystem).Returns(fileSystem); + A.CallTo(() => fileSystem.Path).Returns(pathMock); + + const string filePath = "/test/path/file.txt"; + const string directoryPath = "/test/path"; + + A.CallTo(() => pathMock.GetDirectoryName(filePath)).Returns(directoryPath); + + // Act + directory.EnsureDirectoryForFileNameExists(filePath); + + // Assert + A.CallTo(() => directory.CreateDirectory(directoryPath)) + .MustHaveHappenedOnceExactly(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void EnsureDirectoryForFileNameExists_NullOrEmptyFilePath_DoesNotCreateDirectory(string? filePath) + { + // Arrange + var directory = A.Fake(); + + // Act + directory.EnsureDirectoryForFileNameExists(filePath); + + // Assert + A.CallTo(() => directory.CreateDirectory(A._)) + .MustNotHaveHappened(); + } + + [Fact] + public void EnsureDirectoryForFileNameExists_GetDirectoryNameReturnsNull_DoesNotCreateDirectory() + { + // Arrange + var directory = A.Fake(); + var fileSystem = A.Fake(); + var pathMock = A.Fake(); + + A.CallTo(() => directory.FileSystem).Returns(fileSystem); + A.CallTo(() => fileSystem.Path).Returns(pathMock); + + const string filePath = "file.txt"; + + A.CallTo(() => pathMock.GetDirectoryName(filePath)).Returns(null); + + // Act + directory.EnsureDirectoryForFileNameExists(filePath); + + // Assert + A.CallTo(() => directory.CreateDirectory(A._)) + .MustNotHaveHappened(); + } +}