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
47 changes: 47 additions & 0 deletions BuildMonitor.Test/BranchUrlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.BuildMonitor.Test;

using ktsu.Semantics.Strings;

using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// Covers "Open Branch URL" and "Copy Branch URL". The branch name went into the URL unescaped, so a
/// <c>#</c> started a fragment and a <c>%</c> read as a percent-escape, and the link pointed at the
/// wrong branch or a 404 (ktsu-dev/BuildMonitor#386).
/// </summary>
[TestClass]
public sealed class BranchUrlTests
{
private sealed class StubProvider : BuildProvider
{
internal override BuildProviderName Name { get; } = "Stub".As<BuildProviderName>();

internal override Task UpdateRepositoriesAsync(Owner owner) => Task.CompletedTask;
internal override Task UpdateBuildsAsync(Repository repository) => Task.CompletedTask;
internal override Task UpdateBuildAsync(Build build) => Task.CompletedTask;
internal override Task UpdateRunAsync(Run run) => Task.CompletedTask;
}

private static Build CreateBuild()
{
Owner owner = new StubProvider().CreateOwner("o".As<OwnerName>());
Repository repository = owner.CreateRepository("r".As<RepositoryName>());
return repository.CreateBuild("build".As<BuildName>());
}

[TestMethod]
[DataRow("feature/foo", "https://github.com/o/r/tree/feature/foo")]
[DataRow("main", "https://github.com/o/r/tree/main")]
[DataRow("fix/#123-crash", "https://github.com/o/r/tree/fix/%23123-crash")]
[DataRow("feature/50%", "https://github.com/o/r/tree/feature/50%25")]
public void BranchUrlEscapesEachSegmentAndKeepsSeparators(string branch, string expected)
{
// Act
string url = BuildMonitor.GetBranchUrl(CreateBuild(), branch.As<BranchName>());

// Assert
Assert.AreEqual(expected, url);
}
}
7 changes: 6 additions & 1 deletion BuildMonitor/BuildMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
/// fields actually occupy tests the bug itself, so a fixed binding is recognised as fixed and
/// anything else is refused rather than guessed at.
/// </remarks>
private static unsafe int? ProbeNativeImGuiTableColumnSize()

Check warning on line 236 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Avoid using this unsafe code block.

Check warning on line 236 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Avoid using this unsafe code block.
{
int csharpSize = sizeof(ImGuiTableColumn);
int widthGivenOffset;
Expand Down Expand Up @@ -332,7 +332,7 @@
return narrowFieldBytes == NarrowlyBoundColumnFields.Length * 2 ? csharpSize : null;
}

private static unsafe void SaveColumnWidth(string columnName, int columnIndex)

Check warning on line 335 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Avoid using this unsafe code block.

Check warning on line 335 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Avoid using this unsafe code block.
{
// Null means the layout probe rejected the binding, so there is no address we can trust.
if (NativeImGuiTableColumnSize is not int nativeStructSize)
Expand Down Expand Up @@ -459,7 +459,7 @@
SaveSettingsIfRequired();
}

private static void UpdateOwnerTabs()

Check warning on line 462 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 462 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.
{
// Build expected tabs based on provider type:
// - GitHub: one tab per owner (org/user)
Expand All @@ -468,7 +468,7 @@
HashSet<string> expectedTabIds = [AllOwnersTabId, LogsTabId];
Dictionary<string, (string Label, Action Content)> tabsToCreate = [];

foreach ((BuildProviderName providerName, BuildProvider provider) in AppData.BuildProviders)

Check warning on line 471 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove the unused local variable 'providerName'.

Check warning on line 471 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove the unused local variable 'providerName'.
{
if (provider is GitHub)
{
Expand Down Expand Up @@ -675,7 +675,7 @@
return displayName;
}

private static string MakeRepositoryDisplayName(Build build)

Check warning on line 678 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

All 'MakeRepositoryDisplayName' method overloads should be adjacent.

Check warning on line 678 in BuildMonitor/BuildMonitor.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

All 'MakeRepositoryDisplayName' method overloads should be adjacent.
{
string repoName = build.Repository.Name;
string ownerName = build.Owner.Name;
Expand Down Expand Up @@ -1338,7 +1338,12 @@

private static string GetWorkflowUrl(Build build) => $"https://github.com/{build.Owner.Name}/{build.Repository.Name}/actions/workflows/{build.Id}";

private static string GetBranchUrl(Build build, BranchName branch) => $"https://github.com/{build.Owner.Name}/{build.Repository.Name}/tree/{branch}";
/// <summary>
/// Escapes each segment of the branch name and keeps its <c>/</c> separators, so a <c>#</c> or
/// <c>%</c> in the name stays part of the path rather than starting a fragment or an escape.
/// </summary>
internal static string GetBranchUrl(Build build, BranchName branch) =>
$"https://github.com/{build.Owner.Name}/{build.Repository.Name}/tree/{string.Join('/', branch.ToString().Split('/').Select(Uri.EscapeDataString))}";

private static string GetRunUrl(Run run) => $"https://github.com/{run.Owner.Name}/{run.Repository.Name}/actions/runs/{run.Id}";

Expand Down
Loading