From 94a6b8ff6fabec57d13b4457c7cdd43da996a128 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:06:25 +0000 Subject: [PATCH 1/3] Initial plan From 5c23c74dadb252e3d994c786f35eac84ee20dbf7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:10:26 +0000 Subject: [PATCH 2/3] Fix store logo URL in PDF invoice by correcting GetThumbUrl absolute URL handling Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/5d80e0b7-d5a1-4e18-81b0-0a71e3c14373 Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com> --- .../Services/PictureService.cs | 4 +- .../Services/PictureServiceTests.cs | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Business/Grand.Business.Storage/Services/PictureService.cs b/src/Business/Grand.Business.Storage/Services/PictureService.cs index 57253c867..6dd60f5ca 100644 --- a/src/Business/Grand.Business.Storage/Services/PictureService.cs +++ b/src/Business/Grand.Business.Storage/Services/PictureService.cs @@ -158,8 +158,8 @@ protected virtual async Task GetThumbPhysicalPath(string thumbFileName) /// Local picture thumb path protected virtual string GetThumbUrl(string thumbFileName, string storeLocation = null) { - storeLocation = !string.IsNullOrEmpty(storeLocation) ? storeLocation : ""; - return _mediaFileStore.Combine(storeLocation, ImageThumbPath, thumbFileName); + storeLocation = !string.IsNullOrEmpty(storeLocation) ? storeLocation : string.Empty; + return storeLocation.TrimEnd('/') + _mediaFileStore.Combine(ImageThumbPath, thumbFileName); } /// diff --git a/src/Tests/Grand.Business.Storage.Tests/Services/PictureServiceTests.cs b/src/Tests/Grand.Business.Storage.Tests/Services/PictureServiceTests.cs index 86c1a7206..75575bbff 100644 --- a/src/Tests/Grand.Business.Storage.Tests/Services/PictureServiceTests.cs +++ b/src/Tests/Grand.Business.Storage.Tests/Services/PictureServiceTests.cs @@ -13,6 +13,23 @@ namespace Grand.Business.Storage.Tests.Services; +/// +/// Exposes the protected GetThumbUrl method for testing. +/// +public class TestablePictureService( + IRepository pictureRepository, + ILogger logger, + IMediator mediator, + ICacheBase cacheBase, + IMediaFileStore mediaFileStore, + MediaSettings mediaSettings, + StorageSettings storageSettings) + : PictureService(pictureRepository, logger, mediator, cacheBase, mediaFileStore, mediaSettings, storageSettings) +{ + public string GetThumbUrlPublic(string thumbFileName, string storeLocation = null) + => GetThumbUrl(thumbFileName, storeLocation); +} + [TestClass] public class PictureServiceTests { @@ -22,6 +39,7 @@ public class PictureServiceTests private Mock _mediatorMock; private Mock> _repoMock; private PictureService _service; + private TestablePictureService _testablePictureService; private MediaSettings _settings; private StorageSettings _storagesettings; @@ -42,6 +60,8 @@ public void Init() _storagesettings = new StorageSettings(); _service = new PictureService(_repoMock.Object, _logerMock.Object, _mediatorMock.Object, _cacheMock.Object, _mediaFileStoreMock.Object, _settings, _storagesettings); + _testablePictureService = new TestablePictureService(_repoMock.Object, _logerMock.Object, + _mediatorMock.Object, _cacheMock.Object, _mediaFileStoreMock.Object, _settings, _storagesettings); } [TestMethod] @@ -139,4 +159,28 @@ public async Task DeletePicture_ValidArguments_InvokeRepositoryAndPublishEvent() _repoMock.Verify(c => c.DeleteAsync(It.IsAny()), Times.Once); _mediatorMock.Verify(c => c.Publish(It.IsAny>(), default), Times.Once); } + + [TestMethod] + public void GetThumbUrl_WithAbsoluteStoreLocation_ReturnsValidAbsoluteUrl() + { + const string storeLocation = "https://example.com/"; + const string thumbFileName = "sample_123.jpeg"; + + var result = _testablePictureService.GetThumbUrlPublic(thumbFileName, storeLocation); + + Assert.IsTrue(result.StartsWith("https://"), $"Expected URL to start with 'https://' but got: {result}"); + Assert.IsTrue(result.Contains(thumbFileName), $"Expected URL to contain thumb filename but got: {result}"); + Assert.IsFalse(result.StartsWith("/https://"), $"URL must not have a leading slash before the scheme: {result}"); + } + + [TestMethod] + public void GetThumbUrl_WithNoStoreLocation_ReturnsRelativePath() + { + const string thumbFileName = "sample_123.jpeg"; + + var result = _testablePictureService.GetThumbUrlPublic(thumbFileName); + + Assert.IsTrue(result.StartsWith("/"), $"Expected relative path to start with '/' but got: {result}"); + Assert.IsTrue(result.Contains(thumbFileName), $"Expected path to contain thumb filename but got: {result}"); + } } \ No newline at end of file From aa5a594c87f764ca5a8927b9946db868145601ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:26:29 +0000 Subject: [PATCH 3/3] Use Uri class to combine store location with thumb path in GetThumbUrl Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/19f059e0-4b1b-4db9-b30b-56e92247bc80 Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com> --- .../Grand.Business.Storage/Services/PictureService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Business/Grand.Business.Storage/Services/PictureService.cs b/src/Business/Grand.Business.Storage/Services/PictureService.cs index 6dd60f5ca..e3594919c 100644 --- a/src/Business/Grand.Business.Storage/Services/PictureService.cs +++ b/src/Business/Grand.Business.Storage/Services/PictureService.cs @@ -158,8 +158,11 @@ protected virtual async Task GetThumbPhysicalPath(string thumbFileName) /// Local picture thumb path protected virtual string GetThumbUrl(string thumbFileName, string storeLocation = null) { - storeLocation = !string.IsNullOrEmpty(storeLocation) ? storeLocation : string.Empty; - return storeLocation.TrimEnd('/') + _mediaFileStore.Combine(ImageThumbPath, thumbFileName); + var thumbPath = _mediaFileStore.Combine(ImageThumbPath, thumbFileName); + if (string.IsNullOrEmpty(storeLocation)) + return thumbPath; + + return new Uri(new Uri(storeLocation), thumbPath).ToString(); } ///