V15: Only cache items if all ancestors are published (#18337)

* Introduce IsDocumentPublishedInAnyCulture

Sometimes we don't care about culture

* Check ancestor path when resolving cache items

* Fix tests

* Rebuild NavigationService

* Only set node if it has a published ancestor path

* Remove branch when unpublished

* Add tests

* Add seed test

* Consider published ancestor path when seeding documents

* Introduce MediaBreadthFirstKeyProviderTests

This is needed since the logic of document and media is no longer the same

* Remove unused services

* Move assert page to helper

* Add variant tests

* Add tests

* Filter keys in ContentTypeSeedKeyProvider

* Fix tests

* Add failing test showing refreshing issue

* Don't blow up if we can't resolve the node from navigation cache

Turns out that this can actually happen :D Should be fine to just return false

* Refactor cache refresher check

* Make NavigationQueryService service protected

* Add comment on how to refactor breadth first key provider

* Refactor if statement
This commit is contained in:
Mole
2025-02-17 12:51:33 +01:00
committed by GitHub
parent 69e251ad17
commit c76d764598
15 changed files with 680 additions and 28 deletions

View File

@@ -6,10 +6,19 @@ 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
{
private IPublishStatusQueryService PublishStatusQueryService
{
get
{
var mock = new Mock<IPublishStatusQueryService>();
mock.Setup(x => x.IsDocumentPublishedInAnyCulture(It.IsAny<Guid>())).Returns(true);
return mock.Object;
}
}
[Test]
public void ZeroSeedCountReturnsZeroKeys()
{
@@ -22,7 +31,7 @@ public class DocumentBreadthFirstKeyProviderTests
navigationQueryService.Setup(x => x.TryGetChildrenKeys(It.IsAny<Guid>(), out rootChildren)).Returns(true);
var cacheSettings = new CacheSettings { DocumentBreadthFirstSeedCount = 0 };
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings), PublishStatusQueryService);
var result = sut.GetSeedKeys();
@@ -46,7 +55,7 @@ public class DocumentBreadthFirstKeyProviderTests
var expected = 3;
var cacheSettings = new CacheSettings { DocumentBreadthFirstSeedCount = expected };
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings), PublishStatusQueryService);
var result = sut.GetSeedKeys();
@@ -77,7 +86,7 @@ public class DocumentBreadthFirstKeyProviderTests
// This'll get all children but no grandchildren
var cacheSettings = new CacheSettings { DocumentBreadthFirstSeedCount = 4 };
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings), PublishStatusQueryService);
var result = sut.GetSeedKeys();
@@ -105,7 +114,7 @@ public class DocumentBreadthFirstKeyProviderTests
var settings = new CacheSettings { DocumentBreadthFirstSeedCount = int.MaxValue };
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(settings));
var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(settings), PublishStatusQueryService);
var result = sut.GetSeedKeys();

View File

@@ -0,0 +1,115 @@
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Infrastructure.HybridCache.SeedKeyProviders.Media;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.HybridCache;
[TestFixture]
public class MediaBreadthFirstKeyProviderTests
{
[Test]
public void ZeroSeedCountReturnsZeroKeys()
{
// The structure here doesn't matter greatly, it just matters that there is something.
var navigationQueryService = new Mock<IMediaNavigationQueryService>();
var rootKey = Guid.NewGuid();
IEnumerable<Guid> rootKeyList = new List<Guid> { rootKey };
IEnumerable<Guid> rootChildren = new List<Guid> { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
navigationQueryService.Setup(x => x.TryGetRootKeys(out rootKeyList)).Returns(true);
navigationQueryService.Setup(x => x.TryGetChildrenKeys(It.IsAny<Guid>(), out rootChildren)).Returns(true);
var cacheSettings = new CacheSettings { MediaBreadthFirstSeedCount = 0 };
var sut = new MediaBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));
var result = sut.GetSeedKeys();
Assert.Zero(result.Count);
}
[Test]
public void OnlyReturnsKeysUpToSeedCount()
{
// Structure
// Root
// - Child1
// - Child2
// - Child3
var navigationQueryService = new Mock<IMediaNavigationQueryService>();
var rootKey = Guid.NewGuid();
IEnumerable<Guid> rootKeyList = new List<Guid> { rootKey };
IEnumerable<Guid> rootChildren = new List<Guid> { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
navigationQueryService.Setup(x => x.TryGetRootKeys(out rootKeyList)).Returns(true);
navigationQueryService.Setup(x => x.TryGetChildrenKeys(rootKey, out rootChildren)).Returns(true);
var expected = 3;
var cacheSettings = new CacheSettings { MediaBreadthFirstSeedCount = expected };
var sut = new MediaBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));
var result = sut.GetSeedKeys();
Assert.That(result.Count, Is.EqualTo(expected));
}
[Test]
public void IsBreadthFirst()
{
// Structure
// Root
// - Child1
// - GrandChild
// - Child2
// - Child3
var navigationQueryService = new Mock<IMediaNavigationQueryService>();
var rootKey = Guid.NewGuid();
var child1Key = Guid.NewGuid();
var grandChildKey = Guid.NewGuid();
IEnumerable<Guid> rootKeyList = new List<Guid> { rootKey };
IEnumerable<Guid> rootChildren = new List<Guid> { child1Key, Guid.NewGuid(), Guid.NewGuid() };
IEnumerable<Guid> grandChildren = new List<Guid> { grandChildKey };
navigationQueryService.Setup(x => x.TryGetRootKeys(out rootKeyList)).Returns(true);
navigationQueryService.Setup(x => x.TryGetChildrenKeys(rootKey, out rootChildren)).Returns(true);
navigationQueryService.Setup(x => x.TryGetChildrenKeys(child1Key, out grandChildren)).Returns(true);
// This'll get all children but no grandchildren
var cacheSettings = new CacheSettings { MediaBreadthFirstSeedCount = 4 };
var sut = new MediaBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));
var result = sut.GetSeedKeys();
Assert.That(result.Contains(grandChildKey), Is.False);
}
[Test]
public void CanGetAll()
{
var navigationQueryService = new Mock<IMediaNavigationQueryService>();
var rootKey = Guid.NewGuid();
IEnumerable<Guid> rootKeyList = new List<Guid> { rootKey };
var childrenCount = 300;
List<Guid> rootChildren = new List<Guid>();
for (int i = 0; i < childrenCount; i++)
{
rootChildren.Add(Guid.NewGuid());
}
IEnumerable<Guid> childrenEnumerable = rootChildren;
navigationQueryService.Setup(x => x.TryGetRootKeys(out rootKeyList)).Returns(true);
navigationQueryService.Setup(x => x.TryGetChildrenKeys(rootKey, out childrenEnumerable)).Returns(true);
var settings = new CacheSettings { MediaBreadthFirstSeedCount = int.MaxValue };
var sut = new MediaBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(settings));
var result = sut.GetSeedKeys();
var expected = childrenCount + 1; // Root + children
Assert.That(result.Count, Is.EqualTo(expected));
}
}