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

@@ -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,200 +0,0 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using NUnit.Framework;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositories;
[TestFixture]
internal sealed class SimilarNodeNameTests
{
public void Name_Is_Suffixed()
{
SimilarNodeName[] names = { new SimilarNodeName { Id = 1, Name = "Zulu" } };
var res = SimilarNodeName.GetUniqueName(names, 0, "Zulu");
Assert.AreEqual("Zulu (1)", res);
}
[Test]
public void Suffixed_Name_Is_Incremented()
{
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Zulu"}, new SimilarNodeName {Id = 2, Name = "Kilo (1)"},
new SimilarNodeName {Id = 3, Name = "Kilo"}
};
var res = SimilarNodeName.GetUniqueName(names, 0, "Kilo (1)");
Assert.AreEqual("Kilo (2)", res);
}
[Test]
public void Lower_Number_Suffix_Is_Inserted()
{
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Golf"}, new SimilarNodeName {Id = 2, Name = "Golf (2)"}
};
var res = SimilarNodeName.GetUniqueName(names, 0, "Golf");
Assert.AreEqual("Golf (1)", res);
}
[Test]
[TestCase(0, "Alpha", "Alpha (3)")]
[TestCase(0, "alpha", "alpha (3)")]
public void Case_Is_Ignored(int nodeId, string nodeName, string expected)
{
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Alpha"}, new SimilarNodeName {Id = 2, Name = "Alpha (1)"},
new SimilarNodeName {Id = 3, Name = "Alpha (2)"}
};
var res = SimilarNodeName.GetUniqueName(names, nodeId, nodeName);
Assert.AreEqual(expected, res);
}
[Test]
public void Empty_List_Causes_Unchanged_Name()
{
var names = new SimilarNodeName[] { };
var res = SimilarNodeName.GetUniqueName(names, 0, "Charlie");
Assert.AreEqual("Charlie", res);
}
[Test]
[TestCase(0, "", " (1)")]
[TestCase(0, null, " (1)")]
public void Empty_Name_Is_Suffixed(int nodeId, string nodeName, string expected)
{
var names = new SimilarNodeName[] { };
var res = SimilarNodeName.GetUniqueName(names, nodeId, nodeName);
Assert.AreEqual(expected, res);
}
[Test]
public void Matching_NoedId_Causes_No_Change()
{
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Kilo (1)"}, new SimilarNodeName {Id = 2, Name = "Yankee"},
new SimilarNodeName {Id = 3, Name = "Kilo"}
};
var res = SimilarNodeName.GetUniqueName(names, 1, "Kilo (1)");
Assert.AreEqual("Kilo (1)", res);
}
[Test]
public void Extra_MultiSuffixed_Name_Is_Ignored()
{
// Sequesnce is: Test, Test (1), Test (2)
// Ignore: Test (1) (1)
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 = 5, Name = "Test (1) (1)"}
};
var res = SimilarNodeName.GetUniqueName(names, 0, "Test");
Assert.AreEqual("Test (3)", res);
}
[Test]
public void Matched_Name_Is_Suffixed()
{
SimilarNodeName[] names = { new SimilarNodeName { Id = 1, Name = "Test" } };
var res = SimilarNodeName.GetUniqueName(names, 0, "Test");
Assert.AreEqual("Test (1)", res);
}
[Test]
public void MultiSuffixed_Name_Is_Icremented()
{
// "Test (1)" is treated as the "original" version of the name.
// "Test (1) (1)" is the suffixed result of a copy, and therefore is incremented
// Hence this test result should be the same as Suffixed_Name_Is_Incremented
SimilarNodeName[] names =
{
new SimilarNodeName {Id = 1, Name = "Test (1)"}, new SimilarNodeName {Id = 2, Name = "Test (1) (1)"}
};
var res = SimilarNodeName.GetUniqueName(names, 0, "Test (1) (1)");
Assert.AreEqual("Test (1) (2)", res);
}
[Test]
public void Suffixed_Name_Causes_Secondary_Suffix()
{
SimilarNodeName[] names = { new SimilarNodeName { Id = 6, Name = "Alpha (1)" } };
var res = SimilarNodeName.GetUniqueName(names, 0, "Alpha (1)");
Assert.AreEqual("Alpha (1) (1)", res);
}
[TestCase("Test (0)", "Test (0) (1)")]
[TestCase("Test (-1)", "Test (-1) (1)")]
[TestCase("Test (1) (-1)", "Test (1) (-1) (1)")]
public void NonPositive_Suffix_Is_Ignored(string suffix, string expected)
{
SimilarNodeName[] names = { new SimilarNodeName { Id = 6, Name = suffix } };
var res = SimilarNodeName.GetUniqueName(names, 0, suffix);
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()
{
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 = 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"));
}
}

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)