Merge branch 'main' into v17/dev

# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
This commit is contained in:
Andy Butland
2025-08-18 07:16:06 +01:00
188 changed files with 3654 additions and 3106 deletions

View File

@@ -0,0 +1,3 @@
{
"consoleErrors": []
}

View File

@@ -8,7 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@umbraco/json-models-builders": "^2.0.37",
"@umbraco/playwright-testhelpers": "^16.0.34",
"@umbraco/playwright-testhelpers": "^16.0.36",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -67,9 +67,10 @@
}
},
"node_modules/@umbraco/playwright-testhelpers": {
"version": "16.0.34",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.34.tgz",
"integrity": "sha512-hCOqSUrTVZPNxD3DP+olYz/QFc8HwyZ1QZR6gTv87nIkAlvEjk44+7KblPartfBXQDd93uvasptr7dO3XCapZA==",
"version": "16.0.36",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.36.tgz",
"integrity": "sha512-SjPrVgWI18ErfyCUEuIwt1V7HjCGXFLae0S8u3NO74QBbOO9z79+JM0/U4Xwqwq9KdV2XMiVPkzDm/5xThSvMg==",
"license": "MIT",
"dependencies": {
"@umbraco/json-models-builders": "2.0.37",
"node-fetch": "^2.6.7"

View File

@@ -22,7 +22,7 @@
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.37",
"@umbraco/playwright-testhelpers": "^16.0.34",
"@umbraco/playwright-testhelpers": "^16.0.36",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"

View File

@@ -71,16 +71,17 @@ internal sealed class BackOfficeExamineSearcherTests : ExamineBaseTest
builder.Services.AddHostedService<QueuedHostedService>();
}
private IEnumerable<ISearchResult> BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0) =>
private IEnumerable<ISearchResult> BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0, bool ignoreUserStartNodes = false) =>
BackOfficeExamineSearcher.Search(
query,
UmbracoEntityTypes.Document,
pageSize,
pageIndex,
out _,
null,
null,
ignoreUserStartNodes: true);
totalFound: out _,
contentTypeAliases: null,
trashed: null,
searchFrom: null,
ignoreUserStartNodes: ignoreUserStartNodes);
private async Task SetupUserIdentity(string userId)
{

View File

@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
@@ -26,8 +27,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache;
internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithContent
{
private IPublishedContentCache _mockedCache;
private Mock<IDatabaseCacheRepository> _mockedNucacheRepository;
private IDocumentCacheService _mockDocumentCacheService;
private Mock<IDatabaseCacheRepository> _mockDatabaseCacheRepository;
private IDocumentCacheService _documentCacheService;
protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache();
@@ -38,7 +39,7 @@ internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithC
[SetUp]
public void SetUp()
{
_mockedNucacheRepository = new Mock<IDatabaseCacheRepository>();
_mockDatabaseCacheRepository = new Mock<IDatabaseCacheRepository>();
var contentData = new ContentData(
Textpage.Name,
@@ -76,25 +77,29 @@ internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithC
IsDraft = false,
};
_mockedNucacheRepository.Setup(r => r.GetContentSourceAsync(It.IsAny<Guid>(), true))
_mockDatabaseCacheRepository.Setup(r => r.GetContentSourceAsync(It.IsAny<Guid>(), true))
.ReturnsAsync(draftTestCacheNode);
_mockDatabaseCacheRepository.Setup(r => r.GetContentSourcesAsync(It.IsAny<IEnumerable<Guid>>(), true))
.ReturnsAsync([draftTestCacheNode]);
_mockedNucacheRepository.Setup(r => r.GetContentSourceAsync(It.IsAny<Guid>(), false))
_mockDatabaseCacheRepository.Setup(r => r.GetContentSourceAsync(It.IsAny<Guid>(), false))
.ReturnsAsync(publishedTestCacheNode);
_mockDatabaseCacheRepository.Setup(r => r.GetContentSourcesAsync(It.IsAny<IEnumerable<Guid>>(), false))
.ReturnsAsync([publishedTestCacheNode]);
_mockedNucacheRepository.Setup(r => r.GetContentByContentTypeKey(It.IsAny<IReadOnlyCollection<Guid>>(), ContentCacheDataSerializerEntityType.Document)).Returns(
_mockDatabaseCacheRepository.Setup(r => r.GetContentByContentTypeKey(It.IsAny<IReadOnlyCollection<Guid>>(), ContentCacheDataSerializerEntityType.Document)).Returns(
new List<ContentCacheNode>()
{
draftTestCacheNode,
});
_mockedNucacheRepository.Setup(r => r.DeleteContentItemAsync(It.IsAny<int>()));
_mockDatabaseCacheRepository.Setup(r => r.DeleteContentItemAsync(It.IsAny<int>()));
var mockedPublishedStatusService = new Mock<IPublishStatusQueryService>();
mockedPublishedStatusService.Setup(x => x.IsDocumentPublishedInAnyCulture(It.IsAny<Guid>())).Returns(true);
_mockDocumentCacheService = new DocumentCacheService(
_mockedNucacheRepository.Object,
_documentCacheService = new DocumentCacheService(
_mockDatabaseCacheRepository.Object,
GetRequiredService<IIdKeyMap>(),
GetRequiredService<ICoreScopeProvider>(),
GetRequiredService<Microsoft.Extensions.Caching.Hybrid.HybridCache>(),
@@ -105,9 +110,10 @@ internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithC
GetRequiredService<IPublishedModelFactory>(),
GetRequiredService<IPreviewService>(),
mockedPublishedStatusService.Object,
GetRequiredService<IDocumentNavigationQueryService>());
new NullLogger<DocumentCacheService>());
_mockedCache = new DocumentCache(_mockDocumentCacheService,
_mockedCache = new DocumentCache(
_documentCacheService,
GetRequiredService<IPublishedContentTypeCache>(),
GetRequiredService<IDocumentNavigationQueryService>(),
GetRequiredService<IDocumentUrlService>(),
@@ -118,8 +124,10 @@ internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithC
// So we'll manually create them with a magic options mock.
private IEnumerable<IDocumentSeedKeyProvider> GetSeedProviders(IPublishStatusQueryService publishStatusQueryService)
{
_cacheSettings = new CacheSettings();
_cacheSettings.DocumentBreadthFirstSeedCount = 0;
_cacheSettings = new CacheSettings
{
DocumentBreadthFirstSeedCount = 0
};
var mock = new Mock<IOptions<CacheSettings>>();
mock.Setup(m => m.Value).Returns(() => _cacheSettings);
@@ -140,7 +148,7 @@ internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithC
var textPage2 = await _mockedCache.GetByIdAsync(Textpage.Key, true);
AssertTextPage(textPage);
AssertTextPage(textPage2);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
}
[Test]
@@ -152,79 +160,79 @@ internal sealed class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithC
var textPage2 = await _mockedCache.GetByIdAsync(Textpage.Id, true);
AssertTextPage(textPage);
AssertTextPage(textPage2);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
}
[Test]
public async Task Content_Is_Seeded_By_Id()
{
var schedule = new CultureAndScheduleModel
var schedule = new CulturePublishScheduleModel
{
CulturesToPublishImmediately = new HashSet<string> { "*" }, Schedules = new ContentScheduleCollection(),
Culture = Constants.System.InvariantCulture,
};
var publishResult = await ContentPublishingService.PublishAsync(Textpage.Key, schedule, Constants.Security.SuperUserKey);
var publishResult = await ContentPublishingService.PublishAsync(Textpage.Key, [schedule], Constants.Security.SuperUserKey);
Assert.IsTrue(publishResult.Success);
Textpage.Published = true;
await _mockDocumentCacheService.DeleteItemAsync(Textpage);
await _documentCacheService.DeleteItemAsync(Textpage);
_cacheSettings.ContentTypeKeys = [ Textpage.ContentType.Key ];
await _mockDocumentCacheService.SeedAsync(CancellationToken.None);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
await _documentCacheService.SeedAsync(CancellationToken.None);
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourcesAsync(It.IsAny<IEnumerable<Guid>>(), It.IsAny<bool>()), Times.Exactly(1));
var textPage = await _mockedCache.GetByIdAsync(Textpage.Id);
AssertTextPage(textPage);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourcesAsync(It.IsAny<IEnumerable<Guid>>(), It.IsAny<bool>()), Times.Exactly(1));
}
[Test]
public async Task Content_Is_Seeded_By_Key()
{
var schedule = new CultureAndScheduleModel
var schedule = new CulturePublishScheduleModel
{
CulturesToPublishImmediately = new HashSet<string> { "*" }, Schedules = new ContentScheduleCollection(),
Culture = Constants.System.InvariantCulture,
};
var publishResult = await ContentPublishingService.PublishAsync(Textpage.Key, schedule, Constants.Security.SuperUserKey);
var publishResult = await ContentPublishingService.PublishAsync(Textpage.Key, [schedule], Constants.Security.SuperUserKey);
Assert.IsTrue(publishResult.Success);
Textpage.Published = true;
await _mockDocumentCacheService.DeleteItemAsync(Textpage);
await _documentCacheService.DeleteItemAsync(Textpage);
_cacheSettings.ContentTypeKeys = [ Textpage.ContentType.Key ];
await _mockDocumentCacheService.SeedAsync(CancellationToken.None);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
await _documentCacheService.SeedAsync(CancellationToken.None);
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourcesAsync(It.IsAny<IEnumerable<Guid>>(), It.IsAny<bool>()), Times.Exactly(1));
var textPage = await _mockedCache.GetByIdAsync(Textpage.Key);
AssertTextPage(textPage);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourcesAsync(It.IsAny<IEnumerable<Guid>>(), It.IsAny<bool>()), Times.Exactly(1));
}
[Test]
public async Task Content_Is_Not_Seeded_If_Unpblished_By_Id()
{
await _mockDocumentCacheService.DeleteItemAsync(Textpage);
await _documentCacheService.DeleteItemAsync(Textpage);
_cacheSettings.ContentTypeKeys = [ Textpage.ContentType.Key ];
await _mockDocumentCacheService.SeedAsync(CancellationToken.None);
await _documentCacheService.SeedAsync(CancellationToken.None);
var textPage = await _mockedCache.GetByIdAsync(Textpage.Id, true);
AssertTextPage(textPage);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
}
[Test]
public async Task Content_Is_Not_Seeded_If_Unpublished_By_Key()
{
_cacheSettings.ContentTypeKeys = [ Textpage.ContentType.Key ];
await _mockDocumentCacheService.DeleteItemAsync(Textpage);
await _documentCacheService.DeleteItemAsync(Textpage);
await _mockDocumentCacheService.SeedAsync(CancellationToken.None);
await _documentCacheService.SeedAsync(CancellationToken.None);
var textPage = await _mockedCache.GetByIdAsync(Textpage.Key, true);
AssertTextPage(textPage);
_mockedNucacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
_mockDatabaseCacheRepository.Verify(x => x.GetContentSourceAsync(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Exactly(1));
}
private void AssertTextPage(IPublishedContent textPage)

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Threading;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Extensions;
@@ -13,18 +12,21 @@ public abstract class RuntimeAppCacheTests : AppCacheTests
internal abstract IAppPolicyCache AppPolicyCache { get; }
[Test]
[Explicit("Testing for timeouts cannot work on VSTS.")]
public void Can_Add_And_Expire_Struct_Strongly_Typed_With_Null()
{
var now = DateTime.Now;
AppPolicyCache.Insert("DateTimeTest", () => now, new TimeSpan(0, 0, 0, 0, 200));
Assert.AreEqual(now, AppCache.GetCacheItem<DateTime>("DateTimeTest"));
Assert.AreEqual(now, AppCache.GetCacheItem<DateTime?>("DateTimeTest"));
AppPolicyCache.Insert("DateTimeTest", () => now, new TimeSpan(0, 0, 0, 0, 20));
var cachedDateTime = AppCache.GetCacheItem<DateTime>("DateTimeTest");
var cachedDateTimeNullable = AppCache.GetCacheItem<DateTime?>("DateTimeTest");
Assert.AreEqual(now, cachedDateTime);
Assert.AreEqual(now, cachedDateTimeNullable);
Thread.Sleep(300); // sleep longer than the cache expiration
Thread.Sleep(30); // sleep longer than the cache expiration
Assert.AreEqual(default(DateTime), AppCache.GetCacheItem<DateTime>("DateTimeTest"));
Assert.AreEqual(null, AppCache.GetCacheItem<DateTime?>("DateTimeTest"));
cachedDateTime = AppCache.GetCacheItem<DateTime>("DateTimeTest");
cachedDateTimeNullable = AppCache.GetCacheItem<DateTime?>("DateTimeTest");
Assert.AreEqual(default(DateTime), cachedDateTime);
Assert.AreEqual(null, cachedDateTimeNullable);
}
[Test]

View File

@@ -28,8 +28,6 @@ public class SliderPropertyValueEditorTests
true,
new object(),
new List<string> { "some", "values" },
Guid.NewGuid(),
new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid())
};
[TestCaseSource(nameof(InvalidCaseData))]
@@ -39,6 +37,20 @@ public class SliderPropertyValueEditorTests
Assert.IsNull(fromEditor);
}
[Test]
public void Can_Handle_Invalid_Values_From_Editor_Guid()
{
var fromEditor = FromEditor(Guid.NewGuid());
Assert.IsNull(fromEditor);
}
[Test]
public void Can_Handle_Invalid_Values_From_Editor_Udi()
{
var fromEditor = FromEditor(new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()));
Assert.IsNull(fromEditor);
}
[TestCase("1", 1)]
[TestCase("0", 0)]
[TestCase("-1", -1)]

View File

@@ -0,0 +1,153 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing;
[TestFixture]
public class ContentFinderByUrlNewTests
{
private const int DomainContentId = 1233;
private const int ContentId = 1234;
private static readonly Guid _contentKey = Guid.NewGuid();
private const string ContentPath = "/test-page";
private const string DomainHost = "example.com";
[TestCase(ContentPath, true)]
[TestCase("/missing-page", false)]
public async Task Can_Find_Invariant_Content(string path, bool expectSuccess)
{
var mockContent = CreateMockPublishedContent();
var mockUmbracoContextAccessor = CreateMockUmbracoContextAccessor();
var mockDocumentUrlService = CreateMockDocumentUrlService();
var mockPublishedContentCache = CreateMockPublishedContentCache(mockContent);
var sut = CreateContentFinder(mockUmbracoContextAccessor, mockDocumentUrlService, mockPublishedContentCache);
var publishedRequestBuilder = CreatePublishedRequestBuilder(path);
var result = await sut.TryFindContent(publishedRequestBuilder);
Assert.AreEqual(expectSuccess, result);
if (expectSuccess)
{
Assert.IsNotNull(publishedRequestBuilder.PublishedContent);
}
else
{
Assert.IsNull(publishedRequestBuilder.PublishedContent);
}
}
[TestCase(ContentPath, true, false, true)]
[TestCase("/missing-page", true, false, false)]
[TestCase(ContentPath, true, true, true)]
[TestCase(ContentPath, false, true, false)]
public async Task Can_Find_Invariant_Content_With_Domain(string path, bool setDomain, bool useStrictDomainMatching, bool expectSuccess)
{
var mockContent = CreateMockPublishedContent();
var mockUmbracoContextAccessor = CreateMockUmbracoContextAccessor();
var mockDocumentUrlService = CreateMockDocumentUrlService();
var mockPublishedContentCache = CreateMockPublishedContentCache(mockContent);
var sut = CreateContentFinder(
mockUmbracoContextAccessor,
mockDocumentUrlService,
mockPublishedContentCache,
new WebRoutingSettings
{
UseStrictDomainMatching = useStrictDomainMatching
});
var publishedRequestBuilder = CreatePublishedRequestBuilder(path, withDomain: setDomain);
var result = await sut.TryFindContent(publishedRequestBuilder);
Assert.AreEqual(expectSuccess, result);
if (expectSuccess)
{
Assert.IsNotNull(publishedRequestBuilder.PublishedContent);
}
else
{
Assert.IsNull(publishedRequestBuilder.PublishedContent);
}
}
private static Mock<IPublishedContent> CreateMockPublishedContent()
{
var mockContent = new Mock<IPublishedContent>();
mockContent
.SetupGet(x => x.Id)
.Returns(ContentId);
mockContent
.SetupGet(x => x.ContentType.ItemType)
.Returns(PublishedItemType.Content);
return mockContent;
}
private static Mock<IUmbracoContextAccessor> CreateMockUmbracoContextAccessor()
{
var mockUmbracoContext = new Mock<IUmbracoContext>();
var mockUmbracoContextAccessor = new Mock<IUmbracoContextAccessor>();
var umbracoContext = mockUmbracoContext.Object;
mockUmbracoContextAccessor
.Setup(x => x.TryGetUmbracoContext(out umbracoContext))
.Returns(true);
return mockUmbracoContextAccessor;
}
private static Mock<IDocumentUrlService> CreateMockDocumentUrlService()
{
var mockDocumentUrlService = new Mock<IDocumentUrlService>();
mockDocumentUrlService
.Setup(x => x.GetDocumentKeyByRoute(It.Is<string>(y => y == ContentPath), It.IsAny<string?>(), It.IsAny<int?>(), It.IsAny<bool>()))
.Returns(_contentKey);
return mockDocumentUrlService;
}
private static Mock<IPublishedContentCache> CreateMockPublishedContentCache(Mock<IPublishedContent> mockContent)
{
var mockPublishedContentCache = new Mock<IPublishedContentCache>();
mockPublishedContentCache
.Setup(x => x.GetById(It.IsAny<bool>(), It.Is<Guid>(y => y == _contentKey)))
.Returns(mockContent.Object);
return mockPublishedContentCache;
}
private static ContentFinderByUrlNew CreateContentFinder(
Mock<IUmbracoContextAccessor> mockUmbracoContextAccessor,
Mock<IDocumentUrlService> mockDocumentUrlService,
Mock<IPublishedContentCache> mockPublishedContentCache,
WebRoutingSettings? webRoutingSettings = null)
=> new(
new NullLogger<ContentFinderByUrlNew>(),
mockUmbracoContextAccessor.Object,
mockDocumentUrlService.Object,
mockPublishedContentCache.Object,
Mock.Of<IOptionsMonitor<WebRoutingSettings>>(x => x.CurrentValue == (webRoutingSettings ?? new WebRoutingSettings())));
private static PublishedRequestBuilder CreatePublishedRequestBuilder(string path, bool withDomain = false)
{
var publishedRequestBuilder = new PublishedRequestBuilder(new Uri($"https://example.com{path}"), Mock.Of<IFileService>());
if (withDomain)
{
publishedRequestBuilder.SetDomain(new DomainAndUri(new Domain(1, $"https://{DomainHost}/", DomainContentId, "en-US", false, 0), new Uri($"https://{DomainHost}{path}")));
}
return publishedRequestBuilder;
}
}

View File

@@ -4,7 +4,7 @@
using NUnit.Framework;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositories;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Repositories;
[TestFixture]
internal sealed class SimilarNodeNameTests
@@ -156,45 +156,19 @@ internal sealed class SimilarNodeNameTests
Assert.AreEqual(expected, res);
}
/* Original Tests - Can be deleted, as new tests cover all cases */
[TestCase(0, "Charlie", "Charlie")]
[TestCase(0, "Zulu", "Zulu (1)")]
[TestCase(0, "Golf", "Golf (1)")]
[TestCase(0, "Kilo", "Kilo (2)")]
[TestCase(0, "Alpha", "Alpha (3)")]
//// [TestCase(0, "Kilo (1)", "Kilo (1) (1)")] // though... we might consider "Kilo (2)"
[TestCase(0, "Kilo (1)", "Kilo (2)")] // names[] contains "Kilo" AND "Kilo (1)", which implies that result should be "Kilo (2)"
[TestCase(6, "Kilo (1)", "Kilo (1)")] // because of the id
[TestCase(0, "alpha", "alpha (3)")]
[TestCase(0, "", " (1)")]
[TestCase(0, null, " (1)")]
public void Test(int nodeId, string nodeName, string expected)
{
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Alpha (2)"}, new SimilarNodeName {Id = 2, Name = "Alpha"},
new SimilarNodeName {Id = 3, Name = "Golf"}, new SimilarNodeName {Id = 4, Name = "Zulu"},
new SimilarNodeName {Id = 5, Name = "Mike"}, new SimilarNodeName {Id = 6, Name = "Kilo (1)"},
new SimilarNodeName {Id = 7, Name = "Yankee"}, new SimilarNodeName {Id = 8, Name = "Kilo"},
new SimilarNodeName {Id = 9, Name = "Golf (2)"}, new SimilarNodeName {Id = 10, Name = "Alpha (1)"}
};
Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName));
}
[Test]
[Explicit("This test fails! We need to fix up the logic")]
public void TestMany()
public void Handles_Many_Similar_Names()
{
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Alpha (2)"}, new SimilarNodeName {Id = 2, Name = "Test"},
new SimilarNodeName {Id = 3, Name = "Test (1)"}, new SimilarNodeName {Id = 4, Name = "Test (2)"},
new SimilarNodeName {Id = 1, Name = "Alpha (2)"},
new SimilarNodeName {Id = 2, Name = "Test"},
new SimilarNodeName {Id = 3, Name = "Test (1)"},
new SimilarNodeName {Id = 4, Name = "Test (2)"},
new SimilarNodeName {Id = 22, Name = "Test (1) (1)"}
};
// TODO: this will yield "Test (2)" which is already in use
Assert.AreEqual("Test (3)", SimilarNodeName.GetUniqueName(names, 0, "Test"));
var uniqueName = SimilarNodeName.GetUniqueName(names, 0, "Test");
Assert.AreEqual("Test (3)", uniqueName);
}
}

View File

@@ -1,4 +1,4 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
@@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Infrastructure.HybridCache.SeedKeyProviders.Document;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.HybridCache;
[TestFixture]
public class DocumentBreadthFirstKeyProviderTests
{

View File

@@ -0,0 +1,186 @@
using Microsoft.Extensions.Caching.Hybrid;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Infrastructure.HybridCache.Extensions;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.HybridCache.Extensions;
/// <summary>
/// Provides tests to cover the <see cref="HybridCacheExtensions"/> class.
/// </summary>
/// <remarks>
/// Hat-tip: https://github.com/dotnet/aspnetcore/discussions/57191
/// </remarks>
[TestFixture]
public class HybridCacheExtensionsTests
{
private Mock<Microsoft.Extensions.Caching.Hybrid.HybridCache> _cacheMock;
[SetUp]
public void TestInitialize()
{
_cacheMock = new Mock<Microsoft.Extensions.Caching.Hybrid.HybridCache>();
}
[Test]
public async Task ExistsAsync_WhenKeyExists_ShouldReturnTrue()
{
// Arrange
string key = "test-key";
var expectedValue = "test-value";
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<object>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(expectedValue);
// Act
var exists = await HybridCacheExtensions.ExistsAsync(_cacheMock.Object, key);
// Assert
Assert.IsTrue(exists);
}
[Test]
public async Task ExistsAsync_WhenKeyDoesNotExist_ShouldReturnFalse()
{
// Arrange
string key = "test-key";
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<object>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.Returns((
string key,
object? state,
Func<object, CancellationToken, ValueTask<object>> factory,
HybridCacheEntryOptions? options,
IEnumerable<string>? tags,
CancellationToken token) =>
{
return factory(state!, token);
});
// Act
var exists = await HybridCacheExtensions.ExistsAsync(_cacheMock.Object, key);
// Assert
Assert.IsFalse(exists);
}
[Test]
public async Task TryGetValueAsync_WhenKeyExists_ShouldReturnTrueAndValueAsString()
{
// Arrange
string key = "test-key";
var expectedValue = "test-value";
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<string>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(expectedValue);
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<string>(_cacheMock.Object, key);
// Assert
Assert.IsTrue(exists);
Assert.AreEqual(expectedValue, value);
}
[Test]
public async Task TryGetValueAsync_WhenKeyExists_ShouldReturnTrueAndValueAsInteger()
{
// Arrange
string key = "test-key";
var expectedValue = 5;
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<int>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(expectedValue);
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<int>(_cacheMock.Object, key);
// Assert
Assert.IsTrue(exists);
Assert.AreEqual(expectedValue, value);
}
[Test]
public async Task TryGetValueAsync_WhenKeyExistsButValueIsNull_ShouldReturnTrueAndNullValue()
{
// Arrange
string key = "test-key";
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<object>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(null!);
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<int?>(_cacheMock.Object, key);
// Assert
Assert.IsTrue(exists);
Assert.IsNull(value);
}
[Test]
public async Task TryGetValueAsync_WhenKeyDoesNotExist_ShouldReturnFalseAndNull()
{
// Arrange
string key = "test-key";
_cacheMock.Setup(cache => cache.GetOrCreateAsync(
key,
null,
It.IsAny<Func<object?, CancellationToken, ValueTask<string>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.Returns((
string key,
object? state,
Func<object?, CancellationToken, ValueTask<string>> factory,
HybridCacheEntryOptions? options,
IEnumerable<string>? tags,
CancellationToken token) =>
{
return factory(state, token);
});
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<string>(_cacheMock.Object, key);
// Assert
Assert.IsFalse(exists);
Assert.IsNull(value);
}
}