From b70448c6a8480970937c5add75fef9ae844eb0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Gr=C3=BCnwald?= Date: Mon, 6 Jun 2022 18:02:52 +0200 Subject: [PATCH 1/6] Move implementation of the "generate" command out of Program and into a separate class --- src/ChangeLog/Commands/GenerateCommand.cs | 234 ++++++++++++++++++++++ src/ChangeLog/Commands/ICommand.cs | 9 + src/ChangeLog/Program.cs | 222 +------------------- 3 files changed, 249 insertions(+), 216 deletions(-) create mode 100644 src/ChangeLog/Commands/GenerateCommand.cs create mode 100644 src/ChangeLog/Commands/ICommand.cs diff --git a/src/ChangeLog/Commands/GenerateCommand.cs b/src/ChangeLog/Commands/GenerateCommand.cs new file mode 100644 index 00000000..ab0f0985 --- /dev/null +++ b/src/ChangeLog/Commands/GenerateCommand.cs @@ -0,0 +1,234 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; +using Autofac; +using Grynwald.ChangeLog.CommandLine; +using Grynwald.ChangeLog.Configuration; +using Grynwald.ChangeLog.Filtering; +using Grynwald.ChangeLog.Git; +using Grynwald.ChangeLog.Integrations; +using Grynwald.ChangeLog.Logging; +using Grynwald.ChangeLog.Pipeline; +using Grynwald.ChangeLog.Tasks; +using Grynwald.ChangeLog.Templates; +using Grynwald.Utilities.Configuration; +using Grynwald.Utilities.Logging; +using Microsoft.Extensions.Logging; + + +namespace Grynwald.ChangeLog.Commands +{ + internal class GenerateCommand : ICommand + { + /// + /// Collects settings that are determined dynamically (in + /// that need to be passed into the configuration system. + /// + private class DynamicallyDeterminedSettings + { + [ConfigurationValue("changelog:repositoryPath")] + public string RepositoryPath { get; set; } = ""; + } + + private static readonly string[] s_DefaultConfigurationFilePaths = + { + "changelog.settings.json", + ".config/changelog/settings.json" + }; + + + private readonly GenerateCommandLineParameters m_CommandLineParameters; + + + public GenerateCommand(GenerateCommandLineParameters commandLineParameters) + { + m_CommandLineParameters = commandLineParameters ?? throw new ArgumentNullException(nameof(commandLineParameters)); + } + + + + public async Task RunAsync() + { + var loggerOptions = m_CommandLineParameters.Verbose + ? new SimpleConsoleLoggerConfiguration(LogLevel.Debug, true, true) + : new SimpleConsoleLoggerConfiguration(LogLevel.Information, false, true); + + // for validation of command line parameters, directly create a console logger + // bypassing the DI container because we need to validate the parameters + // before setting up DI + var logger = new SimpleConsoleLogger(loggerOptions, ""); + + if (!ValidateCommandlineParameters(m_CommandLineParameters, logger)) + return 1; + + if (!TryGetRepositoryPath(m_CommandLineParameters, logger, out var repositoryPath)) + return 1; + + if (!TryOpenRepository(repositoryPath, logger, out var gitRepository)) + return 1; + + var configurationFilePath = GetConfigurationFilePath(m_CommandLineParameters, repositoryPath); + if (File.Exists(configurationFilePath)) + logger.LogDebug($"Using configuration file '{configurationFilePath}'"); + else + logger.LogDebug("Continuing without loading a configuration file, because no configuration file was wound"); + + + // pass repository path to configuration loader to make it available through the configuration system + var dynamicSettings = new DynamicallyDeterminedSettings() + { + RepositoryPath = repositoryPath + }; + + var configuration = ChangeLogConfigurationLoader.GetConfiguration(configurationFilePath, m_CommandLineParameters, dynamicSettings); + + using (gitRepository) + { + var containerBuilder = new ContainerBuilder(); + + containerBuilder.RegisterType(); + containerBuilder.RegisterInstance(configuration).SingleInstance(); + containerBuilder.RegisterInstance(gitRepository).SingleInstance().As(); + + containerBuilder.RegisterLogging(loggerOptions); + + containerBuilder.RegisterType(); + + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + containerBuilder.RegisterType(); + + containerBuilder.RegisterIntegrations(); + + try + { + containerBuilder.RegisterTemplate(configuration.Template); + } + catch (InvalidTemplateConfigurationException ex) + { + logger.LogError($"Failed to load template: {ex.Message}"); + return 1; + } + + using (var container = containerBuilder.Build()) + { + var configurationValidator = container.Resolve(); + var validationResult = configurationValidator.Validate(configuration); + + if (!validationResult.IsValid) + { + foreach (var error in validationResult.Errors) + { + logger.LogError($"Invalid configuration: {error.ErrorMessage}"); + } + + logger.LogError($"Validation of configuration failed"); + return 1; + } + + var pipeline = new ChangeLogPipelineBuilder(container) + .AddTask() + .AddTask() + .AddTask() + .AddTaskIf( + configuration.MessageOverrides.Enabled && configuration.MessageOverrides.Provider == ChangeLogConfiguration.MessageOverrideProvider.GitNotes + ) + .AddTaskIf( + configuration.MessageOverrides.Enabled && configuration.MessageOverrides.Provider == ChangeLogConfiguration.MessageOverrideProvider.FileSystem + ) + .AddTask() + .AddTask() + .AddTask() + .AddTask() + .AddTask() + .AddTask() + .AddTask() + .AddIntegrationTasks() + .AddTask() + .Build(); + + var result = await pipeline.RunAsync(); + return result.Success ? 0 : 1; + } + } + } + + private static string? GetConfigurationFilePath(GenerateCommandLineParameters commandlineParameters, string repositoryPath) + { + if (!String.IsNullOrEmpty(commandlineParameters.ConfigurationFilePath)) + return commandlineParameters.ConfigurationFilePath; + + foreach (var path in s_DefaultConfigurationFilePaths) + { + var absolutePath = Path.GetFullPath(Path.Combine(repositoryPath, path)); + + if (File.Exists(absolutePath)) + return absolutePath; + } + + return null; + } + + private static bool ValidateCommandlineParameters(GenerateCommandLineParameters parameters, ILogger logger) + { + var validator = new GenerateCommandLineParametersValidator(); + var result = validator.Validate(parameters); + + foreach (var error in result.Errors) + { + logger.LogError(error.ToString()); + } + + return result.IsValid; + } + + private static bool TryGetRepositoryPath(GenerateCommandLineParameters parameters, ILogger logger, [NotNullWhen(true)] out string? repositoryPath) + { + if (!String.IsNullOrEmpty(parameters.RepositoryPath)) + { + repositoryPath = Path.GetFullPath(parameters.RepositoryPath); + return true; + } + + if (RepositoryLocator.TryGetRepositoryPath(Environment.CurrentDirectory, out repositoryPath)) + { + logger.LogInformation($"Found git repository at '{repositoryPath}'"); + return true; + } + else + { + logger.LogError($"No git repository found in '{Environment.CurrentDirectory}' or any of its parent directories"); + repositoryPath = default; + return false; + } + } + + private static bool TryOpenRepository(string repositoryPath, ILogger logger, [NotNullWhen(true)] out IGitRepository? repository) + { + try + { + repository = new GitRepository(repositoryPath); + return true; + } + catch (RepositoryNotFoundException ex) + { + logger.LogDebug(ex, $"Failed to open repository at '{repositoryPath}'"); + logger.LogError($"'{repositoryPath}' is not a git repository"); + repository = default; + return false; + } + } + + } +} diff --git a/src/ChangeLog/Commands/ICommand.cs b/src/ChangeLog/Commands/ICommand.cs new file mode 100644 index 00000000..f04927e0 --- /dev/null +++ b/src/ChangeLog/Commands/ICommand.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Grynwald.ChangeLog.Commands +{ + internal interface ICommand + { + Task RunAsync(); + } +} diff --git a/src/ChangeLog/Program.cs b/src/ChangeLog/Program.cs index dc88714f..c9773eda 100644 --- a/src/ChangeLog/Program.cs +++ b/src/ChangeLog/Program.cs @@ -1,51 +1,24 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Threading.Tasks; -using Autofac; using CommandLine; using Grynwald.ChangeLog.CommandLine; -using Grynwald.ChangeLog.Configuration; -using Grynwald.ChangeLog.Filtering; -using Grynwald.ChangeLog.Git; -using Grynwald.ChangeLog.Integrations; -using Grynwald.ChangeLog.Logging; -using Grynwald.ChangeLog.Pipeline; -using Grynwald.ChangeLog.Tasks; -using Grynwald.ChangeLog.Templates; -using Grynwald.Utilities.Configuration; -using Grynwald.Utilities.Logging; -using Microsoft.Extensions.Logging; +using Grynwald.ChangeLog.Commands; namespace Grynwald.ChangeLog { internal static class Program { - /// - /// Collects settings that are determined dynamically (in - /// that need to be passed into the configuration system. - /// - private class DynamicallyDeterminedSettings - { - [ConfigurationValue("changelog:repositoryPath")] - public string RepositoryPath { get; set; } = ""; - } - - - private static readonly string[] s_DefaultConfigurationFilePaths = - { - "changelog.settings.json", - ".config/changelog/settings.json" - }; - - private static async Task Main(string[] args) { return await CommandLineParser.Parse(args) .MapResult( - (GenerateCommandLineParameters parsed) => RunAsync(parsed), + (GenerateCommandLineParameters parsed) => + { + var command = new GenerateCommand(parsed); + return command.RunAsync(); + }, (DummyCommandLineParameters dummy) => { Console.Error.WriteLine("Invalid arguments"); @@ -61,188 +34,5 @@ private static async Task Main(string[] args) } ); } - - - private static async Task RunAsync(GenerateCommandLineParameters commandlineParameters) - { - var loggerOptions = commandlineParameters.Verbose - ? new SimpleConsoleLoggerConfiguration(LogLevel.Debug, true, true) - : new SimpleConsoleLoggerConfiguration(LogLevel.Information, false, true); - - // for validation of command line parameters, directly create a console logger - // bypassing the DI container because we need to validate the parameters - // before setting up DI - var logger = new SimpleConsoleLogger(loggerOptions, ""); - - if (!ValidateCommandlineParameters(commandlineParameters, logger)) - return 1; - - if (!TryGetRepositoryPath(commandlineParameters, logger, out var repositoryPath)) - return 1; - - if (!TryOpenRepository(repositoryPath, logger, out var gitRepository)) - return 1; - - var configurationFilePath = GetConfigurationFilePath(commandlineParameters, repositoryPath); - if (File.Exists(configurationFilePath)) - logger.LogDebug($"Using configuration file '{configurationFilePath}'"); - else - logger.LogDebug("Continuing without loading a configuration file, because no configuration file was wound"); - - - // pass repository path to configuration loader to make it available through the configuration system - var dynamicSettings = new DynamicallyDeterminedSettings() - { - RepositoryPath = repositoryPath - }; - - var configuration = ChangeLogConfigurationLoader.GetConfiguration(configurationFilePath, commandlineParameters, dynamicSettings); - - using (gitRepository) - { - var containerBuilder = new ContainerBuilder(); - - containerBuilder.RegisterType(); - containerBuilder.RegisterInstance(configuration).SingleInstance(); - containerBuilder.RegisterInstance(gitRepository).SingleInstance().As(); - - containerBuilder.RegisterLogging(loggerOptions); - - containerBuilder.RegisterType(); - - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - - containerBuilder.RegisterIntegrations(); - - try - { - containerBuilder.RegisterTemplate(configuration.Template); - } - catch (InvalidTemplateConfigurationException ex) - { - logger.LogError($"Failed to load template: {ex.Message}"); - return 1; - } - - using (var container = containerBuilder.Build()) - { - var configurationValidator = container.Resolve(); - var validationResult = configurationValidator.Validate(configuration); - - if (!validationResult.IsValid) - { - foreach (var error in validationResult.Errors) - { - logger.LogError($"Invalid configuration: {error.ErrorMessage}"); - } - - logger.LogError($"Validation of configuration failed"); - return 1; - } - - var pipeline = new ChangeLogPipelineBuilder(container) - .AddTask() - .AddTask() - .AddTask() - .AddTaskIf( - configuration.MessageOverrides.Enabled && configuration.MessageOverrides.Provider == ChangeLogConfiguration.MessageOverrideProvider.GitNotes - ) - .AddTaskIf( - configuration.MessageOverrides.Enabled && configuration.MessageOverrides.Provider == ChangeLogConfiguration.MessageOverrideProvider.FileSystem - ) - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddIntegrationTasks() - .AddTask() - .Build(); - - var result = await pipeline.RunAsync(); - return result.Success ? 0 : 1; - } - } - } - - private static string? GetConfigurationFilePath(GenerateCommandLineParameters commandlineParameters, string repositoryPath) - { - if (!String.IsNullOrEmpty(commandlineParameters.ConfigurationFilePath)) - return commandlineParameters.ConfigurationFilePath; - - foreach (var path in s_DefaultConfigurationFilePaths) - { - var absolutePath = Path.GetFullPath(Path.Combine(repositoryPath, path)); - - if (File.Exists(absolutePath)) - return absolutePath; - } - - return null; - } - - private static bool ValidateCommandlineParameters(GenerateCommandLineParameters parameters, ILogger logger) - { - var validator = new GenerateCommandLineParametersValidator(); - var result = validator.Validate(parameters); - - foreach (var error in result.Errors) - { - logger.LogError(error.ToString()); - } - - return result.IsValid; - } - - private static bool TryGetRepositoryPath(GenerateCommandLineParameters parameters, ILogger logger, [NotNullWhen(true)] out string? repositoryPath) - { - if (!String.IsNullOrEmpty(parameters.RepositoryPath)) - { - repositoryPath = Path.GetFullPath(parameters.RepositoryPath); - return true; - } - - if (RepositoryLocator.TryGetRepositoryPath(Environment.CurrentDirectory, out repositoryPath)) - { - logger.LogInformation($"Found git repository at '{repositoryPath}'"); - return true; - } - else - { - logger.LogError($"No git repository found in '{Environment.CurrentDirectory}' or any of its parent directories"); - repositoryPath = default; - return false; - } - } - - private static bool TryOpenRepository(string repositoryPath, ILogger logger, [NotNullWhen(true)] out IGitRepository? repository) - { - try - { - repository = new GitRepository(repositoryPath); - return true; - } - catch (RepositoryNotFoundException ex) - { - logger.LogDebug(ex, $"Failed to open repository at '{repositoryPath}'"); - logger.LogError($"'{repositoryPath}' is not a git repository"); - repository = default; - return false; - } - } } } From 4bd49ab71e0d43c17d1607e73ae2d10ac9179a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Gr=C3=BCnwald?= Date: Mon, 6 Jun 2022 20:00:55 +0200 Subject: [PATCH 2/6] Register all template implementations with the DI container and select the right implementation in RenderTemplateTask Remove the need to conditioally configure the DI container by no longer registering a single instance of ITemplate based on the configuration. Instead, register all implementations and select the right implementation based in the configuraion in RenderTemplateTask. This (along with future refactorings) will enable composing the application / creating the DI container independently of the configuration --- .../CommandLine/CommandLineParserTest.cs | 7 +- .../ChangeLogConfigurationLoaderTest.cs | 5 +- .../ConfigurationValidatorTest.cs | 11 +- .../Tasks/RenderTemplateTaskTest.cs | 137 ++++++++++++++++-- .../Templates/Default/DefaultTemplateTest.cs | 14 ++ .../GitHubReleaseTemplateTest.cs | 13 ++ .../GitLabReleaseTemplateTest.cs | 13 ++ .../Templates/Html/HtmlTemplateTest.cs | 14 ++ .../TemplateContainerBuildExtensionsTest.cs | 51 +++---- src/ChangeLog.Test/TestLogger.cs | 50 +++++++ src/ChangeLog.Test/XunitLogger.cs | 22 ++- .../GenerateCommandLineParameters.cs | 3 +- src/ChangeLog/Commands/GenerateCommand.cs | 10 +- .../Configuration/ChangeLogConfiguration.cs | 9 +- src/ChangeLog/Tasks/RenderTemplateTask.cs | 51 ++++++- .../Templates/Default/DefaultTemplate.cs | 4 + .../GitHubRelease/GitHubReleaseTemplate.cs | 3 + .../GitLabRelease/GitLabReleaseTemplate.cs | 3 + src/ChangeLog/Templates/Html/HtmlTemplate.cs | 3 + src/ChangeLog/Templates/ITemplate.cs | 5 + .../InvalidTemplateConfigurationException.cs | 11 -- .../TemplateContainerBuildExtensions.cs | 28 +--- src/ChangeLog/Templates/TemplateName.cs | 10 ++ .../Templates/_Base/ScribanBaseTemplate.cs | 4 + utilities/docs/DocsRenderer.cs | 12 +- 25 files changed, 363 insertions(+), 130 deletions(-) create mode 100644 src/ChangeLog.Test/TestLogger.cs delete mode 100644 src/ChangeLog/Templates/InvalidTemplateConfigurationException.cs create mode 100644 src/ChangeLog/Templates/TemplateName.cs diff --git a/src/ChangeLog.Test/CommandLine/CommandLineParserTest.cs b/src/ChangeLog.Test/CommandLine/CommandLineParserTest.cs index 0f878f78..2cbf9da5 100644 --- a/src/ChangeLog.Test/CommandLine/CommandLineParserTest.cs +++ b/src/ChangeLog.Test/CommandLine/CommandLineParserTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Grynwald.ChangeLog.CommandLine; using Grynwald.ChangeLog.Configuration; +using Grynwald.ChangeLog.Templates; using Xunit; namespace Grynwald.ChangeLog.Test.CommandLine @@ -30,9 +31,9 @@ public void Template_parameter_is_optional() public static IEnumerable TemplateNames() { #if NETCOREAPP3_1 - foreach (var value in Enum.GetValues(typeof(ChangeLogConfiguration.TemplateName)).Cast()) + foreach (var value in Enum.GetValues(typeof(TemplateName)).Cast()) #else - foreach (var value in Enum.GetValues()) + foreach (var value in Enum.GetValues()) #endif { yield return new object[] { value.ToString(), value }; @@ -43,7 +44,7 @@ public static IEnumerable TemplateNames() [Theory] [MemberData(nameof(TemplateNames))] - public void Template_parameter_is_parsed_correctly(string template, ChangeLogConfiguration.TemplateName expected) + public void Template_parameter_is_parsed_correctly(string template, TemplateName expected) { // ARRANGE var args = new[] { "--repository", "some-path", "--template", template }; diff --git a/src/ChangeLog.Test/Configuration/ChangeLogConfigurationLoaderTest.cs b/src/ChangeLog.Test/Configuration/ChangeLogConfigurationLoaderTest.cs index 06658fc5..fe910fdc 100644 --- a/src/ChangeLog.Test/Configuration/ChangeLogConfigurationLoaderTest.cs +++ b/src/ChangeLog.Test/Configuration/ChangeLogConfigurationLoaderTest.cs @@ -6,6 +6,7 @@ using System.Linq.Expressions; using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.ConventionalCommits; +using Grynwald.ChangeLog.Templates; using Grynwald.Utilities.Configuration; using Grynwald.Utilities.IO; using Newtonsoft.Json; @@ -224,7 +225,7 @@ static object[] TestCase(Expression> assertion) // Template settings // yield return TestCase(config => Assert.NotNull(config.Template)); - yield return TestCase(config => Assert.Equal(ChangeLogConfiguration.TemplateName.Default, config.Template.Name)); + yield return TestCase(config => Assert.Equal(TemplateName.Default, config.Template.Name)); // // Default Template settings @@ -491,7 +492,7 @@ private static IEnumerable GetEnumValues() where T : Enum // // Template setting // - foreach (var value in GetEnumValues()) + foreach (var value in GetEnumValues()) { yield return TestCase("template:name", config => config.Template.Name, value); } diff --git a/src/ChangeLog.Test/Configuration/ConfigurationValidatorTest.cs b/src/ChangeLog.Test/Configuration/ConfigurationValidatorTest.cs index 2c0a399a..b143592f 100644 --- a/src/ChangeLog.Test/Configuration/ConfigurationValidatorTest.cs +++ b/src/ChangeLog.Test/Configuration/ConfigurationValidatorTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Grynwald.ChangeLog.Configuration; +using Grynwald.ChangeLog.Templates; using Grynwald.Utilities.IO; using Xunit; @@ -792,7 +793,7 @@ public void Name_must_be_defined_value() { // ARRANGE var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); - config.Template.Name = (ChangeLogConfiguration.TemplateName)(-1); + config.Template.Name = (TemplateName)(-1); var sut = new ConfigurationValidator(); @@ -809,7 +810,7 @@ public void Name_must_be_defined_value() [Theory] [CombinatorialData] public void Custom_directory_can_be_null_or_empty( - ChangeLogConfiguration.TemplateName template, + TemplateName template, [CombinatorialValues(null, "")] string customDirectory) { // ARRANGE @@ -832,7 +833,7 @@ public void Custom_directory_can_be_null_or_empty( [Theory] [CombinatorialData] public void Custom_directory_must_not_be_whitespace( - ChangeLogConfiguration.TemplateName template, + TemplateName template, [CombinatorialValues("\t", " ")] string customDirectory) { // ARRANGE @@ -857,7 +858,7 @@ public void Custom_directory_must_not_be_whitespace( [Theory] [CombinatorialData] - public void Custom_directory_must_exist_when_it_is_not_null_or_empty(ChangeLogConfiguration.TemplateName template) + public void Custom_directory_must_exist_when_it_is_not_null_or_empty(TemplateName template) { // ARRANGE var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); @@ -884,7 +885,7 @@ public void Custom_directory_must_exist_when_it_is_not_null_or_empty(ChangeLogCo [Theory] [CombinatorialData] - public void Custom_directory_is_valid_when_directory_exists(ChangeLogConfiguration.TemplateName template) + public void Custom_directory_is_valid_when_directory_exists(TemplateName template) { // ARRANGE using var temporaryDirectory = new TemporaryDirectory(); diff --git a/src/ChangeLog.Test/Tasks/RenderTemplateTaskTest.cs b/src/ChangeLog.Test/Tasks/RenderTemplateTaskTest.cs index a433cad7..649782b0 100644 --- a/src/ChangeLog.Test/Tasks/RenderTemplateTaskTest.cs +++ b/src/ChangeLog.Test/Tasks/RenderTemplateTaskTest.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Model; @@ -18,7 +21,7 @@ namespace Grynwald.ChangeLog.Test.Tasks /// public class RenderTemplateTaskTest { - private readonly ILogger m_Logger; + private readonly XunitLogger m_Logger; public RenderTemplateTaskTest(ITestOutputHelper testOutputHelper) @@ -27,6 +30,125 @@ public RenderTemplateTaskTest(ITestOutputHelper testOutputHelper) } + private Mock CreateTemplateMock(TemplateName? templateName = null) + { + var templateMock = new Mock(MockBehavior.Strict); + + templateMock + .Setup(x => x.Name) + .Returns(templateName ?? TemplateName.Default); + + templateMock + .Setup(x => x.SaveChangeLog(It.IsAny(), It.IsAny())); + + return templateMock; + } + + + [Theory] + [CombinatorialData] + public async Task Run_uses_the_configured_template(TemplateName configuredTemplateName) + { + // ARRANGE + using var temporaryDirectory = new TemporaryDirectory(); + var outputDirectory = Path.Combine(temporaryDirectory, "dir1"); + + var configuration = new ChangeLogConfiguration() + { + OutputPath = Path.Combine(outputDirectory, "changelog.md"), + Template = new() + { + Name = configuredTemplateName + } + }; + + + var templates = new List(); + var activeTemplateMock = default(Mock); +#if NETCOREAPP3_1 + foreach (var name in Enum.GetValues(typeof(TemplateName)).Cast()) +#else + foreach(var name in Enum.GetValues()) +#endif + { + var mock = CreateTemplateMock(name); + templates.Add(mock.Object); + + if (name == configuredTemplateName) + activeTemplateMock = mock; + } + + + var sut = new RenderTemplateTask(m_Logger, configuration, templates); + + // ACT + var result = await sut.RunAsync(new ApplicationChangeLog()); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Success, result); + activeTemplateMock!.Verify(x => x.SaveChangeLog(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task Run_returns_error_if_configured_template_cannot_be_found() + { + // ARRANGE + using var temporaryDirectory = new TemporaryDirectory(); + var configuration = new ChangeLogConfiguration() + { + OutputPath = Path.Combine(temporaryDirectory, "changelog.md"), + Template = new() + { + Name = TemplateName.GitHubRelease + } + }; + + var templates = new[] + { + CreateTemplateMock(TemplateName.Default).Object, + CreateTemplateMock(TemplateName.Html).Object, + }; + + var sut = new RenderTemplateTask(m_Logger, configuration, templates); + + // ACT + var result = await sut.RunAsync(new ApplicationChangeLog()); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Error, result); + Assert.Contains(m_Logger.LoggedMessages, msg => msg.Contains("Template 'GitHubRelease' was not found")); + } + + [Fact] + public async Task Run_returns_error_if_multiple_matching_templates_are_found() + { + // ARRANGE + using var temporaryDirectory = new TemporaryDirectory(); + var configuration = new ChangeLogConfiguration() + { + OutputPath = Path.Combine(temporaryDirectory, "changelog.md"), + Template = new() + { + Name = TemplateName.GitHubRelease + } + }; + + var templates = new[] + { + CreateTemplateMock(TemplateName.GitHubRelease).Object, + CreateTemplateMock(TemplateName.GitHubRelease).Object, + }; + + var sut = new RenderTemplateTask(m_Logger, configuration, templates); + + // ACT + var result = await sut.RunAsync(new ApplicationChangeLog()); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Error, result); + Assert.Contains(m_Logger.LoggedMessages, msg => msg.Contains("Found multiple templates named 'GitHubRelease'")); + } + [Fact] public async Task Run_creates_the_output_directory_before_calling_the_template() { @@ -39,10 +161,9 @@ public async Task Run_creates_the_output_directory_before_calling_the_template() OutputPath = Path.Combine(outputDirectory, "changelog.md") }; - var templateMock = new Mock(MockBehavior.Strict); - templateMock.Setup(x => x.SaveChangeLog(It.IsAny(), It.IsAny())); + var templateMock = CreateTemplateMock(); - var sut = new RenderTemplateTask(m_Logger, configuration, templateMock.Object); + var sut = new RenderTemplateTask(m_Logger, configuration, new[] { templateMock.Object }); // ACT var result = await sut.RunAsync(new ApplicationChangeLog()); @@ -54,8 +175,6 @@ public async Task Run_creates_the_output_directory_before_calling_the_template() templateMock.Verify(x => x.SaveChangeLog(It.IsAny(), configuration.OutputPath), Times.Once); } - - [Fact] public async Task Run_returns_error_if_template_throws_TemplateExecutionException() { @@ -66,12 +185,12 @@ public async Task Run_returns_error_if_template_throws_TemplateExecutionExceptio OutputPath = Path.Combine(temporaryDirectory, "changelog.md") }; - var templateMock = new Mock(MockBehavior.Strict); + var templateMock = CreateTemplateMock(); templateMock .Setup(x => x.SaveChangeLog(It.IsAny(), It.IsAny())) .Throws(new TemplateExecutionException("Irrelevant")); - var sut = new RenderTemplateTask(m_Logger, configuration, templateMock.Object); + var sut = new RenderTemplateTask(m_Logger, configuration, new[] { templateMock.Object }); // ACT var result = await sut.RunAsync(new ApplicationChangeLog()); diff --git a/src/ChangeLog.Test/Templates/Default/DefaultTemplateTest.cs b/src/ChangeLog.Test/Templates/Default/DefaultTemplateTest.cs index c46a59f6..7c15c30e 100644 --- a/src/ChangeLog.Test/Templates/Default/DefaultTemplateTest.cs +++ b/src/ChangeLog.Test/Templates/Default/DefaultTemplateTest.cs @@ -20,6 +20,20 @@ protected override void SetCustomDirectory(ChangeLogConfiguration configuration, } + + [Fact] + public void Name_returns_expected_value() + { + // ARRANGE + var sut = GetTemplateInstance(ChangeLogConfigurationLoader.GetDefaultConfiguration()); + + // ACT + var actual = sut.Name; + + // ASSERT + Assert.Equal(TemplateName.Default, actual); + } + [Fact] public void ChangeLog_with_multiple_versions_is_converted_to_expected_Output() { diff --git a/src/ChangeLog.Test/Templates/GitHubRelease/GitHubReleaseTemplateTest.cs b/src/ChangeLog.Test/Templates/GitHubRelease/GitHubReleaseTemplateTest.cs index 970f62ea..e4af5244 100644 --- a/src/ChangeLog.Test/Templates/GitHubRelease/GitHubReleaseTemplateTest.cs +++ b/src/ChangeLog.Test/Templates/GitHubRelease/GitHubReleaseTemplateTest.cs @@ -17,6 +17,19 @@ protected override void SetCustomDirectory(ChangeLogConfiguration configuration, } + [Fact] + public void Name_returns_expected_value() + { + // ARRANGE + var sut = GetTemplateInstance(ChangeLogConfigurationLoader.GetDefaultConfiguration()); + + // ACT + var actual = sut.Name; + + // ASSERT + Assert.Equal(TemplateName.GitHubRelease, actual); + } + [Fact] public void SaveChangeLog_throws_TemplateExecutionException_when_changelog_contains_multiple_versions() { diff --git a/src/ChangeLog.Test/Templates/GitLabRelease/GitLabReleaseTemplateTest.cs b/src/ChangeLog.Test/Templates/GitLabRelease/GitLabReleaseTemplateTest.cs index 4270bf7a..7b9a34aa 100644 --- a/src/ChangeLog.Test/Templates/GitLabRelease/GitLabReleaseTemplateTest.cs +++ b/src/ChangeLog.Test/Templates/GitLabRelease/GitLabReleaseTemplateTest.cs @@ -17,6 +17,19 @@ protected override void SetCustomDirectory(ChangeLogConfiguration configuration, } + [Fact] + public void Name_returns_expected_value() + { + // ARRANGE + var sut = GetTemplateInstance(ChangeLogConfigurationLoader.GetDefaultConfiguration()); + + // ACT + var actual = sut.Name; + + // ASSERT + Assert.Equal(TemplateName.GitLabRelease, actual); + } + [Fact] public void SaveChangeLog_throws_TemplateExecutionException_when_changelog_contains_multiple_versions() { diff --git a/src/ChangeLog.Test/Templates/Html/HtmlTemplateTest.cs b/src/ChangeLog.Test/Templates/Html/HtmlTemplateTest.cs index 28cd7c9b..f0162556 100644 --- a/src/ChangeLog.Test/Templates/Html/HtmlTemplateTest.cs +++ b/src/ChangeLog.Test/Templates/Html/HtmlTemplateTest.cs @@ -16,6 +16,20 @@ protected override void SetCustomDirectory(ChangeLogConfiguration configuration, configuration.Template.Html.CustomDirectory = customDirectory; } + + [Fact] + public void Name_returns_expected_value() + { + // ARRANGE + var sut = GetTemplateInstance(ChangeLogConfigurationLoader.GetDefaultConfiguration()); + + // ACT + var actual = sut.Name; + + // ASSERT + Assert.Equal(TemplateName.Html, actual); + } + private class CustomTextElement : INormalizedTextElement { public string NormalizedText { get; set; } = ""; diff --git a/src/ChangeLog.Test/Templates/TemplateContainerBuildExtensionsTest.cs b/src/ChangeLog.Test/Templates/TemplateContainerBuildExtensionsTest.cs index ff0a5dd0..149253de 100644 --- a/src/ChangeLog.Test/Templates/TemplateContainerBuildExtensionsTest.cs +++ b/src/ChangeLog.Test/Templates/TemplateContainerBuildExtensionsTest.cs @@ -1,58 +1,41 @@ -using System; +using System.Collections.Generic; using System.Linq; using Autofac; using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Templates; using Grynwald.ChangeLog.Templates.Default; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using Grynwald.ChangeLog.Templates.GitHubRelease; +using Grynwald.ChangeLog.Templates.GitLabRelease; +using Grynwald.ChangeLog.Templates.Html; using Xunit; namespace Grynwald.ChangeLog.Test.Templates { public class TemplateContainerBuildExtensionsTest { - [Theory] - [CombinatorialData] - public void RegisterTemplate_can_register_a_template_for_every_supported_template_name(ChangeLogConfiguration.TemplateName templateName) + [Fact] + public void RegisterTemplates_registers_the_expected_template_instances() { // ARRANGE - var configuration = new ChangeLogConfiguration() - { - Template = new ChangeLogConfiguration.TemplateConfiguration() { Name = templateName } - }; + var configuration = new ChangeLogConfiguration(); var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterInstance(configuration).SingleInstance(); - containerBuilder.RegisterInstance(NullLogger.Instance).As>(); // ACT - containerBuilder.RegisterTemplate(configuration.Template); + containerBuilder.RegisterTemplates(); // ASSERT var container = containerBuilder.Build(); - var template = container.Resolve(); - Assert.NotNull(template); - } - - [Fact] - public void RegisterTemplate_throws_InvalidTemplateConfigurationException_for_invalid_template_Swetting() - { - // ARRANGE - var templateName = (ChangeLogConfiguration.TemplateName) - Enum.GetValues(typeof(ChangeLogConfiguration.TemplateName)) - .Cast() - .Max() + 2; - - var configuration = new ChangeLogConfiguration() - { - Template = new ChangeLogConfiguration.TemplateConfiguration() { Name = templateName } - }; - - var containerBuilder = new ContainerBuilder(); - - // ACT / ASSERT - Assert.Throws(() => containerBuilder.RegisterTemplate(configuration.Template)); + var templates = container.Resolve>(); + + Assert.Collection( + templates.OrderBy(x => x.Name), + x => Assert.IsType(x), + x => Assert.IsType(x), + x => Assert.IsType(x), + x => Assert.IsType(x) + ); } } } diff --git a/src/ChangeLog.Test/TestLogger.cs b/src/ChangeLog.Test/TestLogger.cs new file mode 100644 index 00000000..b48071b9 --- /dev/null +++ b/src/ChangeLog.Test/TestLogger.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace Grynwald.ChangeLog.Test +{ + public class TestLogger : ILogger + { + private readonly string? m_CategoryName; + private readonly List m_LoggedMessages = new(); + + private class LoggerScope : IDisposable + { + public void Dispose() + { } + } + + public IReadOnlyList LoggedMessages => m_LoggedMessages; + + /// + /// Initializes a new instance of + /// + public TestLogger(string? categoryName) + { + m_CategoryName = String.IsNullOrEmpty(categoryName) ? null : categoryName; + } + + /// + public IDisposable BeginScope(TState state) => new LoggerScope(); + + /// + public bool IsEnabled(LogLevel logLevel) => true; + + /// + public virtual void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (IsEnabled(logLevel)) + { + m_LoggedMessages.Add($"{logLevel.ToString().ToUpper()} - {m_CategoryName} - {formatter(state, exception)}"); + } + } + } + + + public class TestLogger : TestLogger, ILogger + { + public TestLogger() : base(typeof(T).Name) + { } + } +} diff --git a/src/ChangeLog.Test/XunitLogger.cs b/src/ChangeLog.Test/XunitLogger.cs index c83e8d4e..d9d65b04 100644 --- a/src/ChangeLog.Test/XunitLogger.cs +++ b/src/ChangeLog.Test/XunitLogger.cs @@ -4,7 +4,7 @@ namespace Grynwald.ChangeLog.Test { - public class XunitLogger : ILogger + public class XunitLogger : TestLogger { private readonly ITestOutputHelper m_TestOutputHelper; private readonly string? m_CategoryName; @@ -18,25 +18,22 @@ public void Dispose() /// /// Initializes a new instance of /// - public XunitLogger(ITestOutputHelper testOutputHelper, string? categoryName) + public XunitLogger(ITestOutputHelper testOutputHelper, string? categoryName) : base(categoryName) { m_TestOutputHelper = testOutputHelper ?? throw new ArgumentNullException(nameof(testOutputHelper)); m_CategoryName = String.IsNullOrEmpty(categoryName) ? null : categoryName; } - /// - public IDisposable BeginScope(TState state) => new LoggerScope(); - - /// - public bool IsEnabled(LogLevel logLevel) => true; /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + public override void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - if (!IsEnabled(logLevel)) - return; + base.Log(logLevel, eventId, state, exception, formatter); - m_TestOutputHelper.WriteLine($"{logLevel.ToString().ToUpper()} - {m_CategoryName} - {formatter(state, exception)}"); + if (IsEnabled(logLevel)) + { + m_TestOutputHelper.WriteLine($"{logLevel.ToString().ToUpper()} - {m_CategoryName} - {formatter(state, exception)}"); + } } } @@ -44,7 +41,6 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except public class XunitLogger : XunitLogger, ILogger { public XunitLogger(ITestOutputHelper testOutputHelper) : base(testOutputHelper, typeof(T).Name) - { - } + { } } } diff --git a/src/ChangeLog/CommandLine/GenerateCommandLineParameters.cs b/src/ChangeLog/CommandLine/GenerateCommandLineParameters.cs index a202fb56..161b9fcc 100644 --- a/src/ChangeLog/CommandLine/GenerateCommandLineParameters.cs +++ b/src/ChangeLog/CommandLine/GenerateCommandLineParameters.cs @@ -2,6 +2,7 @@ using System.IO; using CommandLine; using Grynwald.ChangeLog.Configuration; +using Grynwald.ChangeLog.Templates; using Grynwald.Utilities.Configuration; namespace Grynwald.ChangeLog.CommandLine @@ -58,7 +59,7 @@ public string? OutputPath [Option("template", Required = false, Default = null, HelpText = "Sets the template to use for generating the changelog.")] [ConfigurationValue("changelog:template:name")] - public ChangeLogConfiguration.TemplateName? Template { get; set; } + public TemplateName? Template { get; set; } [Option("integrationProvider", Required = false, Default = null, HelpText = "Sets the integration provider to use")] [ConfigurationValue("changelog:integrations:provider")] diff --git a/src/ChangeLog/Commands/GenerateCommand.cs b/src/ChangeLog/Commands/GenerateCommand.cs index ab0f0985..5e9cadc1 100644 --- a/src/ChangeLog/Commands/GenerateCommand.cs +++ b/src/ChangeLog/Commands/GenerateCommand.cs @@ -111,15 +111,7 @@ public async Task RunAsync() containerBuilder.RegisterIntegrations(); - try - { - containerBuilder.RegisterTemplate(configuration.Template); - } - catch (InvalidTemplateConfigurationException ex) - { - logger.LogError($"Failed to load template: {ex.Message}"); - return 1; - } + containerBuilder.RegisterTemplates(); using (var container = containerBuilder.Build()) { diff --git a/src/ChangeLog/Configuration/ChangeLogConfiguration.cs b/src/ChangeLog/Configuration/ChangeLogConfiguration.cs index f63f558f..069cf717 100644 --- a/src/ChangeLog/Configuration/ChangeLogConfiguration.cs +++ b/src/ChangeLog/Configuration/ChangeLogConfiguration.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Grynwald.ChangeLog.Templates; namespace Grynwald.ChangeLog.Configuration { @@ -84,14 +85,6 @@ public class IntegrationsConfiguration public GitLabIntegrationConfiguration GitLab { get; set; } = new GitLabIntegrationConfiguration(); } - public enum TemplateName - { - Default, - GitLabRelease, - GitHubRelease, - Html, - } - public class TemplateConfiguration { [JsonSchemaDefaultValue] diff --git a/src/ChangeLog/Tasks/RenderTemplateTask.cs b/src/ChangeLog/Tasks/RenderTemplateTask.cs index 70c10ac9..d427214a 100644 --- a/src/ChangeLog/Tasks/RenderTemplateTask.cs +++ b/src/ChangeLog/Tasks/RenderTemplateTask.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Model; using Grynwald.ChangeLog.Pipeline; @@ -13,17 +15,20 @@ internal sealed class RenderTemplateTask : SynchronousChangeLogTask { private readonly ILogger m_Logger; private readonly ChangeLogConfiguration m_Configuration; - private readonly ITemplate m_Template; + private readonly IReadOnlyList m_Templates; /// /// Initializes a new instance of . /// - public RenderTemplateTask(ILogger logger, ChangeLogConfiguration configuration, ITemplate template) + public RenderTemplateTask(ILogger logger, ChangeLogConfiguration configuration, IEnumerable templates) { + if (templates is null) + throw new ArgumentNullException(nameof(templates)); + m_Logger = logger ?? throw new ArgumentNullException(nameof(logger)); m_Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - m_Template = template ?? throw new ArgumentNullException(nameof(template)); + m_Templates = templates.ToList(); } @@ -34,19 +39,53 @@ protected override ChangeLogTaskResult Run(ApplicationChangeLog changeLog) var outputDirectory = Path.GetDirectoryName(outputPath); Directory.CreateDirectory(outputDirectory!); + var template = TryGetTemplate(); + + if (template is null) + { + return ChangeLogTaskResult.Error; + } + m_Logger.LogInformation($"Saving changelog to '{outputPath}'"); - m_Logger.LogDebug($"Using template '{m_Template.GetType().Name}'"); + m_Logger.LogDebug($"Using template '{template.Name}'"); try { - m_Template.SaveChangeLog(changeLog, outputPath); + template.SaveChangeLog(changeLog, outputPath); } catch (TemplateExecutionException ex) { - m_Logger.LogError($"Rendering changelog using template '{m_Template.GetType().Name}' failed: {ex.Message}"); + m_Logger.LogError($"Rendering changelog using template '{template.Name}' failed: {ex.Message}"); return ChangeLogTaskResult.Error; } return ChangeLogTaskResult.Success; } + + + private ITemplate? TryGetTemplate() + { + var templateName = m_Configuration.Template.Name; + var matchingTemplates = m_Templates.Where(x => x.Name == templateName).ToArray(); + + switch (matchingTemplates.Length) + { + case 0: + m_Logger.LogError($"Template '{templateName}' was not found"); + return null; + + case 1: + return matchingTemplates[0]; + + case > 1: + m_Logger.LogError($"Found multiple templates named '{templateName}'"); + return null; + + default: + // Count cannot be < 0 + throw new InvalidOperationException(); + } + + + } } } diff --git a/src/ChangeLog/Templates/Default/DefaultTemplate.cs b/src/ChangeLog/Templates/Default/DefaultTemplate.cs index e029c8a7..a26791f7 100644 --- a/src/ChangeLog/Templates/Default/DefaultTemplate.cs +++ b/src/ChangeLog/Templates/Default/DefaultTemplate.cs @@ -11,8 +11,12 @@ internal class DefaultTemplate : ScribanBaseTemplate /// protected override ChangeLogConfiguration.TemplateSettings TemplateSettings => m_Configuration.Template.Default; + /// protected override string TemplateFileExtension => ".scriban-txt"; + /// + public override TemplateName Name => TemplateName.Default; + public DefaultTemplate(ChangeLogConfiguration configuration) : base(configuration) { } diff --git a/src/ChangeLog/Templates/GitHubRelease/GitHubReleaseTemplate.cs b/src/ChangeLog/Templates/GitHubRelease/GitHubReleaseTemplate.cs index 2ec20042..53e84835 100644 --- a/src/ChangeLog/Templates/GitHubRelease/GitHubReleaseTemplate.cs +++ b/src/ChangeLog/Templates/GitHubRelease/GitHubReleaseTemplate.cs @@ -12,6 +12,9 @@ internal class GitHubReleaseTemplate : DefaultTemplate /// protected override ChangeLogConfiguration.TemplateSettings TemplateSettings => m_Configuration.Template.GitHubRelease; + /// + public override TemplateName Name => TemplateName.GitHubRelease; + public GitHubReleaseTemplate(ChangeLogConfiguration configuration) : base(configuration) { } diff --git a/src/ChangeLog/Templates/GitLabRelease/GitLabReleaseTemplate.cs b/src/ChangeLog/Templates/GitLabRelease/GitLabReleaseTemplate.cs index d6a69c22..a97028be 100644 --- a/src/ChangeLog/Templates/GitLabRelease/GitLabReleaseTemplate.cs +++ b/src/ChangeLog/Templates/GitLabRelease/GitLabReleaseTemplate.cs @@ -15,6 +15,9 @@ internal class GitLabReleaseTemplate : DefaultTemplate /// protected override ChangeLogConfiguration.TemplateSettings TemplateSettings => m_Configuration.Template.GitLabRelease; + /// + public override TemplateName Name => TemplateName.GitLabRelease; + public GitLabReleaseTemplate(ChangeLogConfiguration configuration) : base(configuration) { } diff --git a/src/ChangeLog/Templates/Html/HtmlTemplate.cs b/src/ChangeLog/Templates/Html/HtmlTemplate.cs index abb3588f..65cba374 100644 --- a/src/ChangeLog/Templates/Html/HtmlTemplate.cs +++ b/src/ChangeLog/Templates/Html/HtmlTemplate.cs @@ -11,6 +11,9 @@ internal sealed class HtmlTemplate : ScribanBaseTemplate /// protected override string TemplateFileExtension => ".scriban-html"; + /// + public override TemplateName Name => TemplateName.Html; + public HtmlTemplate(ChangeLogConfiguration configuration) : base(configuration) { } diff --git a/src/ChangeLog/Templates/ITemplate.cs b/src/ChangeLog/Templates/ITemplate.cs index 6f753e58..000d9b80 100644 --- a/src/ChangeLog/Templates/ITemplate.cs +++ b/src/ChangeLog/Templates/ITemplate.cs @@ -7,6 +7,11 @@ namespace Grynwald.ChangeLog.Templates /// public interface ITemplate { + /// + /// Gets the template's name + /// + TemplateName Name { get; } + /// /// Saves the changelog to the specified path /// diff --git a/src/ChangeLog/Templates/InvalidTemplateConfigurationException.cs b/src/ChangeLog/Templates/InvalidTemplateConfigurationException.cs deleted file mode 100644 index 42d28085..00000000 --- a/src/ChangeLog/Templates/InvalidTemplateConfigurationException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Grynwald.ChangeLog.Templates -{ - [Serializable] - internal class InvalidTemplateConfigurationException : Exception - { - public InvalidTemplateConfigurationException(string message) : base(message) - { } - } -} diff --git a/src/ChangeLog/Templates/TemplateContainerBuildExtensions.cs b/src/ChangeLog/Templates/TemplateContainerBuildExtensions.cs index e0aa81ce..a772a79c 100644 --- a/src/ChangeLog/Templates/TemplateContainerBuildExtensions.cs +++ b/src/ChangeLog/Templates/TemplateContainerBuildExtensions.cs @@ -1,5 +1,4 @@ using Autofac; -using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Templates.Default; using Grynwald.ChangeLog.Templates.GitHubRelease; using Grynwald.ChangeLog.Templates.GitLabRelease; @@ -9,29 +8,12 @@ namespace Grynwald.ChangeLog.Templates { internal static class TemplateContainerBuildExtensions { - public static void RegisterTemplate(this ContainerBuilder containerBuilder, ChangeLogConfiguration.TemplateConfiguration configuration) + public static void RegisterTemplates(this ContainerBuilder containerBuilder) { - switch (configuration.Name) - { - case ChangeLogConfiguration.TemplateName.Default: - containerBuilder.RegisterType().As(); - break; - - case ChangeLogConfiguration.TemplateName.GitLabRelease: - containerBuilder.RegisterType().As(); - break; - - case ChangeLogConfiguration.TemplateName.GitHubRelease: - containerBuilder.RegisterType().As(); - break; - - case ChangeLogConfiguration.TemplateName.Html: - containerBuilder.RegisterType().As(); - break; - - default: - throw new InvalidTemplateConfigurationException($"Unknown template '{configuration.Name}'"); - } + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); } } } diff --git a/src/ChangeLog/Templates/TemplateName.cs b/src/ChangeLog/Templates/TemplateName.cs new file mode 100644 index 00000000..ab11cbfe --- /dev/null +++ b/src/ChangeLog/Templates/TemplateName.cs @@ -0,0 +1,10 @@ +namespace Grynwald.ChangeLog.Templates +{ + public enum TemplateName + { + Default, + GitLabRelease, + GitHubRelease, + Html, + } +} diff --git a/src/ChangeLog/Templates/_Base/ScribanBaseTemplate.cs b/src/ChangeLog/Templates/_Base/ScribanBaseTemplate.cs index 7ee98045..896c1418 100644 --- a/src/ChangeLog/Templates/_Base/ScribanBaseTemplate.cs +++ b/src/ChangeLog/Templates/_Base/ScribanBaseTemplate.cs @@ -104,6 +104,9 @@ public static string Slugify(string? value) public IFileSystem FileSystem => m_Filesystem.Value; + /// + public abstract TemplateName Name { get; } + public ScribanBaseTemplate(ChangeLogConfiguration configuration) { @@ -127,6 +130,7 @@ public ScribanBaseTemplate(ChangeLogConfiguration configuration) } + /// public virtual void SaveChangeLog(ApplicationChangeLog changeLog, string outputPath) { var viewModel = new ApplicationChangeLogViewModel(m_Configuration, changeLog); diff --git a/utilities/docs/DocsRenderer.cs b/utilities/docs/DocsRenderer.cs index 39afa440..cb2d5685 100644 --- a/utilities/docs/DocsRenderer.cs +++ b/utilities/docs/DocsRenderer.cs @@ -137,21 +137,21 @@ private class EnumerableFunctions : ScriptObject private class TemplateFunctions : ScriptObject { public static IEnumerable GetTemplateNames() => - Enum.GetValues().Select(x => x.ToString()); + Enum.GetValues().Select(x => x.ToString()); public static string GetFileTree(string templateName) { var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); - if (!Enum.TryParse(templateName, ignoreCase: true, out var parsed)) + if (!Enum.TryParse(templateName, ignoreCase: true, out var parsed)) throw new ArgumentException($"Unknown template name '{templateName}'", nameof(templateName)); ScribanBaseTemplate template = parsed switch { - ChangeLogConfiguration.TemplateName.Default => new DefaultTemplate(configuration), - ChangeLogConfiguration.TemplateName.GitHubRelease => new GitHubReleaseTemplate(configuration), - ChangeLogConfiguration.TemplateName.GitLabRelease => new GitLabReleaseTemplate(configuration), - ChangeLogConfiguration.TemplateName.Html => new HtmlTemplate(configuration), + TemplateName.Default => new DefaultTemplate(configuration), + TemplateName.GitHubRelease => new GitHubReleaseTemplate(configuration), + TemplateName.GitLabRelease => new GitLabReleaseTemplate(configuration), + TemplateName.Html => new HtmlTemplate(configuration), _ => throw new NotImplementedException() }; From 787628005c9051e8098f5b8200e3b5c3483d755c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Gr=C3=BCnwald?= Date: Mon, 6 Jun 2022 21:06:28 +0200 Subject: [PATCH 3/6] Always add LoadMessageOverridesFromFileSystemTask and LoadMessageOverridesFromGitNotesTask to the pipeline Instead of conditionally adding the LoadMessageOverridesFromFileSystemTask and LoadMessageOverridesFromGitNotesTask tasks based on the configuration, always add the tasks but skip the tasks if the corresponding feature is not enabled in the configuration --- ...dMessageOverridesFromFileSystemTaskTest.cs | 68 +++++++++++++++++++ ...oadMessageOverridesFromGitNotesTaskTest.cs | 61 ++++++++++++++++- src/ChangeLog/Commands/GenerateCommand.cs | 8 +-- .../IChangeLogPipelineBuilderExtensions.cs | 16 ----- .../LoadMessageOverridesFromFileSystemTask.cs | 5 +- .../LoadMessageOverridesFromGitNotesTask.cs | 11 ++- .../Tasks/LoadMessageOverridesTask.cs | 11 ++- 7 files changed, 152 insertions(+), 28 deletions(-) delete mode 100644 src/ChangeLog/Pipeline/IChangeLogPipelineBuilderExtensions.cs diff --git a/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromFileSystemTaskTest.cs b/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromFileSystemTaskTest.cs index e59bb645..3e871f59 100644 --- a/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromFileSystemTaskTest.cs +++ b/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromFileSystemTaskTest.cs @@ -66,11 +66,66 @@ public void Repository_must_not_be_null() Assert.Equal("repository", argumentNullException.ParamName); } + [Fact] + public async Task Task_is_skipped_if_message_overrides_are_disabled() + { + // ARRANGE + var repo = Mock.Of(MockBehavior.Strict); + var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = false; + } + + var sut = new LoadMessageOverridesFromFileSystemTask(m_Logger, config, repo); + + // ACT + var changelog = new ApplicationChangeLog() + { + GetSingleVersionChangeLog("1.2"), + GetSingleVersionChangeLog("3.3") + }; + var result = await sut.RunAsync(changelog); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Skipped, result); + } + + [Fact] + public async Task Task_is_skipped_if_a_different_MessageOverrideProvider_is_configured() + { + // ARRANGE + var repo = Mock.Of(MockBehavior.Strict); + var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.GitNotes; + } + + var sut = new LoadMessageOverridesFromFileSystemTask(m_Logger, config, repo); + + // ACT + var changelog = new ApplicationChangeLog() + { + GetSingleVersionChangeLog("1.2"), + GetSingleVersionChangeLog("3.3") + }; + var result = await sut.RunAsync(changelog); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Skipped, result); + } + + [Fact] public async Task Task_does_nothing_for_empty_changelog() { // ARRANGE var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; + } + var repo = new Mock(MockBehavior.Strict); var sut = new LoadMessageOverridesFromFileSystemTask(m_Logger, config, repo.Object); @@ -95,6 +150,8 @@ public async Task Task_succeeds_if_override_directory_does_not_exist(string sour var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); { config.RepositoryPath = repositoryDirectory; + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; config.MessageOverrides.SourceDirectoryPath = sourceDirectoryPath; } @@ -136,6 +193,9 @@ public async Task Task_replaces_commit_messages_with_messages_from_the_configure { config.RepositoryPath = repositoryDirectory; + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; + if (sourceDirectoryPath is not null) config.MessageOverrides.SourceDirectoryPath = sourceDirectoryPath; } @@ -185,6 +245,8 @@ public async Task Task_replaces_commit_messages_with_messages_from_the_directory var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); { config.RepositoryPath = repositoryDirectory; + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; config.MessageOverrides.SourceDirectoryPath = overridesDirectory; } @@ -230,6 +292,8 @@ public async Task Task_replaces_commit_messages_if_file_name_is_abbreviated_id() var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); { config.RepositoryPath = temporaryDirectory; + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; } var commit1 = GetGitCommit(id: TestGitIds.Id1, commitMessage: "Original Message 1"); @@ -275,6 +339,8 @@ public async Task Task_fails_if_multiple_files_in_the_override_directory_resolve var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; config.MessageOverrides.SourceDirectoryPath = overrideDirectory; } @@ -319,6 +385,8 @@ public async Task Task_ignores_files_in_the_override_directory_if_the_commit_can var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; config.MessageOverrides.SourceDirectoryPath = overrideDirectory; } diff --git a/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromGitNotesTaskTest.cs b/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromGitNotesTaskTest.cs index 6e64a31d..60a3e60d 100644 --- a/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromGitNotesTaskTest.cs +++ b/src/ChangeLog.Test/Tasks/LoadMessageOverridesFromGitNotesTaskTest.cs @@ -65,12 +65,65 @@ public void Repository_must_not_be_null() Assert.Equal("repository", argumentNullException.ParamName); } + [Fact] + public async Task Task_is_skipped_if_message_overrides_are_disabled() + { + // ARRANGE + var repo = Mock.Of(MockBehavior.Strict); + var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = false; + } + + var sut = new LoadMessageOverridesFromGitNotesTask(m_Logger, config, repo); + + // ACT + var changelog = new ApplicationChangeLog() + { + GetSingleVersionChangeLog("1.2"), + GetSingleVersionChangeLog("3.3") + }; + var result = await sut.RunAsync(changelog); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Skipped, result); + } + + [Fact] + public async Task Task_is_skipped_if_a_different_MessageOverrideProvider_is_configured() + { + // ARRANGE + var repo = Mock.Of(MockBehavior.Strict); + var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.FileSystem; + } + + var sut = new LoadMessageOverridesFromGitNotesTask(m_Logger, config, repo); + + // ACT + var changelog = new ApplicationChangeLog() + { + GetSingleVersionChangeLog("1.2"), + GetSingleVersionChangeLog("3.3") + }; + var result = await sut.RunAsync(changelog); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Skipped, result); + } + [Fact] public async Task Run_does_nothing_for_empty_changelog() { // ARRANGE var repo = Mock.Of(MockBehavior.Strict); var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.GitNotes; + } var sut = new LoadMessageOverridesFromGitNotesTask(m_Logger, config, repo); @@ -90,9 +143,13 @@ public async Task Run_replaces_commit_messages_with_messages_from_git_notes_a_gi { // ARRANGE var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + config.MessageOverrides.Enabled = true; + config.MessageOverrides.Provider = ChangeLogConfiguration.MessageOverrideProvider.GitNotes; - if (gitNotesNamespace is not null) - config.MessageOverrides.GitNotesNamespace = gitNotesNamespace; + if (gitNotesNamespace is not null) + config.MessageOverrides.GitNotesNamespace = gitNotesNamespace; + } var commit1 = GetGitCommit(id: TestGitIds.Id1, commitMessage: "Original Message 1"); var commit2 = GetGitCommit(id: TestGitIds.Id2, commitMessage: "Original Message 2"); diff --git a/src/ChangeLog/Commands/GenerateCommand.cs b/src/ChangeLog/Commands/GenerateCommand.cs index 5e9cadc1..74ac69d7 100644 --- a/src/ChangeLog/Commands/GenerateCommand.cs +++ b/src/ChangeLog/Commands/GenerateCommand.cs @@ -133,12 +133,8 @@ public async Task RunAsync() .AddTask() .AddTask() .AddTask() - .AddTaskIf( - configuration.MessageOverrides.Enabled && configuration.MessageOverrides.Provider == ChangeLogConfiguration.MessageOverrideProvider.GitNotes - ) - .AddTaskIf( - configuration.MessageOverrides.Enabled && configuration.MessageOverrides.Provider == ChangeLogConfiguration.MessageOverrideProvider.FileSystem - ) + .AddTask() + .AddTask() .AddTask() .AddTask() .AddTask() diff --git a/src/ChangeLog/Pipeline/IChangeLogPipelineBuilderExtensions.cs b/src/ChangeLog/Pipeline/IChangeLogPipelineBuilderExtensions.cs deleted file mode 100644 index 4bb88164..00000000 --- a/src/ChangeLog/Pipeline/IChangeLogPipelineBuilderExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Grynwald.ChangeLog.Pipeline -{ - internal static class IChangeLogPipelineBuilderExtensions - { - public static IChangeLogPipelineBuilder AddTaskIf(this IChangeLogPipelineBuilder builder, bool condition) where T : IChangeLogTask - { - if (condition) - { - builder.AddTask(); - } - - return builder; - } - } - -} diff --git a/src/ChangeLog/Tasks/LoadMessageOverridesFromFileSystemTask.cs b/src/ChangeLog/Tasks/LoadMessageOverridesFromFileSystemTask.cs index c35c24a3..ae79a665 100644 --- a/src/ChangeLog/Tasks/LoadMessageOverridesFromFileSystemTask.cs +++ b/src/ChangeLog/Tasks/LoadMessageOverridesFromFileSystemTask.cs @@ -24,7 +24,7 @@ private class DuplicateOverrideMessageException : Exception private Lazy> m_OverrideMessages; - public LoadMessageOverridesFromFileSystemTask(ILogger logger, ChangeLogConfiguration configuration, IGitRepository repository) : base(logger) + public LoadMessageOverridesFromFileSystemTask(ILogger logger, ChangeLogConfiguration configuration, IGitRepository repository) : base(logger, configuration) { m_Logger = logger ?? throw new ArgumentNullException(nameof(logger)); m_Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -35,6 +35,9 @@ public LoadMessageOverridesFromFileSystemTask(ILogger logger, ChangeLogConfiguration configuration, IGitRepository repository) : base(logger) + public LoadMessageOverridesFromGitNotesTask(ILogger logger, ChangeLogConfiguration configuration, IGitRepository repository) : base(logger, configuration) { m_Logger = logger ?? throw new ArgumentNullException(nameof(logger)); m_Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -25,6 +26,14 @@ public LoadMessageOverridesFromGitNotesTask(ILogger m_Logger; + private readonly ChangeLogConfiguration m_Configuration; - - public LoadMessageOverridesTask(ILogger logger) + public LoadMessageOverridesTask(ILogger logger, ChangeLogConfiguration configuration) { m_Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + m_Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); } protected override ChangeLogTaskResult Run(ApplicationChangeLog changelog) { + if (!m_Configuration.MessageOverrides.Enabled) + { + return ChangeLogTaskResult.Skipped; + } + if (!changelog.Versions.Any()) { return ChangeLogTaskResult.Skipped; From e1d6719690510600ea56c7e92992ed51076d0582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Gr=C3=BCnwald?= Date: Mon, 6 Jun 2022 21:33:15 +0200 Subject: [PATCH 4/6] Always add GitHubLinkTask and GitLabLinkTask to the pipeline Instead of conditionally adding the GitHubLinkTask and GitLabLinkTask tasks based on the configuration, always add the tasks but skip the tasks if the corresponding integration is not enabled in the configuration. --- .../Integrations/GitHub/GitHubLinkTaskTest.cs | 116 +++++++++++-- .../Integrations/GitLab/GitLabLinkTaskTest.cs | 157 ++++++++++++++---- .../IntegrationsExtensionsTest.cs | 18 +- .../Integrations/GitHub/GitHubLinkTask.cs | 5 +- .../Integrations/GitLab/GitLabLinkTask.cs | 5 +- .../Integrations/IntegrationsExtensions.cs | 20 +-- 6 files changed, 238 insertions(+), 83 deletions(-) diff --git a/src/ChangeLog.Test/Integrations/GitHub/GitHubLinkTaskTest.cs b/src/ChangeLog.Test/Integrations/GitHub/GitHubLinkTaskTest.cs index 7c82fdc2..fa7cbb68 100644 --- a/src/ChangeLog.Test/Integrations/GitHub/GitHubLinkTaskTest.cs +++ b/src/ChangeLog.Test/Integrations/GitHub/GitHubLinkTaskTest.cs @@ -80,7 +80,7 @@ public void Serialize(IXunitSerializationInfo info) } private readonly ILogger m_Logger; - private readonly ChangeLogConfiguration m_DefaultConfiguration; + private readonly GitHubClientMock m_GithubClientMock; private readonly Mock m_GitHubClientFactoryMock; @@ -88,8 +88,6 @@ public GitHubLinkTaskTest(ITestOutputHelper testOutputHelper) { m_Logger = new XunitLogger(testOutputHelper); - m_DefaultConfiguration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); - m_GithubClientMock = new(); m_GitHubClientFactoryMock = new(MockBehavior.Strict); @@ -105,7 +103,7 @@ public void Logger_must_not_be_null() // ARRANGE // ACT - var ex = Record.Exception(() => new GitHubLinkTask(null!, m_DefaultConfiguration, Mock.Of(MockBehavior.Strict), Mock.Of(MockBehavior.Strict))); + var ex = Record.Exception(() => new GitHubLinkTask(null!, new ChangeLogConfiguration(), Mock.Of(MockBehavior.Strict), Mock.Of(MockBehavior.Strict))); // ASSERT var argumentNullException = Assert.IsType(ex); @@ -131,7 +129,7 @@ public void GitRepository_must_not_be_null() // ARRANGE // ACT - var ex = Record.Exception(() => new GitHubLinkTask(m_Logger, m_DefaultConfiguration, null!, Mock.Of(MockBehavior.Strict))); + var ex = Record.Exception(() => new GitHubLinkTask(m_Logger, new ChangeLogConfiguration(), null!, Mock.Of(MockBehavior.Strict))); // ASSERT var argumentNullException = Assert.IsType(ex); @@ -144,7 +142,7 @@ public void GitHubClientFactoty_must_not_be_null() // ARRANGE // ACT - var ex = Record.Exception(() => new GitHubLinkTask(m_Logger, m_DefaultConfiguration, Mock.Of(MockBehavior.Strict), null!)); + var ex = Record.Exception(() => new GitHubLinkTask(m_Logger, new ChangeLogConfiguration(), Mock.Of(MockBehavior.Strict), null!)); // ASSERT var argumentNullException = Assert.IsType(ex); @@ -155,10 +153,14 @@ public void GitHubClientFactoty_must_not_be_null() public async Task Run_does_nothing_if_repository_does_not_have_remotes() { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupEmptyRemotes(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog(); // ACT @@ -169,6 +171,39 @@ public async Task Run_does_nothing_if_repository_does_not_have_remotes() m_GitHubClientFactoryMock.Verify(x => x.CreateClient(It.IsAny()), Times.Never); } + [Theory] + [InlineData(ChangeLogConfiguration.IntegrationProvider.None)] + [InlineData(ChangeLogConfiguration.IntegrationProvider.GitLab)] + public async Task Task_is_skipped_if_GitHub_integration_is_disabled(ChangeLogConfiguration.IntegrationProvider integrationProvider) + { + // ARRANGE + var repoMock = new Mock(MockBehavior.Strict); + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = integrationProvider; + } + + var changeLog = new ApplicationChangeLog() + { + GetSingleVersionChangeLog( + version: "1.2.3", + entries: new [] + { + GetChangeLogEntry() + }) + }; + + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); + + // ACT + var result = await sut.RunAsync(changeLog); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Skipped, result); + repoMock.Verify(x => x.Remotes, Times.Never); + m_GitHubClientFactoryMock.Verify(x => x.CreateClient(It.IsAny()), Times.Never); + } + [Theory] [InlineData("origin", "not-a-url")] [InlineData("origin", "http://not-a-github-url.com")] @@ -178,7 +213,10 @@ public async Task Run_does_nothing_if_remote_url_cannot_be_parsed(string remoteN { // ARRANGE var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); - configuration.Integrations.GitHub.RemoteName = remoteName; + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + configuration.Integrations.GitHub.RemoteName = remoteName; + } var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes(remoteName, url); @@ -411,7 +449,10 @@ public async Task Run_uses_the_expected_remote_url(GitHubProjectInfoTestCase tes // Configure remote name to use var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); - configuration.Integrations.GitHub = testCase.Configuration; + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + configuration.Integrations.GitHub = testCase.Configuration; + } // Prepare changelog var changeLog = new ApplicationChangeLog() @@ -458,6 +499,12 @@ public async Task Run_uses_the_expected_remote_url(GitHubProjectInfoTestCase tes public async Task Run_adds_issue_links_to_footers(string footerText, int issueNumber, string owner, string repo) { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git"); @@ -469,7 +516,7 @@ public async Task Run_adds_issue_links_to_footers(string footerText, int issueNu .Setup(x => x.Get(owner, repo, issueNumber)) .ReturnsTestIssue(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -515,6 +562,11 @@ public async Task Run_adds_issue_links_to_footers(string footerText, int issueNu public async Task Run_adds_pull_request_links_to_footers(string footerText, int prNumber, string owner, string repo) { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git"); @@ -531,7 +583,7 @@ public async Task Run_adds_pull_request_links_to_footers(string footerText, int .ReturnsTestPullRequest(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -578,6 +630,11 @@ public async Task Run_adds_pull_request_links_to_footers(string footerText, int public async Task Run_ignores_footers_which_cannot_be_parsed(string footerText) { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git"); @@ -585,7 +642,7 @@ public async Task Run_ignores_footers_which_cannot_be_parsed(string footerText) .SetupGet() .ReturnsTestCommit(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -626,6 +683,11 @@ public async Task Run_ignores_footers_which_cannot_be_parsed(string footerText) public async Task Run_does_not_add_a_links_to_footers_if_no_issue_or_pull_request_cannot_be_found(string footerText, string owner, string repo) { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git"); @@ -641,7 +703,7 @@ public async Task Run_does_not_add_a_links_to_footers_if_no_issue_or_pull_reques .SetupGet() .ThrowsNotFound(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -681,10 +743,15 @@ public async Task Run_does_not_add_a_links_to_footers_if_no_issue_or_pull_reques public async Task Run_creates_client_through_client_factory(string hostName) { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", $"http://{hostName}/owner/repo.git"); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); // ACT var result = await sut.RunAsync(new ApplicationChangeLog()); @@ -700,6 +767,11 @@ public async Task Run_creates_client_through_client_factory(string hostName) public async Task Task_fails_if_GitHub_client_throws_an_ApiException() { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", $"http://github.com/owner/repo.git"); @@ -707,7 +779,7 @@ public async Task Task_fails_if_GitHub_client_throws_an_ApiException() .SetupGet() .ThrowsAsync(new ApiException()); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -736,6 +808,11 @@ public async Task Task_fails_if_GitHub_client_throws_an_ApiException() public async Task Run_adds_web_links_to_commit_references() { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git"); @@ -743,7 +820,7 @@ public async Task Run_adds_web_links_to_commit_references() .Setup(x => x.Get("owner", "repo", It.IsAny())) .ReturnsTestCommit(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -783,6 +860,11 @@ public async Task Run_adds_web_links_to_commit_references() public async Task Run_ignores_commit_references_that_cannot_be_found() { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitHub; + } + var repoMock = new Mock(MockBehavior.Strict); repoMock.SetupRemotes("origin", "http://github.com/owner/repo.git"); @@ -790,7 +872,7 @@ public async Task Run_ignores_commit_references_that_cannot_be_found() .Setup(x => x.Get("owner", "repo", It.IsAny())) .ThrowsNotFound(); - var sut = new GitHubLinkTask(m_Logger, m_DefaultConfiguration, repoMock.Object, m_GitHubClientFactoryMock.Object); + var sut = new GitHubLinkTask(m_Logger, configuration, repoMock.Object, m_GitHubClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { diff --git a/src/ChangeLog.Test/Integrations/GitLab/GitLabLinkTaskTest.cs b/src/ChangeLog.Test/Integrations/GitLab/GitLabLinkTaskTest.cs index cd124cf9..db51379c 100644 --- a/src/ChangeLog.Test/Integrations/GitLab/GitLabLinkTaskTest.cs +++ b/src/ChangeLog.Test/Integrations/GitLab/GitLabLinkTaskTest.cs @@ -85,7 +85,6 @@ public void Serialize(IXunitSerializationInfo info) } private readonly ILogger m_Logger; - private readonly ChangeLogConfiguration m_DefaultConfiguration; private readonly Mock m_ClientFactoryMock; private readonly GitLabClientMock m_ClientMock; private readonly Mock m_RepositoryMock; @@ -94,7 +93,6 @@ public void Serialize(IXunitSerializationInfo info) public GitLabLinkTaskTest(ITestOutputHelper testOutputHelper) { m_Logger = new XunitLogger(testOutputHelper); - m_DefaultConfiguration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); m_ClientFactoryMock = new(MockBehavior.Strict); m_ClientMock = new(); m_ClientFactoryMock.Setup(x => x.CreateClient(It.IsAny())).Returns(m_ClientMock.Object); @@ -143,17 +141,54 @@ private bool AssertAction(Action actionToVerify, params Action[] assert return true; } + [Theory] + [InlineData(ChangeLogConfiguration.IntegrationProvider.None)] + [InlineData(ChangeLogConfiguration.IntegrationProvider.GitHub)] + public async Task Task_is_skipped_if_GitLab_integration_is_disabled(ChangeLogConfiguration.IntegrationProvider integrationProvider) + { + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = integrationProvider; + } + + var changeLog = new ApplicationChangeLog() + { + GetSingleVersionChangeLog( + version: "1.2.3", + entries: new [] + { + GetChangeLogEntry() + }) + }; + + + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + + // ACT + var result = await sut.RunAsync(changeLog); + + // ASSERT + Assert.Equal(ChangeLogTaskResult.Skipped, result); + m_RepositoryMock.Verify(x => x.Remotes, Times.Never); + m_ClientFactoryMock.Verify(x => x.CreateClient(It.IsAny()), Times.Never); + } [Fact] public async Task Run_does_nothing_if_repository_does_not_have_remotes() { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupEmptyRemotes(); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog(); - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -167,13 +202,18 @@ public async Task Run_does_nothing_if_repository_does_not_have_remotes() [InlineData("http://not-a-gitlab-url.com")] public async Task Run_does_nothing_if_remote_url_cannot_be_parsed(string url) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", url); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog(); - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -187,12 +227,17 @@ public async Task Run_does_nothing_if_remote_url_cannot_be_parsed(string url) [InlineData("example.com")] public async Task Run_creates_client_through_client_factory(string hostName) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", $"http://{hostName}/owner/repo.git"); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); - // ACT + // ACT var result = await sut.RunAsync(new ApplicationChangeLog()); // ASSERT @@ -208,7 +253,12 @@ public async Task Run_creates_client_through_client_factory(string hostName) [InlineData("anotherOwner/anotherRepo#42", 42, "anotherOwner/anotherRepo")] public async Task Run_adds_issue_links_to_footers(string footerText, int id, string projectPath) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/owner/repo.git"); m_ClientMock.Commits @@ -221,7 +271,7 @@ public async Task Run_adds_issue_links_to_footers(string footerText, int id, str new Issue() { WebUrl = $"https://example.com/{projectPath}/issues/{id}" } ); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -235,7 +285,7 @@ public async Task Run_adds_issue_links_to_footers(string footerText, int id, str ) }; - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -269,7 +319,12 @@ public async Task Run_adds_issue_links_to_footers(string footerText, int id, str [InlineData("anotherOwner/anotherRepo#42", 42, "anotherOwner/anotherRepo")] public async Task Run_does_not_add_link_if_issue_cannot_be_found(string footerText, int id, string projectPath) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/owner/repo.git"); m_ClientMock.Commits @@ -280,7 +335,7 @@ public async Task Run_does_not_add_link_if_issue_cannot_be_found(string footerTe .SetupGetAsync() .ThrowsNotFound(); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -294,7 +349,7 @@ public async Task Run_does_not_add_link_if_issue_cannot_be_found(string footerTe ) }; - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -321,7 +376,12 @@ public async Task Run_does_not_add_link_if_issue_cannot_be_found(string footerTe [InlineData("anotherOwner/anotherRepo!42", 42, "anotherOwner/anotherRepo")] public async Task Run_adds_merge_request_links_to_footers(string footerText, int id, string projectPath) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/owner/repo.git"); m_ClientMock.Commits @@ -334,7 +394,7 @@ public async Task Run_adds_merge_request_links_to_footers(string footerText, int new List() { new MergeRequest() { WebUrl = $"https://example.com/{projectPath}/merge_requests/{id}" } } ); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -348,7 +408,7 @@ public async Task Run_adds_merge_request_links_to_footers(string footerText, int ) }; - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -394,7 +454,12 @@ public async Task Run_adds_merge_request_links_to_footers(string footerText, int [InlineData("anotherOwner/anotherRepo!42", "anotherOwner/anotherRepo")] public async Task Run_does_not_add_link_if_merge_request_cannot_be_found(string footerText, string projectPath) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/owner/repo.git"); m_ClientMock.Commits @@ -405,7 +470,7 @@ public async Task Run_does_not_add_link_if_merge_request_cannot_be_found(string .Setup(x => x.GetAsync(It.IsAny(), It.IsAny>())) .ThrowsNotFound(); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -419,7 +484,7 @@ public async Task Run_does_not_add_link_if_merge_request_cannot_be_found(string ) }; - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -446,7 +511,12 @@ public async Task Run_does_not_add_link_if_merge_request_cannot_be_found(string [InlineData("anotherOwner/anotherRepo%42", 42, "anotherOwner/anotherRepo")] public async Task Run_adds_milestone_links_to_footers(string footerText, int id, string projectPath) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/owner/repo.git"); m_ClientMock.Commits @@ -459,7 +529,7 @@ public async Task Run_adds_milestone_links_to_footers(string footerText, int id, new List() { new() { WebUrl = $"https://example.com/{projectPath}/milestones/{id}" } } ); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -473,7 +543,7 @@ public async Task Run_adds_milestone_links_to_footers(string footerText, int id, ) }; - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -518,7 +588,12 @@ public async Task Run_adds_milestone_links_to_footers(string footerText, int id, [InlineData("anotherOwner/anotherRepo%42", "anotherOwner/anotherRepo")] public async Task Run_does_not_add_link_if_milestone_cannot_be_found(string footerText, string projectPath) { - // ARRANGE + // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/owner/repo.git"); m_ClientMock.Commits @@ -529,7 +604,7 @@ public async Task Run_does_not_add_link_if_milestone_cannot_be_found(string foot .SetupGetMilestonesAsync() .ThrowsNotFound(); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -543,7 +618,7 @@ public async Task Run_does_not_add_link_if_milestone_cannot_be_found(string foot ) }; - // ACT + // ACT var result = await sut.RunAsync(changeLog); // ASSERT @@ -812,10 +887,14 @@ public async Task Run_uses_the_expected_remote_url(GitLabProjectInfoTestCase tes }) }) }; - var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); - config.Integrations.GitLab = testCase.Configuration; - var sut = new GitLabLinkTask(m_Logger, config, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + configuration.Integrations.GitLab = testCase.Configuration; + } + + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); // // ACT @@ -838,13 +917,18 @@ public async Task Run_uses_the_expected_remote_url(GitLabProjectInfoTestCase tes public async Task Run_adds_web_links_to_commit_references() { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/user/repo.git"); m_ClientMock.Commits .Setup(x => x.GetAsync(MatchProjectId("user/repo"), It.IsAny())) .ReturnsTestCommit(sha => $"https://example.com/commit/{sha}"); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { @@ -884,13 +968,18 @@ public async Task Run_adds_web_links_to_commit_references() public async Task Run_ignores_commit_references_that_cannot_be_found() { // ARRANGE + var configuration = ChangeLogConfigurationLoader.GetDefaultConfiguration(); + { + configuration.Integrations.Provider = ChangeLogConfiguration.IntegrationProvider.GitLab; + } + m_RepositoryMock.SetupRemotes("origin", "http://gitlab.com/user/repo.git"); m_ClientMock.Commits .Setup(x => x.GetAsync(MatchProjectId("user/repo"), It.IsAny())) .ThrowsNotFound(); - var sut = new GitLabLinkTask(m_Logger, m_DefaultConfiguration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); + var sut = new GitLabLinkTask(m_Logger, configuration, m_RepositoryMock.Object, m_ClientFactoryMock.Object); var changeLog = new ApplicationChangeLog() { diff --git a/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs b/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs index f6575ee5..d81a47ac 100644 --- a/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs +++ b/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs @@ -44,13 +44,11 @@ public void RegisterIntegrations_registers_expected_types() AutofacAssert.CanResolveType(container); } - [Theory] - [CombinatorialData] - public void AddIntegrationTasks_adds_expected_tasks(ChangeLogConfiguration.IntegrationProvider integrationProvider) + [Fact] + public void AddIntegrationTasks_adds_expected_tasks() { // ARRANGE var configuration = new ChangeLogConfiguration(); - configuration.Integrations.Provider = integrationProvider; using var container = BuildContainer(b => b.RegisterInstance(configuration)); @@ -63,16 +61,8 @@ public void AddIntegrationTasks_adds_expected_tasks(ChangeLogConfiguration.Integ pipelineBuilderMock.Object.AddIntegrationTasks(); // ASSERT - pipelineBuilderMock.Verify( - x => x.AddTask(), - integrationProvider == ChangeLogConfiguration.IntegrationProvider.GitHub ? Times.Once() : Times.Never() - ); - pipelineBuilderMock.Verify( - x => x.AddTask(), - integrationProvider == ChangeLogConfiguration.IntegrationProvider.GitLab ? Times.Once() : Times.Never() - ); + pipelineBuilderMock.Verify(x => x.AddTask(), Times.Once); + pipelineBuilderMock.Verify(x => x.AddTask(), Times.Once); } - - } } diff --git a/src/ChangeLog/Integrations/GitHub/GitHubLinkTask.cs b/src/ChangeLog/Integrations/GitHub/GitHubLinkTask.cs index 3131bb4b..bb448de6 100644 --- a/src/ChangeLog/Integrations/GitHub/GitHubLinkTask.cs +++ b/src/ChangeLog/Integrations/GitHub/GitHubLinkTask.cs @@ -18,7 +18,7 @@ namespace Grynwald.ChangeLog.Integrations.GitHub /// [BeforeTask(typeof(RenderTemplateTask))] [AfterTask(typeof(ParseCommitsTask))] - // AddCommitFooterTask must run before eGitHubLinkTask so a web link can be added to the "Commit" footer + // AddCommitFooterTask must run before GitHubLinkTask so a web link can be added to the "Commit" footer [AfterTask(typeof(AddCommitFooterTask))] internal sealed class GitHubLinkTask : IChangeLogTask { @@ -39,6 +39,9 @@ public GitHubLinkTask(ILogger logger, ChangeLogConfiguration con public async Task RunAsync(ApplicationChangeLog changeLog) { + if (m_Configuration.Integrations.Provider != ChangeLogConfiguration.IntegrationProvider.GitHub) + return ChangeLogTaskResult.Skipped; + var projectInfo = GetProjectInfo(); if (projectInfo != null) { diff --git a/src/ChangeLog/Integrations/GitLab/GitLabLinkTask.cs b/src/ChangeLog/Integrations/GitLab/GitLabLinkTask.cs index 78603084..6e0321fb 100644 --- a/src/ChangeLog/Integrations/GitLab/GitLabLinkTask.cs +++ b/src/ChangeLog/Integrations/GitLab/GitLabLinkTask.cs @@ -17,7 +17,7 @@ namespace Grynwald.ChangeLog.Integrations.GitLab { [BeforeTask(typeof(RenderTemplateTask))] [AfterTask(typeof(ParseCommitsTask))] - // AddCommitFooterTask must run before eGitHubLinkTask so a web link can be added to the "Commit" footer + // AddCommitFooterTask must run before GitHubLinkTask so a web link can be added to the "Commit" footer [AfterTask(typeof(AddCommitFooterTask))] internal sealed class GitLabLinkTask : IChangeLogTask { @@ -39,6 +39,9 @@ public GitLabLinkTask(ILogger logger, ChangeLogConfiguration con /// public async Task RunAsync(ApplicationChangeLog changeLog) { + if (m_Configuration.Integrations.Provider != ChangeLogConfiguration.IntegrationProvider.GitLab) + return ChangeLogTaskResult.Skipped; + var projectInfo = GetProjectInfo(); if (projectInfo != null) { diff --git a/src/ChangeLog/Integrations/IntegrationsExtensions.cs b/src/ChangeLog/Integrations/IntegrationsExtensions.cs index 1580baca..dce0a641 100644 --- a/src/ChangeLog/Integrations/IntegrationsExtensions.cs +++ b/src/ChangeLog/Integrations/IntegrationsExtensions.cs @@ -1,5 +1,4 @@ using Autofac; -using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Integrations.GitHub; using Grynwald.ChangeLog.Integrations.GitLab; using Grynwald.ChangeLog.Pipeline; @@ -18,20 +17,9 @@ public static void RegisterIntegrations(this ContainerBuilder containerBuilder) } - public static IChangeLogPipelineBuilder AddIntegrationTasks(this IChangeLogPipelineBuilder pipelineBuilder) - { - var configuration = pipelineBuilder.Container.Resolve(); - - if (configuration.Integrations.Provider == ChangeLogConfiguration.IntegrationProvider.GitHub) - { - pipelineBuilder = pipelineBuilder.AddTask(); - } - else if (configuration.Integrations.Provider == ChangeLogConfiguration.IntegrationProvider.GitLab) - { - pipelineBuilder = pipelineBuilder.AddTask(); - } - - return pipelineBuilder; - } + public static IChangeLogPipelineBuilder AddIntegrationTasks(this IChangeLogPipelineBuilder pipelineBuilder) => + pipelineBuilder + .AddTask() + .AddTask(); } } From 406fa1b6599d255dfcb776ac6e7395c8deeab154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Gr=C3=BCnwald?= Date: Mon, 6 Jun 2022 21:47:41 +0200 Subject: [PATCH 5/6] Remove ChangeLogPipelineBuilder Remove ChangeLogPipelineBuilder and directory create ChangeLogPipeline through the DI container (injecting all regsitered implementations of IChangeLogTask) --- .../IntegrationsExtensionsTest.cs | 35 ++------ .../Pipeline/ChangeLogPipelineBuilderTest.cs | 89 ------------------- .../Pipeline/ChangeLogPipelineTest.cs | 26 ++++-- src/ChangeLog/Commands/GenerateCommand.cs | 43 +++------ .../Integrations/IntegrationsExtensions.cs | 10 +-- src/ChangeLog/Pipeline/ChangeLogPipeline.cs | 10 ++- .../Pipeline/ChangeLogPipelineBuilder.cs | 40 --------- .../Pipeline/IChangeLogPipelineBuilder.cs | 16 ---- 8 files changed, 52 insertions(+), 217 deletions(-) delete mode 100644 src/ChangeLog.Test/Pipeline/ChangeLogPipelineBuilderTest.cs delete mode 100644 src/ChangeLog/Pipeline/ChangeLogPipelineBuilder.cs delete mode 100644 src/ChangeLog/Pipeline/IChangeLogPipelineBuilder.cs diff --git a/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs b/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs index d81a47ac..25168216 100644 --- a/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs +++ b/src/ChangeLog.Test/Integrations/IntegrationsExtensionsTest.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Autofac; using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Git; @@ -37,32 +38,12 @@ public void RegisterIntegrations_registers_expected_types() }); // ASSERT - AutofacAssert.CanResolveType(container); - AutofacAssert.CanResolveType(container); - - AutofacAssert.CanResolveType(container); - AutofacAssert.CanResolveType(container); - } - - [Fact] - public void AddIntegrationTasks_adds_expected_tasks() - { - // ARRANGE - var configuration = new ChangeLogConfiguration(); - - using var container = BuildContainer(b => b.RegisterInstance(configuration)); - - var pipelineBuilderMock = new Mock(MockBehavior.Strict); - pipelineBuilderMock.Setup(x => x.Container).Returns(container); - pipelineBuilderMock.Setup(x => x.AddTask()).Returns(pipelineBuilderMock.Object); - pipelineBuilderMock.Setup(x => x.AddTask()).Returns(pipelineBuilderMock.Object); - - // ACT - pipelineBuilderMock.Object.AddIntegrationTasks(); - - // ASSERT - pipelineBuilderMock.Verify(x => x.AddTask(), Times.Once); - pipelineBuilderMock.Verify(x => x.AddTask(), Times.Once); + var tasks = container.Resolve>(); + Assert.Collection( + tasks.OrderBy(x => x.GetType().Name), + x => Assert.IsType(x), + x => Assert.IsType(x) + ); } } } diff --git a/src/ChangeLog.Test/Pipeline/ChangeLogPipelineBuilderTest.cs b/src/ChangeLog.Test/Pipeline/ChangeLogPipelineBuilderTest.cs deleted file mode 100644 index 7df04279..00000000 --- a/src/ChangeLog.Test/Pipeline/ChangeLogPipelineBuilderTest.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Threading.Tasks; -using Autofac; -using Grynwald.ChangeLog.Model; -using Grynwald.ChangeLog.Pipeline; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Xunit; - -namespace Grynwald.ChangeLog.Test.Pipeline -{ - /// - /// Tests for - /// - public class ChangeLogPipelineBuilderTest : ContainerTestBase - { - [Fact] - public void Container_must_not_be_null() - { - Assert.Throws(() => new ChangeLogPipelineBuilder(null!)); - } - - [Fact] - public void Constructor_initializes_Container_property() - { - // ARRANGE - var container = Mock.Of(MockBehavior.Strict); - - // ACT - var pipelineBuilder = new ChangeLogPipelineBuilder(container); - - // ASSERT - Assert.NotNull(pipelineBuilder.Container); - Assert.Same(container, pipelineBuilder.Container); - } - - private class TestTask : IChangeLogTask - { - public Task RunAsync(ApplicationChangeLog changeLog) => throw new NotImplementedException(); - } - - [Fact] - public void AddTask_resolves_task_from_the_container() - { - // ARRANGE - var task = new TestTask(); - - using var container = BuildContainer(b => - { - b.RegisterInstance(task); - }); - - var pipelineBuilder = new ChangeLogPipelineBuilder(container); - - // ACT - _ = pipelineBuilder.AddTask(); - - // ASSERT - var registeredTask = Assert.Single(pipelineBuilder.Tasks); - Assert.Same(task, registeredTask); - } - - [Fact] - public void Build_creates_a_ChangeLogPipeline_using_the_container() - { - // ARRANGE - var task = new TestTask(); - - using var container = BuildContainer(b => - { - b.RegisterType(); - b.RegisterInstance(NullLogger.Instance).As>(); - b.RegisterInstance(task); - }); - - var pipelineBuilder = new ChangeLogPipelineBuilder(container); - _ = pipelineBuilder.AddTask(); - - // ACT - var pipeline = pipelineBuilder.Build(); - - // ASSERT - Assert.NotNull(pipeline); - var registeredTask = Assert.Single(pipeline.Tasks); - Assert.Same(task, registeredTask); - } - } -} diff --git a/src/ChangeLog.Test/Pipeline/ChangeLogPipelineTest.cs b/src/ChangeLog.Test/Pipeline/ChangeLogPipelineTest.cs index fc14bf36..741eb709 100644 --- a/src/ChangeLog.Test/Pipeline/ChangeLogPipelineTest.cs +++ b/src/ChangeLog.Test/Pipeline/ChangeLogPipelineTest.cs @@ -30,7 +30,7 @@ public void Logger_must_not_be_null() // ARRANGE // ACT - var ex = Record.Exception(() => new ChangeLogPipeline(null!, Array.Empty())); + var ex = Record.Exception(() => new ChangeLogPipeline(logger: null!, tasks: new[] { Mock.Of() })); // ASSERT var argumentNullException = Assert.IsType(ex); @@ -43,15 +43,29 @@ public void Tasks_must_not_be_null() // ARRANGE // ACT - var ex = Record.Exception(() => new ChangeLogPipeline(m_Logger, null!)); + var ex = Record.Exception(() => new ChangeLogPipeline(logger: m_Logger, tasks: null!)); // ASSERT var argumentNullException = Assert.IsType(ex); Assert.Equal("tasks", argumentNullException.ParamName); } + [Fact] + public void Tasks_must_not_be_empty() + { + // ARRANGE + + // ACT + var ex = Record.Exception(() => new ChangeLogPipeline(logger: m_Logger, tasks: Array.Empty())); + + // ASSERT + var argumentException = Assert.IsType(ex); + Assert.Equal("tasks", argumentException.ParamName); + Assert.Contains("Task list must not be empty", ex.Message); + } + + [Theory] - [InlineData(0)] [InlineData(1)] [InlineData(10)] public async Task Run_executes_all_tasks_in_the_insertion_order(int numberOfTasks) @@ -66,7 +80,7 @@ public async Task Run_executes_all_tasks_in_the_insertion_order(int numberOfTask }) .ToArray(); - var sut = new ChangeLogPipeline(m_Logger, tasks.Select(x => x.Object)); + var sut = new ChangeLogPipeline(m_Logger, tasks.Select(x => x.Object).ToArray()); // ACT var result = await sut.RunAsync(); @@ -102,7 +116,7 @@ public async Task Run_aborts_execution_if_a_task_fails() tasks[1].Setup(x => x.RunAsync(It.IsAny())).Returns(Task.FromResult(ChangeLogTaskResult.Error)); - var sut = new ChangeLogPipeline(m_Logger, tasks.Select(x => x.Object)); + var sut = new ChangeLogPipeline(m_Logger, tasks.Select(x => x.Object).ToArray()); // ACT var result = await sut.RunAsync(); @@ -149,7 +163,7 @@ public async Task Run_continues_execution_if_a_task_is_skipped() tasks[1].Setup(x => x.RunAsync(It.IsAny())).Returns(Task.FromResult(ChangeLogTaskResult.Skipped)); - var sut = new ChangeLogPipeline(m_Logger, tasks.Select(x => x.Object)); + var sut = new ChangeLogPipeline(m_Logger, tasks.Select(x => x.Object).ToArray()); // ACT var result = await sut.RunAsync(); diff --git a/src/ChangeLog/Commands/GenerateCommand.cs b/src/ChangeLog/Commands/GenerateCommand.cs index 74ac69d7..5977249f 100644 --- a/src/ChangeLog/Commands/GenerateCommand.cs +++ b/src/ChangeLog/Commands/GenerateCommand.cs @@ -95,19 +95,19 @@ public async Task RunAsync() containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); - containerBuilder.RegisterType(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterType().As(); containerBuilder.RegisterIntegrations(); @@ -129,22 +129,7 @@ public async Task RunAsync() return 1; } - var pipeline = new ChangeLogPipelineBuilder(container) - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddTask() - .AddIntegrationTasks() - .AddTask() - .Build(); + var pipeline = container.Resolve(); var result = await pipeline.RunAsync(); return result.Success ? 0 : 1; diff --git a/src/ChangeLog/Integrations/IntegrationsExtensions.cs b/src/ChangeLog/Integrations/IntegrationsExtensions.cs index dce0a641..7b68568b 100644 --- a/src/ChangeLog/Integrations/IntegrationsExtensions.cs +++ b/src/ChangeLog/Integrations/IntegrationsExtensions.cs @@ -10,16 +10,10 @@ internal static class IntegrationsExtensions public static void RegisterIntegrations(this ContainerBuilder containerBuilder) { containerBuilder.RegisterType().As(); - containerBuilder.RegisterType(); + containerBuilder.RegisterType().As(); containerBuilder.RegisterType().As(); - containerBuilder.RegisterType(); + containerBuilder.RegisterType().As(); } - - - public static IChangeLogPipelineBuilder AddIntegrationTasks(this IChangeLogPipelineBuilder pipelineBuilder) => - pipelineBuilder - .AddTask() - .AddTask(); } } diff --git a/src/ChangeLog/Pipeline/ChangeLogPipeline.cs b/src/ChangeLog/Pipeline/ChangeLogPipeline.cs index a62aa2c5..aa4e7cc9 100644 --- a/src/ChangeLog/Pipeline/ChangeLogPipeline.cs +++ b/src/ChangeLog/Pipeline/ChangeLogPipeline.cs @@ -17,9 +17,15 @@ public sealed class ChangeLogPipeline public IEnumerable Tasks => m_Tasks; - public ChangeLogPipeline(ILogger logger, IEnumerable tasks) + public ChangeLogPipeline(ILogger logger, IReadOnlyList tasks) { - m_Tasks = (tasks ?? throw new ArgumentNullException(nameof(tasks))).ToList(); + if (tasks is null) + throw new ArgumentNullException(nameof(tasks)); + + if (tasks.Count == 0) + throw new ArgumentException("Task list must not be empty", nameof(tasks)); + + m_Tasks = tasks; m_Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } diff --git a/src/ChangeLog/Pipeline/ChangeLogPipelineBuilder.cs b/src/ChangeLog/Pipeline/ChangeLogPipelineBuilder.cs deleted file mode 100644 index b6183b9c..00000000 --- a/src/ChangeLog/Pipeline/ChangeLogPipelineBuilder.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using Autofac; -using Grynwald.ChangeLog.Tasks; - -namespace Grynwald.ChangeLog.Pipeline -{ - internal class ChangeLogPipelineBuilder : IChangeLogPipelineBuilder - { - private readonly List m_Tasks; - - - public IContainer Container { get; } - - public IEnumerable Tasks => m_Tasks; - - - public ChangeLogPipelineBuilder(IContainer container) - { - Container = container ?? throw new ArgumentNullException(nameof(container)); - m_Tasks = new List(); - } - - - public IChangeLogPipelineBuilder AddTask() where T : IChangeLogTask - { - var task = Container.Resolve(); - m_Tasks.Add(task); - return this; - } - - - public ChangeLogPipeline Build() - { - return Container.Resolve( - TypedParameter.From>(m_Tasks) - ); - } - } -} diff --git a/src/ChangeLog/Pipeline/IChangeLogPipelineBuilder.cs b/src/ChangeLog/Pipeline/IChangeLogPipelineBuilder.cs deleted file mode 100644 index 7b071b66..00000000 --- a/src/ChangeLog/Pipeline/IChangeLogPipelineBuilder.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using Autofac; - -namespace Grynwald.ChangeLog.Pipeline -{ - internal interface IChangeLogPipelineBuilder - { - IContainer Container { get; } - - IEnumerable Tasks { get; } - - IChangeLogPipelineBuilder AddTask() where T : IChangeLogTask; - - ChangeLogPipeline Build(); - } -} From 127dbb42aa031db5d14d6e70f9a43f35a6aa1920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Gr=C3=BCnwald?= Date: Mon, 6 Jun 2022 22:36:33 +0200 Subject: [PATCH 6/6] Start implementing CompositionRoot Add CompositionRoot which will be responsible for creating all commands and required types. Currently, the implementation is incomplete and composition is still split between CompositionRoot and GenerateCommand (which uses the Autofac DI container) --- src/ChangeLog/Commands/GenerateCommand.cs | 114 +++++++++++----------- src/ChangeLog/CompositionRoot.cs | 52 ++++++++++ src/ChangeLog/Program.cs | 9 +- 3 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 src/ChangeLog/CompositionRoot.cs diff --git a/src/ChangeLog/Commands/GenerateCommand.cs b/src/ChangeLog/Commands/GenerateCommand.cs index 5977249f..605c629a 100644 --- a/src/ChangeLog/Commands/GenerateCommand.cs +++ b/src/ChangeLog/Commands/GenerateCommand.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using Autofac; +using FluentValidation; using Grynwald.ChangeLog.CommandLine; using Grynwald.ChangeLog.Configuration; using Grynwald.ChangeLog.Filtering; @@ -13,7 +14,6 @@ using Grynwald.ChangeLog.Tasks; using Grynwald.ChangeLog.Templates; using Grynwald.Utilities.Configuration; -using Grynwald.Utilities.Logging; using Microsoft.Extensions.Logging; @@ -39,59 +39,42 @@ private class DynamicallyDeterminedSettings private readonly GenerateCommandLineParameters m_CommandLineParameters; + private readonly ILogger m_Logger; + private readonly IValidator m_CommandLineValidator; + private readonly IValidator m_ConfigurationValidator; - - public GenerateCommand(GenerateCommandLineParameters commandLineParameters) + public GenerateCommand(GenerateCommandLineParameters commandLine, ILogger logger, IValidator commandLineValidator, IValidator configurationValidator) { - m_CommandLineParameters = commandLineParameters ?? throw new ArgumentNullException(nameof(commandLineParameters)); + m_CommandLineParameters = commandLine ?? throw new ArgumentNullException(nameof(commandLine)); + m_Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + m_CommandLineValidator = commandLineValidator ?? throw new ArgumentNullException(nameof(commandLineValidator)); + m_ConfigurationValidator = configurationValidator ?? throw new ArgumentNullException(nameof(configurationValidator)); } public async Task RunAsync() { - var loggerOptions = m_CommandLineParameters.Verbose - ? new SimpleConsoleLoggerConfiguration(LogLevel.Debug, true, true) - : new SimpleConsoleLoggerConfiguration(LogLevel.Information, false, true); - - // for validation of command line parameters, directly create a console logger - // bypassing the DI container because we need to validate the parameters - // before setting up DI - var logger = new SimpleConsoleLogger(loggerOptions, ""); - - if (!ValidateCommandlineParameters(m_CommandLineParameters, logger)) + if (!ValidateCommandlineParameters(m_CommandLineParameters)) return 1; - if (!TryGetRepositoryPath(m_CommandLineParameters, logger, out var repositoryPath)) + if (!TryGetRepositoryPath(m_CommandLineParameters, out var repositoryPath)) return 1; - if (!TryOpenRepository(repositoryPath, logger, out var gitRepository)) + if (!TryLoadConfiguration(repositoryPath, out var configuration)) return 1; - var configurationFilePath = GetConfigurationFilePath(m_CommandLineParameters, repositoryPath); - if (File.Exists(configurationFilePath)) - logger.LogDebug($"Using configuration file '{configurationFilePath}'"); - else - logger.LogDebug("Continuing without loading a configuration file, because no configuration file was wound"); - - - // pass repository path to configuration loader to make it available through the configuration system - var dynamicSettings = new DynamicallyDeterminedSettings() - { - RepositoryPath = repositoryPath - }; - - var configuration = ChangeLogConfigurationLoader.GetConfiguration(configurationFilePath, m_CommandLineParameters, dynamicSettings); + if (!TryOpenRepository(repositoryPath, out var gitRepository)) + return 1; using (gitRepository) { var containerBuilder = new ContainerBuilder(); - containerBuilder.RegisterType(); containerBuilder.RegisterInstance(configuration).SingleInstance(); containerBuilder.RegisterInstance(gitRepository).SingleInstance().As(); - containerBuilder.RegisterLogging(loggerOptions); + containerBuilder.RegisterLogging(CompositionRoot.CreateLoggerConfiguration(m_CommandLineParameters.Verbose)); containerBuilder.RegisterType(); @@ -115,20 +98,6 @@ public async Task RunAsync() using (var container = containerBuilder.Build()) { - var configurationValidator = container.Resolve(); - var validationResult = configurationValidator.Validate(configuration); - - if (!validationResult.IsValid) - { - foreach (var error in validationResult.Errors) - { - logger.LogError($"Invalid configuration: {error.ErrorMessage}"); - } - - logger.LogError($"Validation of configuration failed"); - return 1; - } - var pipeline = container.Resolve(); var result = await pipeline.RunAsync(); @@ -137,6 +106,7 @@ public async Task RunAsync() } } + private static string? GetConfigurationFilePath(GenerateCommandLineParameters commandlineParameters, string repositoryPath) { if (!String.IsNullOrEmpty(commandlineParameters.ConfigurationFilePath)) @@ -153,20 +123,19 @@ public async Task RunAsync() return null; } - private static bool ValidateCommandlineParameters(GenerateCommandLineParameters parameters, ILogger logger) + private bool ValidateCommandlineParameters(GenerateCommandLineParameters parameters) { - var validator = new GenerateCommandLineParametersValidator(); - var result = validator.Validate(parameters); + var result = m_CommandLineValidator.Validate(parameters); foreach (var error in result.Errors) { - logger.LogError(error.ToString()); + m_Logger.LogError(error.ToString()); } return result.IsValid; } - private static bool TryGetRepositoryPath(GenerateCommandLineParameters parameters, ILogger logger, [NotNullWhen(true)] out string? repositoryPath) + private bool TryGetRepositoryPath(GenerateCommandLineParameters parameters, [NotNullWhen(true)] out string? repositoryPath) { if (!String.IsNullOrEmpty(parameters.RepositoryPath)) { @@ -176,18 +145,18 @@ private static bool TryGetRepositoryPath(GenerateCommandLineParameters parameter if (RepositoryLocator.TryGetRepositoryPath(Environment.CurrentDirectory, out repositoryPath)) { - logger.LogInformation($"Found git repository at '{repositoryPath}'"); + m_Logger.LogInformation($"Found git repository at '{repositoryPath}'"); return true; } else { - logger.LogError($"No git repository found in '{Environment.CurrentDirectory}' or any of its parent directories"); + m_Logger.LogError($"No git repository found in '{Environment.CurrentDirectory}' or any of its parent directories"); repositoryPath = default; return false; } } - private static bool TryOpenRepository(string repositoryPath, ILogger logger, [NotNullWhen(true)] out IGitRepository? repository) + private bool TryOpenRepository(string repositoryPath, [NotNullWhen(true)] out IGitRepository? repository) { try { @@ -196,12 +165,45 @@ private static bool TryOpenRepository(string repositoryPath, ILogger logger, [No } catch (RepositoryNotFoundException ex) { - logger.LogDebug(ex, $"Failed to open repository at '{repositoryPath}'"); - logger.LogError($"'{repositoryPath}' is not a git repository"); + m_Logger.LogDebug(ex, $"Failed to open repository at '{repositoryPath}'"); + m_Logger.LogError($"'{repositoryPath}' is not a git repository"); repository = default; return false; } } + private bool TryLoadConfiguration(string repositoryPath, [NotNullWhen(true)] out ChangeLogConfiguration? configuration) + { + var configurationFilePath = GetConfigurationFilePath(m_CommandLineParameters, repositoryPath); + if (File.Exists(configurationFilePath)) + m_Logger.LogDebug($"Using configuration file '{configurationFilePath}'"); + else + m_Logger.LogDebug("Continuing without loading a configuration file, because no configuration file was wound"); + + + // pass repository path to configuration loader to make it available through the configuration system + var dynamicSettings = new DynamicallyDeterminedSettings() + { + RepositoryPath = repositoryPath + }; + + configuration = ChangeLogConfigurationLoader.GetConfiguration(configurationFilePath, m_CommandLineParameters, dynamicSettings); + + var validationResult = m_ConfigurationValidator.Validate(configuration); + + if (!validationResult.IsValid) + { + foreach (var error in validationResult.Errors) + { + m_Logger.LogError($"Invalid configuration: {error.ErrorMessage}"); + } + + m_Logger.LogError($"Validation of configuration failed"); + return false; + } + + return true; + + } } } diff --git a/src/ChangeLog/CompositionRoot.cs b/src/ChangeLog/CompositionRoot.cs new file mode 100644 index 00000000..b4e1eec9 --- /dev/null +++ b/src/ChangeLog/CompositionRoot.cs @@ -0,0 +1,52 @@ +using System; +using Grynwald.ChangeLog.CommandLine; +using Grynwald.ChangeLog.Commands; +using Grynwald.ChangeLog.Configuration; +using Grynwald.Utilities.Logging; +using Microsoft.Extensions.Logging; + +namespace Grynwald.ChangeLog +{ + internal class CompositionRoot : IDisposable + { + + public GenerateCommand CreateGenerateCommand(GenerateCommandLineParameters commandLine) + { + if (commandLine is null) + throw new ArgumentNullException(nameof(commandLine)); + + return new GenerateCommand( + commandLine: commandLine, + logger: CreateLogger(commandLine.Verbose), + commandLineValidator: new GenerateCommandLineParametersValidator(), + configurationValidator: new ConfigurationValidator() + ); + } + + + + public ILogger CreateLogger(bool verbose) + { + var loggerFactory = new LoggerFactory(); + var provider = new SimpleConsoleLoggerProvider(CreateLoggerConfiguration(verbose)); + loggerFactory.AddProvider(provider); + + return new Logger(loggerFactory); + } + + + //TODO: Temporary workaround + public static SimpleConsoleLoggerConfiguration CreateLoggerConfiguration(bool verbose) + { + return verbose + ? new SimpleConsoleLoggerConfiguration(LogLevel.Debug, true, true) + : new SimpleConsoleLoggerConfiguration(LogLevel.Information, false, true); + } + + + public void Dispose() + { + //TODO + } + } +} diff --git a/src/ChangeLog/Program.cs b/src/ChangeLog/Program.cs index c9773eda..c3ad01c7 100644 --- a/src/ChangeLog/Program.cs +++ b/src/ChangeLog/Program.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using CommandLine; using Grynwald.ChangeLog.CommandLine; -using Grynwald.ChangeLog.Commands; namespace Grynwald.ChangeLog { @@ -12,13 +11,11 @@ internal static class Program { private static async Task Main(string[] args) { + using var compositionRoot = new CompositionRoot(); + return await CommandLineParser.Parse(args) .MapResult( - (GenerateCommandLineParameters parsed) => - { - var command = new GenerateCommand(parsed); - return command.RunAsync(); - }, + async (GenerateCommandLineParameters generateParameters) => await compositionRoot.CreateGenerateCommand(generateParameters).RunAsync(), (DummyCommandLineParameters dummy) => { Console.Error.WriteLine("Invalid arguments");