Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public override async Task<List<Version>> GetTagHistoryAsync()
.ToList();
// Filter out non-version tags
return tagNames
.Select(Version.Create)
.Select(Version.TryCreate)
.Where(t => t != null)
.Cast<Version>()
.ToList();
Expand All @@ -103,16 +103,21 @@ public override async Task<List<string>> GetPullRequestsBetweenTagsAsync(Version
{
range = "HEAD";
}
else if (from == null)
else if (from == null && to != null)
{
range = ValidateTag(to!.Tag);
range = ValidateTag(to.Tag);
}
else if (to == null)
else if (to == null && from != null)
{
range = $"{ValidateTag(from.Tag)}..HEAD";
}
else
{
// Both from and to are not null (verified by the preceding conditions)
if (from == null || to == null)
{
throw new InvalidOperationException("Unexpected null version");
}
range = $"{ValidateTag(from.Tag)}..{ValidateTag(to.Tag)}";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public Task<List<Version>> GetTagHistoryAsync()
{
// Use dictionary keys to avoid duplication, filter out non-version tags
var tagInfoList = _tagHashes.Keys
.Select(Version.Create)
.Select(Version.TryCreate)
.Where(t => t != null)
.Cast<Version>()
.ToList();
Expand Down
20 changes: 18 additions & 2 deletions src/DemaConsulting.BuildMark/Version.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public partial record Version(string Tag, string FullVersion, string SemanticVer
private static partial Regex TagPattern();

/// <summary>
/// Creates a Version from a tag string, or returns null if the tag doesn't match version format.
/// Tries to create a Version from a tag string.
/// </summary>
/// <param name="tag">Tag name to parse.</param>
/// <returns>Version instance if tag matches version format, null otherwise.</returns>
public static Version? Create(string tag)
public static Version? TryCreate(string tag)
{
var match = TagPattern().Match(tag);
if (!match.Success)
Expand Down Expand Up @@ -83,4 +83,20 @@ public partial record Version(string Tag, string FullVersion, string SemanticVer

return new Version(tag, fullVersion, version, preRelease, metadata, hasPreRelease);
}

/// <summary>
/// Creates a Version from a tag string.
/// </summary>
/// <param name="tag">Tag name to parse.</param>
/// <returns>Version instance.</returns>
/// <exception cref="ArgumentException">Thrown if tag doesn't match version format.</exception>
public static Version Create(string tag)
{
var version = TryCreate(tag);
if (version == null)
{
throw new ArgumentException($"Tag '{tag}' does not match version format", nameof(tag));
}
return version;
}
}
30 changes: 17 additions & 13 deletions test/DemaConsulting.BuildMark.Tests/BuildInformationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task BuildInformation_CreateAsync_WorksWithExplicitVersion()
var connector = new MockRepoConnector();

// Act
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.1.0")!);
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.1.0"));

// Assert
Assert.AreEqual("v2.1.0", buildInfo.ToVersion.Tag);
Expand Down Expand Up @@ -105,7 +105,7 @@ public async Task BuildInformation_CreateAsync_PreReleaseUsesPreviousTag()
var connector = new MockRepoConnector();

// Act
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0-beta.1")!);
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0-beta.1"));

// Assert
Assert.AreEqual("v2.0.0-beta.1", buildInfo.ToVersion.Tag);
Expand All @@ -122,7 +122,7 @@ public async Task BuildInformation_CreateAsync_ReleaseSkipsPreReleases()
var connector = new MockRepoConnector();

// Act
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0")!);
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0"));

// Assert
Assert.AreEqual("v2.0.0", buildInfo.ToVersion.Tag);
Expand All @@ -139,7 +139,7 @@ public async Task BuildInformation_CreateAsync_CollectsIssuesCorrectly()
var connector = new MockRepoConnector();

// Act
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("ver-1.1.0")!);
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("ver-1.1.0"));

// Assert
Assert.HasCount(1, buildInfo.ChangeIssues);
Expand All @@ -165,7 +165,7 @@ public async Task BuildInformation_CreateAsync_SeparatesBugAndChangeIssues()
var connector = new MockRepoConnector();

// Act
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0")!);
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v2.0.0"));

// Assert
Assert.HasCount(2, buildInfo.ChangeIssues);
Expand All @@ -184,7 +184,7 @@ public async Task BuildInformation_CreateAsync_HandlesFirstReleaseCorrectly()
var connector = new MockRepoConnector();

// Act
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v1.0.0")!);
var buildInfo = await BuildInformation.CreateAsync(connector, Version.Create("v1.0.0"));

// Assert
Assert.IsNull(buildInfo.FromVersion);
Expand Down Expand Up @@ -212,8 +212,10 @@ private class MockRepoConnectorEmpty : IRepoConnector
/// </summary>
private class MockRepoConnectorMismatch : IRepoConnector
{
public Task<List<Version>> GetTagHistoryAsync() =>
Task.FromResult(new List<Version> { Version.Create("v1.0.0")! });
public Task<List<Version>> GetTagHistoryAsync()
{
return Task.FromResult(new List<Version> { Version.Create("v1.0.0") });
}
public Task<List<string>> GetPullRequestsBetweenTagsAsync(Version? from, Version? to) => Task.FromResult(new List<string>());
public Task<List<string>> GetIssuesForPullRequestAsync(string pullRequestId) => Task.FromResult(new List<string>());
public Task<string> GetIssueTitleAsync(string issueId) => Task.FromResult("Title");
Expand All @@ -228,13 +230,15 @@ public Task<List<Version>> GetTagHistoryAsync() =>
/// </summary>
private class MockRepoConnectorMatchingTag : IRepoConnector
{
public Task<List<Version>> GetTagHistoryAsync() =>
Task.FromResult(new List<Version>
public Task<List<Version>> GetTagHistoryAsync()
{
return Task.FromResult(new List<Version>
{
Version.Create("v1.0.0")!,
Version.Create("ver-1.1.0")!,
Version.Create("v2.0.0")!
Version.Create("v1.0.0"),
Version.Create("ver-1.1.0"),
Version.Create("v2.0.0")
});
}
public Task<List<string>> GetPullRequestsBetweenTagsAsync(Version? from, Version? to) => Task.FromResult(new List<string>());
public Task<List<string>> GetIssuesForPullRequestAsync(string pullRequestId) => Task.FromResult(new List<string>());
public Task<string> GetIssueTitleAsync(string issueId) => Task.FromResult("Title");
Expand Down
10 changes: 6 additions & 4 deletions test/DemaConsulting.BuildMark.Tests/GitHubRepoConnectorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ public async Task GitHubRepoConnector_GetPullRequestsBetweenTagsAsync_ReturnsExp
"abc123 Merge pull request #10 from feature/x\ndef456 Merge pull request #11 from bugfix/y");

// Act
var prs = await connector.GetPullRequestsBetweenTagsAsync(Version.Create("v1.0.0")!, Version.Create("v2.0.0")!);
var prs = await connector.GetPullRequestsBetweenTagsAsync(
Version.Create("v1.0.0"),
Version.Create("v2.0.0"));

// Assert
Assert.HasCount(2, prs);
Expand All @@ -135,7 +137,7 @@ public async Task GitHubRepoConnector_GetPullRequestsBetweenTagsAsync_HandlesNul
"abc123 Merge pull request #10 from feature/x");

// Act
var prs = await connector.GetPullRequestsBetweenTagsAsync(null, Version.Create("v1.0.0")!);
var prs = await connector.GetPullRequestsBetweenTagsAsync(null, Version.Create("v1.0.0"));

// Assert
Assert.HasCount(1, prs);
Expand All @@ -156,7 +158,7 @@ public async Task GitHubRepoConnector_GetPullRequestsBetweenTagsAsync_HandlesNul
"abc123 Merge pull request #11 from feature/y");

// Act
var prs = await connector.GetPullRequestsBetweenTagsAsync(Version.Create("v1.0.0")!, null);
var prs = await connector.GetPullRequestsBetweenTagsAsync(Version.Create("v1.0.0"), null);

// Assert
Assert.HasCount(1, prs);
Expand Down Expand Up @@ -305,7 +307,7 @@ public async Task GitHubRepoConnector_GetHashForTagAsync_ReturnsExpectedHash()
connector.AddCommandResult("git", "rev-parse v1.0.0", "abc123def456789");

// Act
var hash = await connector.GetHashForTagAsync(Version.Create("v1.0.0")!.Tag);
var hash = await connector.GetHashForTagAsync(Version.Create("v1.0.0").Tag);

// Assert
Assert.AreEqual("abc123def456789", hash);
Expand Down
12 changes: 8 additions & 4 deletions test/DemaConsulting.BuildMark.Tests/MockRepoConnectorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ public async Task MockRepoConnector_GetPullRequestsBetweenTagsAsync_ReturnsExpec
var connector = new MockRepoConnector();

// Act
var prs = await connector.GetPullRequestsBetweenTagsAsync(Version.Create("v1.0.0")!, Version.Create("ver-1.1.0")!);
var prs = await connector.GetPullRequestsBetweenTagsAsync(
Version.Create("v1.0.0"),
Version.Create("ver-1.1.0"));

// Assert
Assert.HasCount(1, prs);
Expand All @@ -74,7 +76,9 @@ public async Task MockRepoConnector_GetPullRequestsBetweenTagsAsync_ReturnsExpec
var connector = new MockRepoConnector();

// Act
var prs = await connector.GetPullRequestsBetweenTagsAsync(Version.Create("ver-1.1.0")!, Version.Create("2.0.0")!);
var prs = await connector.GetPullRequestsBetweenTagsAsync(
Version.Create("ver-1.1.0"),
Version.Create("2.0.0"));

// Assert
Assert.HasCount(2, prs);
Expand Down Expand Up @@ -205,7 +209,7 @@ public async Task MockRepoConnector_GetHashForTagAsync_ReturnsExpectedHash()
var connector = new MockRepoConnector();

// Act
var hash = await connector.GetHashForTagAsync(Version.Create("v1.0.0")!.Tag);
var hash = await connector.GetHashForTagAsync(Version.Create("v1.0.0").Tag);

// Assert
Assert.AreEqual("abc123def456", hash);
Expand Down Expand Up @@ -237,7 +241,7 @@ public async Task MockRepoConnector_GetHashForTagAsync_ReturnsUnknownHashForUnkn
var connector = new MockRepoConnector();

// Act
var hash = await connector.GetHashForTagAsync(Version.Create("v999.0.0")!.Tag);
var hash = await connector.GetHashForTagAsync(Version.Create("v999.0.0").Tag);

// Assert
Assert.AreEqual("unknown000hash000", hash);
Expand Down
46 changes: 31 additions & 15 deletions test/DemaConsulting.BuildMark.Tests/VersionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public void TagInfo_Constructor_ParsesSimpleVPrefix()
{
// Arrange & Act
var tagVersion = Version.Create("v1.2.3");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v1.2.3", tagVersion.Tag);
Expand All @@ -50,7 +49,6 @@ public void TagInfo_Constructor_ParsesVerPrefix()
{
// Arrange & Act
var tagVersion = Version.Create("ver-2.0.0");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("ver-2.0.0", tagVersion.Tag);
Expand All @@ -66,7 +64,6 @@ public void TagInfo_Constructor_ParsesReleaseUnderscorePrefix()
{
// Arrange & Act
var tagVersion = Version.Create("release_3.1.4");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("release_3.1.4", tagVersion.Tag);
Expand All @@ -82,7 +79,6 @@ public void TagInfo_Constructor_DetectsAlphaPreRelease()
{
// Arrange & Act
var tagVersion = Version.Create("v2.0.0-alpha.1");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v2.0.0-alpha.1", tagVersion.Tag);
Expand All @@ -98,7 +94,6 @@ public void TagInfo_Constructor_DetectsBetaPreRelease()
{
// Arrange & Act
var tagVersion = Version.Create("v2.0.0-beta.2");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v2.0.0-beta.2", tagVersion.Tag);
Expand All @@ -114,7 +109,6 @@ public void TagInfo_Constructor_DetectsRcPreRelease()
{
// Arrange & Act
var tagVersion = Version.Create("v2.0.0-rc.1");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v2.0.0-rc.1", tagVersion.Tag);
Expand All @@ -130,7 +124,6 @@ public void TagInfo_Constructor_DetectsPrePreRelease()
{
// Arrange & Act
var tagVersion = Version.Create("v2.0.0-pre");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v2.0.0-pre", tagVersion.Tag);
Expand All @@ -146,7 +139,6 @@ public void TagInfo_Constructor_HandlesNoPrefix()
{
// Arrange & Act
var tagVersion = Version.Create("1.0.0");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("1.0.0", tagVersion.Tag);
Expand All @@ -162,7 +154,6 @@ public void TagInfo_Constructor_HandlesComplexPrefix()
{
// Arrange & Act
var tagVersion = Version.Create("my-awesome-release_1.2.3-beta");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("my-awesome-release_1.2.3-beta", tagVersion.Tag);
Expand All @@ -178,7 +169,6 @@ public void TagInfo_Constructor_ParsesBuildMetadata()
{
// Arrange & Act
var tagVersion = Version.Create("v1.0.0+arch");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v1.0.0+arch", tagVersion.Tag);
Expand All @@ -194,7 +184,6 @@ public void TagInfo_Constructor_AcceptsDotSeparatedAsPreRelease()
{
// Arrange & Act
var tagVersion = Version.Create("v1.0.0.arch");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v1.0.0.arch", tagVersion.Tag);
Expand All @@ -210,7 +199,6 @@ public void TagInfo_Constructor_ParsesComplexVersionWithMetadata()
{
// Arrange & Act
var tagVersion = Version.Create("Rel_1.2.3.rc.4+build.5");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("Rel_1.2.3.rc.4+build.5", tagVersion.Tag);
Expand All @@ -226,7 +214,6 @@ public void TagInfo_Constructor_ParsesComplexVersionWithHyphenPreRelease()
{
// Arrange & Act
var tagVersion = Version.Create("Rel_1.2.3-rc.4+build.5");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("Rel_1.2.3-rc.4+build.5", tagVersion.Tag);
Expand All @@ -242,7 +229,6 @@ public void TagInfo_Constructor_CorrectlyDetectsRcWithNumber()
{
// Arrange & Act
var tagVersion = Version.Create("v1.0.0-rc1");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v1.0.0-rc1", tagVersion.Tag);
Expand All @@ -258,11 +244,41 @@ public void TagInfo_Constructor_ParsesPreReleaseWithBuildMetadata()
{
// Arrange & Act
var tagVersion = Version.Create("v2.0.0-beta.1+linux.x64");
Assert.IsNotNull(tagVersion);

// Assert
Assert.AreEqual("v2.0.0-beta.1+linux.x64", tagVersion.Tag);
Assert.AreEqual("2.0.0-beta.1+linux.x64", tagVersion.FullVersion);
Assert.IsTrue(tagVersion.IsPreRelease);
}

/// <summary>
/// Test that TryCreate returns null for invalid tag.
/// </summary>
[TestMethod]
public void Version_TryCreate_ReturnsNullForInvalidTag()
{
// Arrange & Act
var tagVersion = Version.TryCreate("not-a-version");

// Assert
Assert.IsNull(tagVersion);
}

/// <summary>
/// Test that Create throws ArgumentException for invalid tag.
/// </summary>
[TestMethod]
public void Version_Create_ThrowsForInvalidTag()
{
// Arrange, Act & Assert
try
{
_ = Version.Create("not-a-version");
Assert.Fail("Expected ArgumentException to be thrown");
}
catch (ArgumentException)
{
// Expected exception
}
}
}