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/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..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,42 +38,12 @@ public void RegisterIntegrations_registers_expected_types() }); // ASSERT - AutofacAssert.CanResolveType(container); - AutofacAssert.CanResolveType(container); - - AutofacAssert.CanResolveType(container); - AutofacAssert.CanResolveType(container); - } - - [Theory] - [CombinatorialData] - public void AddIntegrationTasks_adds_expected_tasks(ChangeLogConfiguration.IntegrationProvider integrationProvider) - { - // ARRANGE - var configuration = new ChangeLogConfiguration(); - configuration.Integrations.Provider = integrationProvider; - - 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(), - integrationProvider == ChangeLogConfiguration.IntegrationProvider.GitHub ? Times.Once() : Times.Never() - ); - pipelineBuilderMock.Verify( - x => x.AddTask(), - integrationProvider == ChangeLogConfiguration.IntegrationProvider.GitLab ? Times.Once() : Times.Never() + 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.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.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 new file mode 100644 index 00000000..605c629a --- /dev/null +++ b/src/ChangeLog/Commands/GenerateCommand.cs @@ -0,0 +1,209 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; +using Autofac; +using FluentValidation; +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 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; + private readonly ILogger m_Logger; + private readonly IValidator m_CommandLineValidator; + private readonly IValidator m_ConfigurationValidator; + + public GenerateCommand(GenerateCommandLineParameters commandLine, ILogger logger, IValidator commandLineValidator, IValidator configurationValidator) + { + 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() + { + if (!ValidateCommandlineParameters(m_CommandLineParameters)) + return 1; + + if (!TryGetRepositoryPath(m_CommandLineParameters, out var repositoryPath)) + return 1; + + if (!TryLoadConfiguration(repositoryPath, out var configuration)) + return 1; + + if (!TryOpenRepository(repositoryPath, out var gitRepository)) + return 1; + + using (gitRepository) + { + var containerBuilder = new ContainerBuilder(); + + containerBuilder.RegisterInstance(configuration).SingleInstance(); + containerBuilder.RegisterInstance(gitRepository).SingleInstance().As(); + + containerBuilder.RegisterLogging(CompositionRoot.CreateLoggerConfiguration(m_CommandLineParameters.Verbose)); + + 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(); + + containerBuilder.RegisterTemplates(); + + using (var container = containerBuilder.Build()) + { + var pipeline = container.Resolve(); + + 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 bool ValidateCommandlineParameters(GenerateCommandLineParameters parameters) + { + var result = m_CommandLineValidator.Validate(parameters); + + foreach (var error in result.Errors) + { + m_Logger.LogError(error.ToString()); + } + + return result.IsValid; + } + + private bool TryGetRepositoryPath(GenerateCommandLineParameters parameters, [NotNullWhen(true)] out string? repositoryPath) + { + if (!String.IsNullOrEmpty(parameters.RepositoryPath)) + { + repositoryPath = Path.GetFullPath(parameters.RepositoryPath); + return true; + } + + if (RepositoryLocator.TryGetRepositoryPath(Environment.CurrentDirectory, out repositoryPath)) + { + m_Logger.LogInformation($"Found git repository at '{repositoryPath}'"); + return true; + } + else + { + m_Logger.LogError($"No git repository found in '{Environment.CurrentDirectory}' or any of its parent directories"); + repositoryPath = default; + return false; + } + } + + private bool TryOpenRepository(string repositoryPath, [NotNullWhen(true)] out IGitRepository? repository) + { + try + { + repository = new GitRepository(repositoryPath); + return true; + } + catch (RepositoryNotFoundException ex) + { + 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/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/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/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/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..7b68568b 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; @@ -11,27 +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(); - } - - - 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; + containerBuilder.RegisterType().As(); } } } 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(); - } -} 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/Program.cs b/src/ChangeLog/Program.cs index dc88714f..c3ad01c7 100644 --- a/src/ChangeLog/Program.cs +++ b/src/ChangeLog/Program.cs @@ -1,51 +1,21 @@ 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; 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) { + using var compositionRoot = new CompositionRoot(); + return await CommandLineParser.Parse(args) .MapResult( - (GenerateCommandLineParameters parsed) => RunAsync(parsed), + async (GenerateCommandLineParameters generateParameters) => await compositionRoot.CreateGenerateCommand(generateParameters).RunAsync(), (DummyCommandLineParameters dummy) => { Console.Error.WriteLine("Invalid arguments"); @@ -61,188 +31,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; - } - } } } 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; 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() };