From c54071b8cce721f5e9ab97d78f41410b4ab0bb6c Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Thu, 11 Apr 2024 04:15:28 -0500 Subject: [PATCH 1/3] test: New Site Command tests --- source/Commands/NewSiteCommand.cs | 39 +++++--- source/Helpers/IFileSystem.cs | 53 +++++++++++ .../CommandLineOptions/NewSiteOptions.cs | 6 +- source/Models/ISite.cs | 5 ++ source/Models/Site.cs | 11 +-- source/Program.cs | 3 +- test/Commands/NewSiteCommandTests.cs | 90 +++++++++++++++++++ 7 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 source/Helpers/IFileSystem.cs create mode 100644 test/Commands/NewSiteCommandTests.cs diff --git a/source/Commands/NewSiteCommand.cs b/source/Commands/NewSiteCommand.cs index e201071..cd50213 100644 --- a/source/Commands/NewSiteCommand.cs +++ b/source/Commands/NewSiteCommand.cs @@ -1,42 +1,57 @@ using Serilog; using SuCoS.Models; using SuCoS.Models.CommandLineOptions; +using SuCoS.Parser; namespace SuCoS; /// /// Check links of a given site. /// -public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logger) +public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logger, IFileSystem fileSystem, ISite site) { + private static SiteSettings siteSettings = null!; + /// - /// Run the app + /// Generate the needed data for the class /// + /// + /// + /// /// - public int Run() + public static NewSiteCommand Create(NewSiteOptions options, ILogger logger, IFileSystem fileSystem) { - var siteSettings = new SiteSettings() + ArgumentNullException.ThrowIfNull(options); + + siteSettings = new SiteSettings() { Title = options.Title, Description = options.Description, BaseURL = options.BaseURL }; - // TODO: Refactor Site class to not need YAML parser nor FrontMatterParser - var site = new Site(new ServeOptions() { SourceOption = options.Output }, siteSettings, null!, logger, null); + var site = new Site(new GenerateOptions() { SourceOption = options.Output }, siteSettings, new YAMLParser(), null!, null); + return new NewSiteCommand(options, logger, fileSystem, site); + } - var outputPath = Path.GetFullPath(options.Output); - var siteSettingsPath = Path.Combine(outputPath, "sucos.yaml"); + /// + /// Run the app + /// + /// + public int Run() + { + var outputPath = fileSystem.GetFullPath(options.Output); + var siteSettingsPath = fileSystem.Combine(outputPath, "sucos.yaml"); - if (File.Exists(siteSettingsPath) && !options.Force) + if (fileSystem.FileExists(siteSettingsPath) && !options.Force) { logger.Error("{directoryPath} already exists", outputPath); return 1; } - logger.Information("Creating a new site: {title} at {outputPath}", siteSettings.Title, outputPath); + logger.Information("Creating a new site: {title} at {outputPath}", site.Title, outputPath); - CreateFolders(site.SourceFodlers); + CreateFolders(site.SourceFolders); try { @@ -61,7 +76,7 @@ public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logge foreach (var folder in folders) { logger.Information("Creating {folder}", folder); - Directory.CreateDirectory(folder); + fileSystem.CreateDirectory(folder); } } } \ No newline at end of file diff --git a/source/Helpers/IFileSystem.cs b/source/Helpers/IFileSystem.cs new file mode 100644 index 0000000..b6e5eeb --- /dev/null +++ b/source/Helpers/IFileSystem.cs @@ -0,0 +1,53 @@ +namespace SuCoS; + +/// +/// Interface for the System.File class +/// +public interface IFileSystem +{ + /// + /// Path.GetFullPath + /// + /// + /// + string GetFullPath(string path); + + /// + /// Path.Combine + /// + /// + /// + /// + string Combine(string path1, string path2); + + /// + /// File.Exists + /// + /// + /// + bool FileExists(string path); + + /// + /// Directory.CreateDirectory + /// + /// + void CreateDirectory(string path); +} + +/// +/// Actual implementation of FS operations +/// +public class FileSystem : IFileSystem +{ + /// + public string GetFullPath(string path) => Path.GetFullPath(path); + + /// + public string Combine(string path1, string path2) => Path.Combine(path1, path2); + + /// + public bool FileExists(string path) => File.Exists(path); + + /// + public void CreateDirectory(string path) => Directory.CreateDirectory(path); +} diff --git a/source/Models/CommandLineOptions/NewSiteOptions.cs b/source/Models/CommandLineOptions/NewSiteOptions.cs index f0dc375..6584a1e 100644 --- a/source/Models/CommandLineOptions/NewSiteOptions.cs +++ b/source/Models/CommandLineOptions/NewSiteOptions.cs @@ -24,17 +24,17 @@ public class NewSiteOptions /// Site title. /// [Option("title", Required = false, HelpText = "Site title")] - public required string Title { get; init; } = "My Site"; + public string Title { get; init; } = "My Site"; /// /// Site description. /// [Option("description", Required = false, HelpText = "Site description")] - public required string Description { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; /// /// Site base url. /// [Option("url", Required = false, HelpText = "Site base url")] - public required string BaseURL { get; init; } = "https://example.org/"; + public string BaseURL { get; init; } = "https://example.org/"; } diff --git a/source/Models/ISite.cs b/source/Models/ISite.cs index 40cab16..cdbc88b 100644 --- a/source/Models/ISite.cs +++ b/source/Models/ISite.cs @@ -72,6 +72,11 @@ public interface ISite : ISiteSettings, IParams /// public ILogger Logger { get; } + /// + /// List of all basic source folders + /// + public IEnumerable SourceFolders { get; } + /// /// Resets the template cache to force a reload of all templates. /// diff --git a/source/Models/Site.cs b/source/Models/Site.cs index a70d290..c315c2b 100644 --- a/source/Models/Site.cs +++ b/source/Models/Site.cs @@ -1,4 +1,3 @@ -using CommandLine; using Serilog; using SuCoS.Helpers; using SuCoS.Models.CommandLineOptions; @@ -64,10 +63,8 @@ public class Site : ISite /// public string SourceThemePath => Path.Combine(Options.Source, settings.ThemeDir, settings.Theme ?? string.Empty); - /// - /// List of all basic source folders - /// - public IEnumerable SourceFodlers => [ + /// + public IEnumerable SourceFolders => [ SourceContentPath, SourceStaticPath, SourceThemePath @@ -167,13 +164,13 @@ public class Site : ISite public Site( in IGenerateOptions options, in SiteSettings settings, - in IMetadataParser frontMatterParser, + in IMetadataParser parser, in ILogger logger, ISystemClock? clock) { Options = options; this.settings = settings; Logger = logger; - Parser = frontMatterParser; + Parser = parser; TemplateEngine = new FluidTemplateEngine(); this.clock = clock ?? new SystemClock(); diff --git a/source/Program.cs b/source/Program.cs index d99daa7..c6cf3f0 100644 --- a/source/Program.cs +++ b/source/Program.cs @@ -103,7 +103,8 @@ public class Program(ILogger logger) }, (NewSiteOptions options) => { - var command = new NewSiteCommand(options, logger); + var fileSystem = new FileSystem(); + var command = NewSiteCommand.Create(options, logger, fileSystem); return Task.FromResult(command.Run()); }, (NewThemeOptions options) => diff --git a/test/Commands/NewSiteCommandTests.cs b/test/Commands/NewSiteCommandTests.cs new file mode 100644 index 0000000..ac7c10d --- /dev/null +++ b/test/Commands/NewSiteCommandTests.cs @@ -0,0 +1,90 @@ +using SuCoS; +using SuCoS.Models; +using SuCoS.Models.CommandLineOptions; +using Xunit; +using NSubstitute; +using Serilog; + +namespace Tests.Commands; + +public class NewSiteCommandTests +{ + [Fact] + public void Run_ShouldReturn0_WhenDirectoryDoesNotExist() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Force = false }; + var logger = Substitute.For(); + var site = Substitute.For(); + var fileSystem = Substitute.For(); + fileSystem.FileExists(Arg.Any()).Returns(false); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + var result = newSiteCommand.Run(); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public void Run_ShouldReturn1_WhenForceIsFalseAndDirectoryExists() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Force = false }; + var logger = Substitute.For(); + var site = Substitute.For(); + var fileSystem = Substitute.For(); + fileSystem.FileExists(Arg.Any()).Returns(true); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + var result = newSiteCommand.Run(); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public void Run_ShouldReturn0_WhenForceIsTrueAndDirectoryExists() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Force = true }; + var logger = Substitute.For(); + var site = Substitute.For(); + var fileSystem = Substitute.For(); + fileSystem.FileExists(Arg.Any()).Returns(true); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + var result = newSiteCommand.Run(); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public void Run_ShouldReturn1_WhenExportThrowsException() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Force = true }; + var logger = Substitute.For(); + var site = Substitute.For(); + var fileSystem = Substitute.For(); + fileSystem.FileExists(Arg.Any()).Returns(false); + site.Parser + .When(x => x.Export(Arg.Any(), Arg.Any())) + .Do(x => { throw new ArgumentNullException(); }); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + var result = newSiteCommand.Run(); + + // Assert + Assert.Equal(1, result); + } +} -- GitLab From a3c4d5e09242f22ee5637928429fd1f67eff8889 Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 24 Apr 2024 09:51:05 -0500 Subject: [PATCH 2/3] chore: Tests.Commands namespace --- .../YamlDotNet.Analyzers.StaticGenerator/.editorconfig | 2 -- test/{ => Commands}/BaseGeneratorCommandTests.cs | 9 +++------ 2 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig rename test/{ => Commands}/BaseGeneratorCommandTests.cs (86%) diff --git a/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig b/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig deleted file mode 100644 index 0489b34..0000000 --- a/source/YamlDotNet.Analyzers.StaticGenerator/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.cs] -dotnet_diagnostic.CS1591.severity = none # disable on debug genereated files diff --git a/test/BaseGeneratorCommandTests.cs b/test/Commands/BaseGeneratorCommandTests.cs similarity index 86% rename from test/BaseGeneratorCommandTests.cs rename to test/Commands/BaseGeneratorCommandTests.cs index 4aa4d7c..28040d2 100644 --- a/test/BaseGeneratorCommandTests.cs +++ b/test/Commands/BaseGeneratorCommandTests.cs @@ -5,7 +5,7 @@ using SuCoS.TemplateEngine; using System.Reflection; using Xunit; -namespace Tests; +namespace Tests.Commands; public class BaseGeneratorCommandTests { @@ -16,11 +16,8 @@ public class BaseGeneratorCommandTests private static readonly ILogger testLogger = new LoggerConfiguration().CreateLogger(); - private class BaseGeneratorCommandStub : BaseGeneratorCommand - { - public BaseGeneratorCommandStub(IGenerateOptions options, ILogger logger) - : base(options, logger) { } - } + private class BaseGeneratorCommandStub(IGenerateOptions options, ILogger logger) + : BaseGeneratorCommand(options, logger); [Fact] public void Constructor_ShouldThrowArgumentNullException_WhenOptionsIsNull() -- GitLab From 91e32ef41fb226691a131e2d1f12120688a9cd9b Mon Sep 17 00:00:00 2001 From: Bruno Massa Date: Wed, 24 Apr 2024 10:21:26 -0500 Subject: [PATCH 3/3] test: more tests, including logging info --- source/Commands/NewSiteCommand.cs | 3 +- test/Commands/NewSiteCommandTests.cs | 128 ++++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 14 deletions(-) diff --git a/source/Commands/NewSiteCommand.cs b/source/Commands/NewSiteCommand.cs index cd50213..3b9ae66 100644 --- a/source/Commands/NewSiteCommand.cs +++ b/source/Commands/NewSiteCommand.cs @@ -51,10 +51,9 @@ public sealed partial class NewSiteCommand(NewSiteOptions options, ILogger logge logger.Information("Creating a new site: {title} at {outputPath}", site.Title, outputPath); - CreateFolders(site.SourceFolders); - try { + CreateFolders(site.SourceFolders); site.Parser.Export(siteSettings, siteSettingsPath); } catch (Exception ex) diff --git a/test/Commands/NewSiteCommandTests.cs b/test/Commands/NewSiteCommandTests.cs index ac7c10d..af1c371 100644 --- a/test/Commands/NewSiteCommandTests.cs +++ b/test/Commands/NewSiteCommandTests.cs @@ -9,14 +9,110 @@ namespace Tests.Commands; public class NewSiteCommandTests { + readonly ILogger logger; + readonly IFileSystem fileSystem; + readonly ISite site; + + public NewSiteCommandTests() + { + logger = Substitute.For(); + fileSystem = Substitute.For(); + site = Substitute.For(); + } + + [Fact] + public void Create_ShouldReturnNewSiteCommand_WhenParametersAreValid() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com" }; + + // Act + var result = NewSiteCommand.Create(options, logger, fileSystem); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void Create_ShouldThrowArgumentNullException_WhenOptionsIsNull() + { + // Act and Assert + Assert.Throws(testCode: () => NewSiteCommand.Create(null!, logger, fileSystem)); + } + + [Fact] + public void Create_ShouldNotReturnNull_WhenParametersAreValid() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com" }; + + // Act + var result = NewSiteCommand.Create(options, logger, fileSystem); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public void Run_ShouldLogInformation_WhenCreatingNewSite() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false }; + fileSystem.FileExists(Arg.Any()).Returns(false); + + var newSiteCommand = NewSiteCommand.Create(options, logger, fileSystem); + + // Act + newSiteCommand.Run(); + + // Assert + logger.Received(1).Information("Creating a new site: {title} at {outputPath}", options.Title, Arg.Any()); + } + + [Fact] + public void Run_ShouldCallCreateDirectoryWithCorrectPaths_ForEachFolder() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false }; + var site = Substitute.For(); + site.SourceFolders.Returns(["folder1", "folder2"]); + fileSystem.FileExists(Arg.Any()).Returns(false); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + newSiteCommand.Run(); + + // Assert + fileSystem.Received(1).CreateDirectory("folder1"); + fileSystem.Received(1).CreateDirectory("folder2"); + } + + [Fact] + public void Run_ShouldReturn1_WhenCreateDirectoryThrowsException() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Title = "Test", Description = "Test", BaseURL = "http://test.com", Force = false }; + var site = Substitute.For(); + site.SourceFolders.Returns(["folder1", "folder2"]); + fileSystem.FileExists(Arg.Any()).Returns(false); + fileSystem.When(x => x.CreateDirectory(Arg.Any())) + .Do(x => { throw new ArgumentNullException(); }); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + var result = newSiteCommand.Run(); + + // Assert + Assert.Equal(1, result); + } + [Fact] public void Run_ShouldReturn0_WhenDirectoryDoesNotExist() { // Arrange var options = new NewSiteOptions { Output = "test", Force = false }; - var logger = Substitute.For(); - var site = Substitute.For(); - var fileSystem = Substitute.For(); fileSystem.FileExists(Arg.Any()).Returns(false); var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); @@ -33,9 +129,6 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = false }; - var logger = Substitute.For(); - var site = Substitute.For(); - var fileSystem = Substitute.For(); fileSystem.FileExists(Arg.Any()).Returns(true); var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); @@ -52,9 +145,6 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = true }; - var logger = Substitute.For(); - var site = Substitute.For(); - var fileSystem = Substitute.For(); fileSystem.FileExists(Arg.Any()).Returns(true); var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); @@ -71,9 +161,6 @@ public class NewSiteCommandTests { // Arrange var options = new NewSiteOptions { Output = "test", Force = true }; - var logger = Substitute.For(); - var site = Substitute.For(); - var fileSystem = Substitute.For(); fileSystem.FileExists(Arg.Any()).Returns(false); site.Parser .When(x => x.Export(Arg.Any(), Arg.Any())) @@ -87,4 +174,21 @@ public class NewSiteCommandTests // Assert Assert.Equal(1, result); } + + [Fact] + public void Run_ShouldCallCreateDirectory_ForEachFolder() + { + // Arrange + var options = new NewSiteOptions { Output = "test", Force = false }; + site.SourceFolders.Returns(["folder1", "folder2"]); + fileSystem.FileExists(Arg.Any()).Returns(false); + + var newSiteCommand = new NewSiteCommand(options, logger, fileSystem, site); + + // Act + newSiteCommand.Run(); + + // Assert + fileSystem.Received(2).CreateDirectory(Arg.Any()); + } } -- GitLab