From 97979bfe202152a478b84b1a00c625ec239b0ce7 Mon Sep 17 00:00:00 2001 From: "Emil H. Clausen" Date: Wed, 16 Sep 2026 13:36:29 +0200 Subject: [PATCH] fix: portal base URL for Capability.NameLink variable wrong protocol --- .../TestTemplateRenderingService.cs | 18 ++++++++++++++++++ .../Persistence/TemplateRenderingService.cs | 12 ++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/SelfService.Tests/Infrastructure/Persistence/TestTemplateRenderingService.cs b/src/SelfService.Tests/Infrastructure/Persistence/TestTemplateRenderingService.cs index 1162afeb..0f61cc5e 100644 --- a/src/SelfService.Tests/Infrastructure/Persistence/TestTemplateRenderingService.cs +++ b/src/SelfService.Tests/Infrastructure/Persistence/TestTemplateRenderingService.cs @@ -58,6 +58,24 @@ public void RenderTemplate_ExistingCapabilityVariables_StillWork() Assert.Equal("My Capability - A test capability (5 members)", result); } + [Theory] + [InlineData("https://build.dfds.cloud")] + [InlineData("https://build.dfds.cloud/")] + [InlineData("build.dfds.cloud")] + public void RenderTemplate_CapabilityNameLink_UsesPortalBaseUrlWithSingleScheme(string portalBaseUrl) + { + var configMock = new Mock(); + configMock.Setup(c => c["SS_PORTAL_BASE_URL"]).Returns(portalBaseUrl); + var sut = new TemplateRenderingService(configMock.Object); + + var capability = A.Capability.WithId("my-capability-abc12").WithName("My Capability").Build(); + var context = CreateContext(capability: capability); + + var result = sut.RenderTemplate("{{Capability.NameLink}}", context); + + Assert.Equal("My Capability", result); + } + [Fact] public void RenderTemplate_MemberVariables_StillWork() { diff --git a/src/SelfService/Infrastructure/Persistence/TemplateRenderingService.cs b/src/SelfService/Infrastructure/Persistence/TemplateRenderingService.cs index faf3fa11..0492c9f8 100644 --- a/src/SelfService/Infrastructure/Persistence/TemplateRenderingService.cs +++ b/src/SelfService/Infrastructure/Persistence/TemplateRenderingService.cs @@ -80,9 +80,10 @@ private sealed record PatternVariable( public TemplateRenderingService(IConfiguration configuration) { - _portalBaseUrl = + var portalBaseUrl = configuration["SS_PORTAL_BASE_URL"] ?? throw new InvalidOperationException("SS_PORTAL_BASE_URL configuration is required but not set."); + _portalBaseUrl = NormalizePortalBaseUrl(portalBaseUrl); _variables = InitializeVariables(); _byName = _variables.OfType().ToDictionary(v => v.Name); _patterns = _variables.OfType().ToArray(); @@ -118,7 +119,7 @@ private VariableEntry[] InitializeVariables() return ""; var capId = ctx.Capability.Id.ToString(); var name = ctx.Capability.Name; - return $"{name}"; + return $"{name}"; } ), new StaticVariable( @@ -473,6 +474,13 @@ private string ExpandUserCapabilitiesBlocks(string template, TemplateRenderConte ); } + // Local configuration omits the scheme; production includes it. + private static string NormalizePortalBaseUrl(string value) + { + var trimmed = value.Trim().TrimEnd('/'); + return trimmed.Contains("://", StringComparison.Ordinal) ? trimmed : "https://" + trimmed; + } + private string? Resolve(string name, TemplateRenderContext context) { if (_byName.TryGetValue(name, out var def))