diff --git a/src/DemaConsulting.BuildMark/BuildInformation.cs b/src/DemaConsulting.BuildMark/BuildInformation.cs index c7beccf..ac3a5bf 100644 --- a/src/DemaConsulting.BuildMark/BuildInformation.cs +++ b/src/DemaConsulting.BuildMark/BuildInformation.cs @@ -233,6 +233,105 @@ public static async Task CreateAsync(IRepoConnector connector, knownIssues); } + /// + /// Generates a Markdown build report from this build information. + /// + /// Root markdown heading depth (default 1). + /// Flag for whether to include known issues (default false). + /// Markdown-formatted build report. + public string ToMarkdown(int headingDepth = 1, bool includeKnownIssues = false) + { + // Build heading prefix based on requested depth + var heading = new string('#', headingDepth); + var subHeading = new string('#', headingDepth + 1); + + // Start building the markdown report + var markdown = new System.Text.StringBuilder(); + + // Add title section + markdown.AppendLine($"{heading} Build Report"); + markdown.AppendLine(); + + // Add version information section + markdown.AppendLine($"{subHeading} Version Information"); + markdown.AppendLine(); + markdown.AppendLine("| Field | Value |"); + markdown.AppendLine("|-------|-------|"); + markdown.AppendLine($"| **Version** | {ToVersion.Tag} |"); + markdown.AppendLine($"| **Commit Hash** | {ToHash} |"); + if (FromVersion != null) + { + markdown.AppendLine($"| **Previous Version** | {FromVersion.Tag} |"); + markdown.AppendLine($"| **Previous Commit Hash** | {FromHash} |"); + } + else + { + markdown.AppendLine("| **Previous Version** | N/A |"); + markdown.AppendLine("| **Previous Commit Hash** | N/A |"); + } + markdown.AppendLine(); + + // Add changes section + markdown.AppendLine($"{subHeading} Changes"); + markdown.AppendLine(); + markdown.AppendLine("| Issue | Title |"); + markdown.AppendLine("|-------|-------|"); + if (ChangeIssues.Count > 0) + { + foreach (var issue in ChangeIssues) + { + markdown.AppendLine($"| [{issue.Id}]({issue.Url}) | {issue.Title} |"); + } + } + else + { + markdown.AppendLine("| N/A | N/A |"); + } + markdown.AppendLine(); + + // Add bugs fixed section + markdown.AppendLine($"{subHeading} Bugs Fixed"); + markdown.AppendLine(); + markdown.AppendLine("| Issue | Title |"); + markdown.AppendLine("|-------|-------|"); + if (BugIssues.Count > 0) + { + foreach (var issue in BugIssues) + { + markdown.AppendLine($"| [{issue.Id}]({issue.Url}) | {issue.Title} |"); + } + } + else + { + markdown.AppendLine("| N/A | N/A |"); + } + markdown.AppendLine(); + + // Add known issues section if requested + if (includeKnownIssues) + { + markdown.AppendLine($"{subHeading} Known Issues"); + markdown.AppendLine(); + markdown.AppendLine("| Issue | Title |"); + markdown.AppendLine("|-------|-------|"); + if (KnownIssues.Count > 0) + { + foreach (var issue in KnownIssues) + { + markdown.AppendLine($"| [{issue.Id}]({issue.Url}) | {issue.Title} |"); + } + } + else + { + markdown.AppendLine("| N/A | N/A |"); + } + markdown.AppendLine(); + } + + // Return the complete markdown report + return markdown.ToString(); + } + /// /// Finds the index of a tag in the tag history by normalized version. /// diff --git a/test/DemaConsulting.BuildMark.Tests/BuildInformationTests.cs b/test/DemaConsulting.BuildMark.Tests/BuildInformationTests.cs index e612f6f..ed2944e 100644 --- a/test/DemaConsulting.BuildMark.Tests/BuildInformationTests.cs +++ b/test/DemaConsulting.BuildMark.Tests/BuildInformationTests.cs @@ -189,6 +189,158 @@ public async Task BuildInformation_CreateAsync_HandlesFirstReleaseCorrectly() Assert.AreEqual("v1.0.0", buildInfo.ToVersion.Tag); } + /// + /// Test that ToMarkdown generates correct markdown with default parameters. + /// + [TestMethod] + public async Task BuildInformation_ToMarkdown_GeneratesCorrectMarkdownWithDefaults() + { + // Arrange + var connector = new MockRepoConnector(); + var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0")); + + // Act + var markdown = buildInfo.ToMarkdown(); + + // Assert + Assert.Contains("# Build Report", markdown); + Assert.Contains("## Version Information", markdown); + Assert.Contains("## Changes", markdown); + Assert.Contains("## Bugs Fixed", markdown); + Assert.DoesNotContain("## Known Issues", markdown); + Assert.Contains("v2.0.0", markdown); + Assert.Contains("ver-1.1.0", markdown); + } + + /// + /// Test that ToMarkdown includes known issues when requested. + /// + [TestMethod] + public async Task BuildInformation_ToMarkdown_IncludesKnownIssuesWhenRequested() + { + // Arrange + var connector = new MockRepoConnector(); + var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0")); + + // Act + var markdown = buildInfo.ToMarkdown(includeKnownIssues: true); + + // Assert + Assert.Contains("## Known Issues", markdown); + Assert.Contains("Known bug A", markdown); + Assert.Contains("Known bug B", markdown); + } + + /// + /// Test that ToMarkdown respects custom heading depth. + /// + [TestMethod] + public async Task BuildInformation_ToMarkdown_RespectsCustomHeadingDepth() + { + // Arrange + var connector = new MockRepoConnector(); + var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0")); + + // Act + var markdown = buildInfo.ToMarkdown(headingDepth: 3); + + // Assert + Assert.Contains("### Build Report", markdown); + Assert.Contains("#### Version Information", markdown); + Assert.Contains("#### Changes", markdown); + Assert.Contains("#### Bugs Fixed", markdown); + } + + /// + /// Test that ToMarkdown displays N/A for empty changes table. + /// + [TestMethod] + public void BuildInformation_ToMarkdown_DisplaysNAForEmptyChanges() + { + // Arrange - Create build info with no change issues + var buildInfo = new BuildInformation( + Version.Create("v1.0.0"), + Version.Create("v1.1.0"), + "abc123", + "def456", + new List(), // No changes + new List { new IssueInfo("2", "Bug fix", "https://example.com/2") }, + new List()); + + // Act + var markdown = buildInfo.ToMarkdown(); + + // Assert - Check that Changes section contains N/A + var changesSectionStart = markdown.IndexOf("## Changes", StringComparison.Ordinal); + var bugsSectionStart = markdown.IndexOf("## Bugs Fixed", StringComparison.Ordinal); + var changesSection = markdown.Substring(changesSectionStart, bugsSectionStart - changesSectionStart); + Assert.Contains("| N/A | N/A |", changesSection); + } + + /// + /// Test that ToMarkdown displays N/A for empty bugs table. + /// + [TestMethod] + public void BuildInformation_ToMarkdown_DisplaysNAForEmptyBugs() + { + // Arrange - Create build info with no bug issues + var buildInfo = new BuildInformation( + Version.Create("v1.0.0"), + Version.Create("v1.1.0"), + "abc123", + "def456", + new List { new IssueInfo("1", "Feature", "https://example.com/1") }, + new List(), // No bugs + new List()); + + // Act + var markdown = buildInfo.ToMarkdown(); + + // Assert - Check that Bugs Fixed section contains N/A + var bugsSectionStart = markdown.IndexOf("## Bugs Fixed", StringComparison.Ordinal); + var bugsSection = markdown.Substring(bugsSectionStart); + Assert.Contains("| N/A | N/A |", bugsSection); + } + + /// + /// Test that ToMarkdown includes issue links in tables. + /// + [TestMethod] + public async Task BuildInformation_ToMarkdown_IncludesIssueLinks() + { + // Arrange + var connector = new MockRepoConnector(); + var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0")); + + // Act + var markdown = buildInfo.ToMarkdown(); + + // Assert + Assert.Contains("[3](https://github.com/example/repo/issues/3)", markdown); + Assert.Contains("[2](https://github.com/example/repo/issues/2)", markdown); + } + + /// + /// Test that ToMarkdown handles first release with N/A for previous version. + /// + [TestMethod] + public async Task BuildInformation_ToMarkdown_HandlesFirstReleaseWithNA() + { + // Arrange + var connector = new MockRepoConnector(); + var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v1.0.0")); + + // Act + var markdown = buildInfo.ToMarkdown(); + + // Assert + var versionInfoStart = markdown.IndexOf("## Version Information", StringComparison.Ordinal); + var changesStart = markdown.IndexOf("## Changes", StringComparison.Ordinal); + var versionInfo = markdown.Substring(versionInfoStart, changesStart - versionInfoStart); + Assert.Contains("| **Previous Version** | N/A |", versionInfo); + Assert.Contains("| **Previous Commit Hash** | N/A |", versionInfo); + } + /// /// Mock connector with no tags. ///