Amend root content routing and ensure trailing slashes as configured (#18958)
* Amend root content routing and ensure trailing slashes as configured * Fix false positives at root + add more tests * Awaited async method and resolved warning around readonly. --------- Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -19,6 +20,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
private readonly IPublishedContentCache _contentCache;
|
||||
private readonly IDocumentNavigationQueryService _navigationQueryService;
|
||||
private readonly IPublishStatusQueryService _publishStatusQueryService;
|
||||
private readonly IDocumentUrlService _documentUrlService;
|
||||
private RequestHandlerSettings _requestSettings;
|
||||
|
||||
public ApiContentRouteBuilder(
|
||||
@@ -29,7 +31,8 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IPublishStatusQueryService publishStatusQueryService)
|
||||
IPublishStatusQueryService publishStatusQueryService,
|
||||
IDocumentUrlService documentUrlService)
|
||||
{
|
||||
_apiContentPathProvider = apiContentPathProvider;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
@@ -37,11 +40,35 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
_contentCache = contentCache;
|
||||
_navigationQueryService = navigationQueryService;
|
||||
_publishStatusQueryService = publishStatusQueryService;
|
||||
_documentUrlService = documentUrlService;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_requestSettings = requestSettings.CurrentValue;
|
||||
requestSettings.OnChange(settings => _requestSettings = settings);
|
||||
}
|
||||
|
||||
[Obsolete("Use the non-obsolete constructor, scheduled for removal in v17")]
|
||||
public ApiContentRouteBuilder(
|
||||
IApiContentPathProvider apiContentPathProvider,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IRequestPreviewService requestPreviewService,
|
||||
IOptionsMonitor<RequestHandlerSettings> requestSettings,
|
||||
IPublishedContentCache contentCache,
|
||||
IDocumentNavigationQueryService navigationQueryService,
|
||||
IPublishStatusQueryService publishStatusQueryService)
|
||||
: this(
|
||||
apiContentPathProvider,
|
||||
globalSettings,
|
||||
variationContextAccessor,
|
||||
requestPreviewService,
|
||||
requestSettings,
|
||||
contentCache,
|
||||
navigationQueryService,
|
||||
publishStatusQueryService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use the non-obsolete constructor, scheduled for removal in v17")]
|
||||
public ApiContentRouteBuilder(
|
||||
IApiContentPathProvider apiContentPathProvider,
|
||||
@@ -59,7 +86,8 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
requestSettings,
|
||||
contentCache,
|
||||
navigationQueryService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
|
||||
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IDocumentUrlService>())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -113,7 +141,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
// we can perform fallback to the content route.
|
||||
if (IsInvalidContentPath(contentPath))
|
||||
{
|
||||
contentPath = _contentCache.GetRouteById(content.Id, culture) ?? contentPath;
|
||||
contentPath = _documentUrlService.GetLegacyRouteFormat(content.Key, culture ?? _variationContextAccessor.VariationContext?.Culture, isPreview);
|
||||
}
|
||||
|
||||
// if the content path has still not been resolved as a valid path, the content is un-routable in this culture
|
||||
@@ -125,7 +153,9 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
|
||||
: null;
|
||||
}
|
||||
|
||||
return contentPath;
|
||||
return _requestSettings.AddTrailingSlash
|
||||
? contentPath?.EnsureEndsWith('/')
|
||||
: contentPath?.TrimEnd('/');
|
||||
}
|
||||
|
||||
private string ContentPreviewPath(IPublishedContent content) => $"{Constants.DeliveryApi.Routing.PreviewContentPathPrefix}{content.Key:D}{(_requestSettings.AddTrailingSlash ? "/" : string.Empty)}";
|
||||
|
||||
@@ -86,10 +86,35 @@ public sealed class ApiPublishedContentCache : IApiPublishedContentCache
|
||||
_variationContextAccessor.VariationContext?.Culture,
|
||||
_requestPreviewService.IsPreview());
|
||||
|
||||
// in multi-root settings, we've historically resolved all but the first root by their ID + URL segment,
|
||||
// e.g. "1234/second-root-url-segment". in V15+, IDocumentUrlService won't resolve this anymore; it will
|
||||
// however resolve "1234/" correctly, so to remain backwards compatible, we need to perform this extra step.
|
||||
var verifyUrlSegment = false;
|
||||
if (documentKey is null && route.TrimEnd('/').CountOccurrences("/") is 1)
|
||||
{
|
||||
documentKey = _apiDocumentUrlService.GetDocumentKeyByRoute(
|
||||
route[..(route.IndexOf('/') + 1)],
|
||||
_variationContextAccessor.VariationContext?.Culture,
|
||||
_requestPreviewService.IsPreview());
|
||||
verifyUrlSegment = true;
|
||||
}
|
||||
|
||||
IPublishedContent? content = documentKey.HasValue
|
||||
? _publishedContentCache.GetById(isPreviewMode, documentKey.Value)
|
||||
: null;
|
||||
|
||||
// the additional look-up above can result in false positives; if attempting to request a non-existing child to
|
||||
// the currently contextualized request root (either by start item or by domain), the root content key might
|
||||
// get resolved. to counter for this, we compare the requested URL segment with the resolved content URL segment.
|
||||
if (content is not null && verifyUrlSegment)
|
||||
{
|
||||
var expectedUrlSegment = route[(route.IndexOf('/') + 1)..];
|
||||
if (content.UrlSegment != expectedUrlSegment)
|
||||
{
|
||||
content = null;
|
||||
}
|
||||
}
|
||||
|
||||
return ContentOrNullIfDisallowed(content);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Integration.Attributes;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
public class ApiContentPathResolverInvariantTests : ApiContentPathResolverTestBase
|
||||
{
|
||||
private Dictionary<string, IContent> _contentByName = new ();
|
||||
|
||||
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
|
||||
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpTest()
|
||||
{
|
||||
UmbracoContextFactory.EnsureUmbracoContext();
|
||||
SetRequestHost("localhost");
|
||||
|
||||
if (_contentByName.Any())
|
||||
{
|
||||
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
|
||||
// predictable state with each test run.
|
||||
RefreshContentCache();
|
||||
return;
|
||||
}
|
||||
|
||||
await DocumentUrlService.InitAsync(true, CancellationToken.None);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithAlias("theContentType")
|
||||
.Build();
|
||||
contentType.AllowedAsRoot = true;
|
||||
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
|
||||
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
foreach (var rootNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var root = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithName($"Root {rootNumber}")
|
||||
.Build();
|
||||
ContentService.Save(root);
|
||||
ContentService.Publish(root, ["*"]);
|
||||
_contentByName[root.Name!] = root;
|
||||
|
||||
foreach (var childNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var child = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(root)
|
||||
.WithName($"Child {childNumber}")
|
||||
.Build();
|
||||
ContentService.Save(child);
|
||||
ContentService.Publish(child, ["*"]);
|
||||
_contentByName[$"{root.Name!}/{child.Name!}"] = child;
|
||||
|
||||
foreach (var grandchildNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var grandchild = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(child)
|
||||
.WithName($"Grandchild {grandchildNumber}")
|
||||
.Build();
|
||||
ContentService.Save(grandchild);
|
||||
ContentService.Publish(grandchild, ["*"]);
|
||||
_contentByName[$"{root.Name!}/{child.Name!}/{grandchild.Name!}"] = grandchild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void First_Root_Without_StartItem()
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void First_Root_Without_StartItem_With_Top_Level_Node_Included()
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void First_Root_Child_Without_StartItem(int child)
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root 1/Child {child}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1)]
|
||||
[TestCase(2, 2)]
|
||||
[TestCase(3, 3)]
|
||||
[TestCase(1, 2)]
|
||||
[TestCase(2, 3)]
|
||||
[TestCase(3, 1)]
|
||||
public void First_Root_Grandchild_Without_StartItem(int child, int grandchild)
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}/grandchild-{grandchild}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root 1/Child {child}/Grandchild {grandchild}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void Root_With_StartItem(int root)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void Root_With_StartItem_With_Top_Level_Node_Included(int root)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1)]
|
||||
[TestCase(2, 2)]
|
||||
[TestCase(3, 3)]
|
||||
[TestCase(1, 2)]
|
||||
[TestCase(2, 3)]
|
||||
[TestCase(3, 1)]
|
||||
public void Child_With_StartItem(int root, int child)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1)]
|
||||
[TestCase(2, 2)]
|
||||
[TestCase(3, 3)]
|
||||
[TestCase(1, 2)]
|
||||
[TestCase(2, 3)]
|
||||
[TestCase(3, 1)]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void Child_With_StartItem_With_Top_Level_Node_Included(int root, int child)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1, 1)]
|
||||
[TestCase(2, 2, 2)]
|
||||
[TestCase(3, 3, 3)]
|
||||
[TestCase(1, 2, 3)]
|
||||
[TestCase(2, 3, 1)]
|
||||
[TestCase(3, 1, 2)]
|
||||
public void Grandchild_With_StartItem(int root, int child, int grandchild)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}/grandchild-{grandchild}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}/Grandchild {grandchild}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("/", 1)]
|
||||
[TestCase("/root-2", 2)]
|
||||
[TestCase("/root-3", 3)]
|
||||
public void Root_By_Path_With_StartItem(string path, int root)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("/", 1)]
|
||||
[TestCase("/root-2", 2)]
|
||||
[TestCase("/root-3", 3)]
|
||||
public void Root_By_Path_Without_StartItem(string path, int root)
|
||||
{
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public async Task Root_With_Domain_Bindings(int root)
|
||||
{
|
||||
await SetContentHost(_contentByName[$"Root {root}"], "some.host", "en-US");
|
||||
SetRequestHost("some.host");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("/a", 1)]
|
||||
[TestCase("/123", 2)]
|
||||
[TestCase("/no-such-child", 3)]
|
||||
[TestCase("/a/b", 1)]
|
||||
[TestCase("/123/456", 2)]
|
||||
[TestCase("/no-such-child/no-such-grandchild", 3)]
|
||||
public void Non_Existant_Descendant_By_Path_With_StartItem(string path, int root)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNull(content);
|
||||
}
|
||||
|
||||
[TestCase("/a")]
|
||||
[TestCase("/123")]
|
||||
[TestCase("/a/b")]
|
||||
[TestCase("/123/456")]
|
||||
public void Non_Existant_Descendant_By_Path_Without_StartItem(string path)
|
||||
{
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNull(content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
|
||||
public abstract class ApiContentPathResolverTestBase : ApiContentRequestTestBase
|
||||
{
|
||||
protected IApiContentPathResolver ApiContentPathResolver => GetRequiredService<IApiContentPathResolver>();
|
||||
|
||||
protected IDocumentUrlService DocumentUrlService => GetRequiredService<IDocumentUrlService>();
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Integration.Attributes;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
public class ApiContentPathResolverVariantTests : ApiContentPathResolverTestBase
|
||||
{
|
||||
private readonly Dictionary<string, IContent> _contentByName = new ();
|
||||
|
||||
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
|
||||
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpTest()
|
||||
{
|
||||
UmbracoContextFactory.EnsureUmbracoContext();
|
||||
SetRequestHost("localhost");
|
||||
|
||||
if (_contentByName.Any())
|
||||
{
|
||||
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
|
||||
// predictable state with each test run.
|
||||
RefreshContentCache();
|
||||
return;
|
||||
}
|
||||
|
||||
await DocumentUrlService.InitAsync(true, CancellationToken.None);
|
||||
|
||||
await GetRequiredService<ILanguageService>().CreateAsync(new Language("da-DK", "Danish"), Constants.Security.SuperUserKey);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithAlias("theContentType")
|
||||
.WithContentVariation(ContentVariation.Culture)
|
||||
.Build();
|
||||
contentType.AllowedAsRoot = true;
|
||||
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
|
||||
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
foreach (var rootNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var root = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName("en-US", $"Root {rootNumber} en-US")
|
||||
.WithCultureName("da-DK", $"Root {rootNumber} da-DK")
|
||||
.Build();
|
||||
ContentService.Save(root);
|
||||
ContentService.Publish(root, ["*"]);
|
||||
_contentByName[$"Root {rootNumber}"] = root;
|
||||
|
||||
foreach (var childNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var child = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(root)
|
||||
.WithCultureName("en-US", $"Child {childNumber} en-US")
|
||||
.WithCultureName("da-DK", $"Child {childNumber} da-DK")
|
||||
.Build();
|
||||
ContentService.Save(child);
|
||||
ContentService.Publish(child, ["*"]);
|
||||
_contentByName[$"Root {rootNumber}/Child {childNumber}"] = child;
|
||||
|
||||
foreach (var grandchildNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var grandchild = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(child)
|
||||
.WithCultureName("en-US", $"Grandchild {grandchildNumber} en-US")
|
||||
.WithCultureName("da-DK", $"Grandchild {grandchildNumber} da-DK")
|
||||
.Build();
|
||||
ContentService.Save(grandchild);
|
||||
ContentService.Publish(grandchild, ["*"]);
|
||||
_contentByName[$"Root {rootNumber}/Child {childNumber}/Grandchild {grandchildNumber}"] = grandchild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("en-US")]
|
||||
[TestCase("da-DK")]
|
||||
public void First_Root_Without_StartItem(string culture)
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("en-US")]
|
||||
[TestCase("da-DK")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void First_Root_Without_StartItem_With_Top_Level_Node_Included(string culture)
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public void First_Root_Child_Without_StartItem(int child, string culture)
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root 1/Child {child}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1, "en-US")]
|
||||
[TestCase(2, 2, "en-US")]
|
||||
[TestCase(3, 3, "en-US")]
|
||||
[TestCase(1, 2, "en-US")]
|
||||
[TestCase(2, 3, "en-US")]
|
||||
[TestCase(3, 1, "en-US")]
|
||||
[TestCase(1, 1, "da-DK")]
|
||||
[TestCase(2, 2, "da-DK")]
|
||||
[TestCase(3, 3, "da-DK")]
|
||||
[TestCase(1, 2, "da-DK")]
|
||||
[TestCase(2, 3, "da-DK")]
|
||||
[TestCase(3, 1, "da-DK")]
|
||||
public void First_Root_Grandchild_Without_StartItem(int child, int grandchild, string culture)
|
||||
{
|
||||
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}/grandchild-{grandchild}-{culture.ToLowerInvariant()}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root 1/Child {child}/Grandchild {grandchild}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public void Root_With_StartItem(int root, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "da-DK")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void Root_With_StartItem_With_Top_Level_Node_Included(int root, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1, "en-US")]
|
||||
[TestCase(2, 2, "en-US")]
|
||||
[TestCase(3, 3, "en-US")]
|
||||
[TestCase(1, 2, "en-US")]
|
||||
[TestCase(2, 3, "en-US")]
|
||||
[TestCase(3, 1, "en-US")]
|
||||
[TestCase(1, 1, "da-DK")]
|
||||
[TestCase(2, 2, "da-DK")]
|
||||
[TestCase(3, 3, "da-DK")]
|
||||
[TestCase(1, 2, "da-DK")]
|
||||
[TestCase(2, 3, "da-DK")]
|
||||
[TestCase(3, 1, "da-DK")]
|
||||
public void Child_With_StartItem(int root, int child, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1, "en-US")]
|
||||
[TestCase(2, 2, "en-US")]
|
||||
[TestCase(3, 3, "en-US")]
|
||||
[TestCase(1, 2, "en-US")]
|
||||
[TestCase(2, 3, "en-US")]
|
||||
[TestCase(3, 1, "en-US")]
|
||||
[TestCase(1, 1, "da-DK")]
|
||||
[TestCase(2, 2, "da-DK")]
|
||||
[TestCase(3, 3, "da-DK")]
|
||||
[TestCase(1, 2, "da-DK")]
|
||||
[TestCase(2, 3, "da-DK")]
|
||||
[TestCase(3, 1, "da-DK")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void Child_With_StartItem_With_Top_Level_Node_Included(int root, int child, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, 1, 1, "en-US")]
|
||||
[TestCase(2, 2, 2, "en-US")]
|
||||
[TestCase(3, 3, 3, "en-US")]
|
||||
[TestCase(1, 2, 3, "en-US")]
|
||||
[TestCase(2, 3, 1, "en-US")]
|
||||
[TestCase(3, 1, 2, "en-US")]
|
||||
[TestCase(1, 1, 1, "da-DK")]
|
||||
[TestCase(2, 2, 2, "da-DK")]
|
||||
[TestCase(3, 3, 3, "da-DK")]
|
||||
[TestCase(1, 2, 3, "da-DK")]
|
||||
[TestCase(2, 3, 1, "da-DK")]
|
||||
[TestCase(3, 1, 2, "da-DK")]
|
||||
public void Grandchild_With_StartItem(int root, int child, int grandchild, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}/grandchild-{grandchild}-{culture.ToLowerInvariant()}");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}/Grandchild {grandchild}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("/", 1, "en-US")]
|
||||
[TestCase("/root-2-en-us", 2, "en-US")]
|
||||
[TestCase("/root-3-en-us", 3, "en-US")]
|
||||
[TestCase("/", 1, "da-DK")]
|
||||
[TestCase("/root-2-da-dk", 2, "da-DK")]
|
||||
[TestCase("/root-3-da-dk", 3, "da-DK")]
|
||||
public void Root_By_Path_With_StartItem(string path, int root, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("/", 1, "en-US")]
|
||||
[TestCase("/root-2-en-us", 2, "en-US")]
|
||||
[TestCase("/root-3-en-us", 3, "en-US")]
|
||||
[TestCase("/", 1, "da-DK")]
|
||||
[TestCase("/root-2-da-dk", 2, "da-DK")]
|
||||
[TestCase("/root-3-da-dk", 3, "da-DK")]
|
||||
public void Root_By_Path_Without_StartItem(string path, int root, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public async Task Root_With_Domain_Bindings(int root, string culture)
|
||||
{
|
||||
await SetContentHost(_contentByName[$"Root {root}"], "some.host", "en-US");
|
||||
SetRequestHost("some.host");
|
||||
SetVariationContext(culture);
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath("/");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
|
||||
}
|
||||
|
||||
[TestCase("/a", 1, "en-US")]
|
||||
[TestCase("/b", 1, "da-DK")]
|
||||
[TestCase("/123", 2, "en-US")]
|
||||
[TestCase("/456", 2, "da-DK")]
|
||||
[TestCase("/no-such-child", 3, "en-US")]
|
||||
[TestCase("/not-at-all", 3, "da-DK")]
|
||||
[TestCase("/a/b", 1, "en-US")]
|
||||
[TestCase("/c/d", 1, "da-DK")]
|
||||
[TestCase("/123/456", 2, "en-US")]
|
||||
[TestCase("/789/012", 2, "da-DK")]
|
||||
[TestCase("/no-such-child/no-such-grandchild", 3, "en-US")]
|
||||
[TestCase("/not-at-all/aint-no-way", 3, "da-DK")]
|
||||
public void Non_Existant_Descendant_By_Path_With_StartItem(string path, int root, string culture)
|
||||
{
|
||||
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
|
||||
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNull(content);
|
||||
}
|
||||
|
||||
[TestCase("/a")]
|
||||
[TestCase("/123")]
|
||||
[TestCase("/a/b")]
|
||||
[TestCase("/123/456")]
|
||||
public void Non_Existant_Descendant_By_Path_Without_StartItem(string path)
|
||||
{
|
||||
var content = ApiContentPathResolver.ResolveContentPath(path);
|
||||
Assert.IsNull(content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
public abstract class ApiContentRequestTestBase : UmbracoIntegrationTest
|
||||
{
|
||||
protected IContentService ContentService => GetRequiredService<IContentService>();
|
||||
|
||||
protected IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
|
||||
|
||||
protected IApiContentRouteBuilder ApiContentRouteBuilder => GetRequiredService<IApiContentRouteBuilder>();
|
||||
|
||||
protected IVariationContextAccessor VariationContextAccessor => GetRequiredService<IVariationContextAccessor>();
|
||||
|
||||
protected IUmbracoContextAccessor UmbracoContextAccessor => GetRequiredService<IUmbracoContextAccessor>();
|
||||
|
||||
protected IUmbracoContextFactory UmbracoContextFactory => GetRequiredService<IUmbracoContextFactory>();
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.AddUmbracoHybridCache();
|
||||
builder.AddNotificationHandler<ContentTreeChangeNotification, ContentTreeChangeDistributedCacheNotificationHandler>();
|
||||
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
|
||||
|
||||
builder.AddDeliveryApi();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task CleanUpAfterTest()
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
foreach (var content in ContentService.GetRootContent())
|
||||
{
|
||||
await domainService.UpdateDomainsAsync(content.Key, new DomainsUpdateModel { Domains = [] });
|
||||
}
|
||||
|
||||
var httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
|
||||
httpContextAccessor.HttpContext?.Request.Headers.Clear();
|
||||
}
|
||||
|
||||
protected void SetVariationContext(string? culture)
|
||||
=> VariationContextAccessor.VariationContext = new VariationContext(culture: culture);
|
||||
|
||||
protected async Task SetContentHost(IContent content, string host, string culture)
|
||||
=> await GetRequiredService<IDomainService>().UpdateDomainsAsync(
|
||||
content.Key,
|
||||
new DomainsUpdateModel { Domains = [new DomainModel { DomainName = host, IsoCode = culture }] });
|
||||
|
||||
protected void SetRequestHost(string host)
|
||||
{
|
||||
var httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
|
||||
|
||||
httpContextAccessor.HttpContext = new DefaultHttpContext
|
||||
{
|
||||
Request =
|
||||
{
|
||||
Scheme = "https",
|
||||
Host = new HostString(host),
|
||||
Path = "/",
|
||||
QueryString = new QueryString(string.Empty)
|
||||
},
|
||||
RequestServices = Services
|
||||
};
|
||||
}
|
||||
|
||||
protected void SetRequestStartItem(string startItem)
|
||||
{
|
||||
var httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
|
||||
if (httpContextAccessor.HttpContext is null)
|
||||
{
|
||||
throw new InvalidOperationException("HTTP context is null");
|
||||
}
|
||||
|
||||
httpContextAccessor.HttpContext.Request.Headers["Start-Item"] = startItem;
|
||||
}
|
||||
|
||||
protected void RefreshContentCache()
|
||||
=> Services.GetRequiredService<ContentCacheRefresher>().Refresh([new ContentCacheRefresher.JsonPayload { ChangeTypes = TreeChangeTypes.RefreshAll }]);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Integration.Attributes;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
public class ApiContentRouteBuilderInvariantTests : ApiContentRouteBuilderTestBase
|
||||
{
|
||||
private readonly Dictionary<string, IContent> _contentByName = new ();
|
||||
|
||||
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
|
||||
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
|
||||
|
||||
public static void ConfigureOmitTrailingSlash(IUmbracoBuilder builder)
|
||||
=> builder.Services.Configure<RequestHandlerSettings>(config => config.AddTrailingSlash = false);
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpTest()
|
||||
{
|
||||
SetRequestHost("localhost");
|
||||
|
||||
if (_contentByName.Any())
|
||||
{
|
||||
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
|
||||
// predictable state with each test run.
|
||||
RefreshContentCache();
|
||||
return;
|
||||
}
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithAlias("theContentType")
|
||||
.Build();
|
||||
contentType.AllowedAsRoot = true;
|
||||
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
|
||||
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
foreach (var rootNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var root = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithName($"Root {rootNumber}")
|
||||
.Build();
|
||||
ContentService.Save(root);
|
||||
ContentService.Publish(root, ["*"]);
|
||||
_contentByName[root.Name!] = root;
|
||||
|
||||
foreach (var childNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var child = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(root)
|
||||
.WithName($"Child {childNumber}")
|
||||
.Build();
|
||||
ContentService.Save(child);
|
||||
ContentService.Publish(child, ["*"]);
|
||||
_contentByName[$"{root.Name!}/{child.Name!}"] = child;
|
||||
|
||||
foreach (var grandchildNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var grandchild = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(child)
|
||||
.WithName($"Grandchild {grandchildNumber}")
|
||||
.Build();
|
||||
ContentService.Save(grandchild);
|
||||
ContentService.Publish(grandchild, ["*"]);
|
||||
_contentByName[$"{root.Name!}/{child.Name!}/{grandchild.Name!}"] = grandchild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void First_Root()
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName["Root 1"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/", route.Path);
|
||||
Assert.AreEqual("root-1", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName["Root 1"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Last_Root()
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName["Root 3"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/root-3/", route.Path);
|
||||
Assert.AreEqual("root-3", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName["Root 3"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void First_Child(int root)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/child-1/", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void Last_Child(int root)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/child-3/", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void First_Grandchild(int root)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1/Grandchild 1"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/child-1/grandchild-1/", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void Last_Grandchild(int root)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3/Grandchild 3"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/child-3/grandchild-3/", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void Root_With_Top_Level_Node_Included(int root)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public async Task Root_With_Domain_Bindings(int root)
|
||||
{
|
||||
await SetContentHost(_contentByName[$"Root {root}"], "some.host", "en-US");
|
||||
SetRequestHost("some.host");
|
||||
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "/")]
|
||||
[TestCase(2, "/root-2")]
|
||||
[TestCase(3, "/root-3")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
|
||||
public void Root_Without_Trailing_Slash(int root, string expectedPath)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(expectedPath, route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, 1)]
|
||||
[TestCase(2, 2)]
|
||||
[TestCase(3, 3)]
|
||||
[TestCase(1, 2)]
|
||||
[TestCase(2, 3)]
|
||||
[TestCase(3, 1)]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
|
||||
public void Child_Without_Trailing_Slash(int root, int child)
|
||||
{
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child {child}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/child-{child}", route.Path);
|
||||
Assert.AreEqual($"root-{root}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
|
||||
public abstract class ApiContentRouteBuilderTestBase : ApiContentRequestTestBase
|
||||
{
|
||||
protected IPublishedContent GetPublishedContent(Guid key)
|
||||
{
|
||||
UmbracoContextAccessor.Clear();
|
||||
var umbracoContext = UmbracoContextFactory.EnsureUmbracoContext().UmbracoContext;
|
||||
var publishedContent = umbracoContext.Content?.GetById(key);
|
||||
Assert.IsNotNull(publishedContent);
|
||||
|
||||
return publishedContent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Integration.Attributes;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
|
||||
|
||||
public class ApiContentRouteBuilderVariantTests : ApiContentRouteBuilderTestBase
|
||||
{
|
||||
private readonly Dictionary<string, IContent> _contentByName = new ();
|
||||
|
||||
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
|
||||
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
|
||||
|
||||
public static void ConfigureOmitTrailingSlash(IUmbracoBuilder builder)
|
||||
=> builder.Services.Configure<RequestHandlerSettings>(config => config.AddTrailingSlash = false);
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpTest()
|
||||
{
|
||||
SetRequestHost("localhost");
|
||||
|
||||
if (_contentByName.Any())
|
||||
{
|
||||
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
|
||||
// predictable state with each test run.
|
||||
RefreshContentCache();
|
||||
return;
|
||||
}
|
||||
|
||||
await GetRequiredService<ILanguageService>().CreateAsync(new Language("da-DK", "Danish"), Constants.Security.SuperUserKey);
|
||||
|
||||
var contentType = new ContentTypeBuilder()
|
||||
.WithAlias("theContentType")
|
||||
.WithContentVariation(ContentVariation.Culture)
|
||||
.Build();
|
||||
contentType.AllowedAsRoot = true;
|
||||
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
|
||||
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
|
||||
foreach (var rootNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var root = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName("en-US", $"Root {rootNumber} en-US")
|
||||
.WithCultureName("da-DK", $"Root {rootNumber} da-DK")
|
||||
.Build();
|
||||
ContentService.Save(root);
|
||||
ContentService.Publish(root, ["*"]);
|
||||
_contentByName[$"Root {rootNumber}"] = root;
|
||||
|
||||
foreach (var childNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var child = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(root)
|
||||
.WithCultureName("en-US", $"Child {childNumber} en-US")
|
||||
.WithCultureName("da-DK", $"Child {childNumber} da-DK")
|
||||
.Build();
|
||||
ContentService.Save(child);
|
||||
ContentService.Publish(child, ["*"]);
|
||||
_contentByName[$"Root {rootNumber}/Child {childNumber}"] = child;
|
||||
|
||||
foreach (var grandchildNumber in Enumerable.Range(1, 3))
|
||||
{
|
||||
var grandchild = new ContentBuilder()
|
||||
.WithContentType(contentType)
|
||||
.WithParent(child)
|
||||
.WithCultureName("en-US", $"Grandchild {grandchildNumber} en-US")
|
||||
.WithCultureName("da-DK", $"Grandchild {grandchildNumber} da-DK")
|
||||
.Build();
|
||||
ContentService.Save(grandchild);
|
||||
ContentService.Publish(grandchild, ["*"]);
|
||||
_contentByName[$"Root {rootNumber}/Child {childNumber}/Grandchild {grandchildNumber}"] = grandchild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("da-DK")]
|
||||
[TestCase("en-US")]
|
||||
public void First_Root(string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root 1"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/", route.Path);
|
||||
Assert.AreEqual($"root-1-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root 1"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase("da-DK")]
|
||||
[TestCase("en-US")]
|
||||
public void Last_Root(string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName["Root 3"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/root-3-{culture.ToLowerInvariant()}/", route.Path);
|
||||
Assert.AreEqual($"root-3-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName["Root 3"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public void First_Child(int root, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/child-1-{culture.ToLowerInvariant()}/", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public void Last_Child(int root, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/child-3-{culture.ToLowerInvariant()}/", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public void First_Grandchild(int root, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1/Grandchild 1"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/child-1-{culture.ToLowerInvariant()}/grandchild-1-{culture.ToLowerInvariant()}/", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public void Last_Grandchild(int root, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3/Grandchild 3"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/child-3-{culture.ToLowerInvariant()}/grandchild-3-{culture.ToLowerInvariant()}/", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(3, "da-DK")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
|
||||
public void Root_With_Top_Level_Node_Included(int root, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US")]
|
||||
[TestCase(1, "da-DK")]
|
||||
[TestCase(2, "en-US")]
|
||||
[TestCase(2, "da-DK")]
|
||||
[TestCase(3, "en-US")]
|
||||
[TestCase(3, "da-DK")]
|
||||
public async Task Root_With_Domain_Bindings(int root, string culture)
|
||||
{
|
||||
await SetContentHost(_contentByName[$"Root {root}"], "some.host", culture);
|
||||
SetRequestHost("some.host");
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("/", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, "en-US", "/")]
|
||||
[TestCase(1, "da-DK", "/")]
|
||||
[TestCase(2, "en-US", "/root-2-en-us")]
|
||||
[TestCase(2, "da-DK", "/root-2-da-dk")]
|
||||
[TestCase(3, "en-US", "/root-3-en-us")]
|
||||
[TestCase(3, "da-DK", "/root-3-da-dk")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
|
||||
public void Root_Without_Trailing_Slash(int root, string culture, string expectedPath)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(expectedPath, route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1, 1, "en-US")]
|
||||
[TestCase(1, 1, "da-DK")]
|
||||
[TestCase(2, 2, "en-US")]
|
||||
[TestCase(2, 2, "da-DK")]
|
||||
[TestCase(3, 3, "en-US")]
|
||||
[TestCase(3, 3, "da-DK")]
|
||||
[TestCase(1, 2, "en-US")]
|
||||
[TestCase(1, 2, "da-DK")]
|
||||
[TestCase(2, 3, "en-US")]
|
||||
[TestCase(2, 3, "da-DK")]
|
||||
[TestCase(3, 1, "en-US")]
|
||||
[TestCase(3, 1, "da-DK")]
|
||||
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
|
||||
public void Child_Without_Trailing_Slash(int root, int child, string culture)
|
||||
{
|
||||
SetVariationContext(culture);
|
||||
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child {child}"].Key);
|
||||
var route = ApiContentRouteBuilder.Build(publishedContent);
|
||||
Assert.IsNotNull(route);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual($"/child-{child}-{culture.ToLowerInvariant()}", route.Path);
|
||||
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
|
||||
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class ContentBuilderTests : DeliveryApiTests
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual("The page", result.Name);
|
||||
Assert.AreEqual("thePageType", result.ContentType);
|
||||
Assert.AreEqual("/url:url-segment", result.Route.Path);
|
||||
Assert.AreEqual("/url:url-segment/", result.Route.Path);
|
||||
Assert.AreEqual(key, result.Id);
|
||||
Assert.AreEqual(2, result.Properties.Count);
|
||||
Assert.AreEqual("Delivery API value", result.Properties["deliveryApi"]);
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ContentPickerValueConverterTests : PropertyValueConverterTests
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual("The page", result.Name);
|
||||
Assert.AreEqual(PublishedContent.Key, result.Id);
|
||||
Assert.AreEqual("/the-page-url", result.Route.Path);
|
||||
Assert.AreEqual("/the-page-url/", result.Route.Path);
|
||||
Assert.AreEqual("TheContentType", result.ContentType);
|
||||
Assert.IsEmpty(result.Properties);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public class ContentPickerValueConverterTests : PropertyValueConverterTests
|
||||
Assert.NotNull(result);
|
||||
Assert.AreEqual("The page", result.Name);
|
||||
Assert.AreEqual(content.Object.Key, result.Id);
|
||||
Assert.AreEqual("/page-url-segment", result.Route.Path);
|
||||
Assert.AreEqual("/page-url-segment/", result.Route.Path);
|
||||
Assert.AreEqual("TheContentType", result.ContentType);
|
||||
Assert.AreEqual(2, result.Properties.Count);
|
||||
Assert.AreEqual("Delivery API value", result.Properties[DeliveryApiPropertyType.Alias]);
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
IEnumerable<Guid> ancestorsKeys = [rootKey];
|
||||
navigationQueryServiceMock.Setup(x => x.TryGetAncestorsKeys(childKey, out ancestorsKeys)).Returns(true);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var grandchildKey = Guid.NewGuid();
|
||||
var grandchild = SetupInvariantPublishedContent("The Grandchild", grandchildKey, navigationQueryServiceMock, child);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), grandchild.Key)).Returns(grandchild);
|
||||
@@ -104,7 +104,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var childKey = Guid.NewGuid();
|
||||
var child = SetupVariantPublishedContent("The Child", childKey, navigationQueryServiceMock, root);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -136,7 +136,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var childKey = Guid.NewGuid();
|
||||
var child = SetupInvariantPublishedContent("The Child", childKey, navigationQueryServiceMock, root);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -168,7 +168,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var childKey = Guid.NewGuid();
|
||||
var child = SetupVariantPublishedContent("The Child", childKey, navigationQueryServiceMock, root);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -209,7 +209,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
{
|
||||
var result = GetUnRoutableRoute(resolvedUrl, "/the/content/route");
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual("/the/content/route", result.Path);
|
||||
Assert.AreEqual("/the/content/route/", result.Path);
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
@@ -266,7 +266,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var childKey = Guid.NewGuid();
|
||||
var child = SetupInvariantPublishedContent("The Child", childKey, navigationQueryServiceMock, root, false);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -293,7 +293,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var childKey = Guid.NewGuid();
|
||||
var child = SetupInvariantPublishedContent("The Child", childKey, navigationQueryServiceMock, root, false);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -321,7 +321,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var requestPreviewServiceMock = new Mock<IRequestPreviewService>();
|
||||
requestPreviewServiceMock.Setup(m => m.IsPreview()).Returns(isPreview);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -355,7 +355,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var childKey = Guid.NewGuid();
|
||||
var child = SetupInvariantPublishedContent("The Child", childKey, navigationQueryServiceMock, root);
|
||||
|
||||
var contentCache = CreatePublishedContentCache("#");
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), root.Key)).Returns(root);
|
||||
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<bool>(), child.Key)).Returns(child);
|
||||
|
||||
@@ -460,7 +460,7 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
var requestPreviewServiceMock = new Mock<IRequestPreviewService>();
|
||||
requestPreviewServiceMock.Setup(m => m.IsPreview()).Returns(isPreview);
|
||||
|
||||
contentCache ??= CreatePublishedContentCache("#");
|
||||
contentCache ??= CreatePublishedContentCache();
|
||||
apiContentPathProvider ??= SetupApiContentPathProvider(hideTopLevelNodeFromPath, contentCache, navigationQueryService);
|
||||
|
||||
return CreateContentRouteBuilder(
|
||||
@@ -480,25 +480,24 @@ public class ContentRouteBuilderTests : DeliveryApiTests
|
||||
.Returns(publishedUrl);
|
||||
var contentPathProvider = new ApiContentPathProvider(publishedUrlProviderMock.Object);
|
||||
|
||||
var contentCache = CreatePublishedContentCache(routeById);
|
||||
var contentCache = CreatePublishedContentCache();
|
||||
var navigationQueryServiceMock = new Mock<IDocumentNavigationQueryService>();
|
||||
var content = SetupVariantPublishedContent("The Content", Guid.NewGuid(), navigationQueryServiceMock);
|
||||
|
||||
var documentUrlServiceMock = new Mock<IDocumentUrlService>();
|
||||
documentUrlServiceMock
|
||||
.Setup(m => m.GetLegacyRouteFormat(It.IsAny<Guid>(), It.IsAny<string?>(), It.IsAny<bool>()))
|
||||
.Returns(routeById);
|
||||
|
||||
var builder = CreateContentRouteBuilder(
|
||||
contentPathProvider,
|
||||
CreateGlobalSettings(),
|
||||
contentCache: contentCache);
|
||||
contentCache: contentCache,
|
||||
documentUrlService: documentUrlServiceMock.Object);
|
||||
|
||||
return builder.Build(content);
|
||||
}
|
||||
|
||||
private IPublishedContentCache CreatePublishedContentCache(string routeById)
|
||||
{
|
||||
var publishedContentCacheMock = new Mock<IPublishedContentCache>();
|
||||
publishedContentCacheMock
|
||||
.Setup(c => c.GetRouteById(It.IsAny<int>(), It.IsAny<string?>()))
|
||||
.Returns(routeById);
|
||||
|
||||
return publishedContentCacheMock.Object;
|
||||
}
|
||||
private IPublishedContentCache CreatePublishedContentCache()
|
||||
=> Mock.Of<IPublishedContentCache>();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.DeliveryApi;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -128,7 +129,8 @@ public class DeliveryApiTests
|
||||
IOptionsMonitor<RequestHandlerSettings>? requestHandlerSettingsMonitor = null,
|
||||
IPublishedContentCache? contentCache = null,
|
||||
IDocumentNavigationQueryService? navigationQueryService = null,
|
||||
IPublishStatusQueryService? publishStatusQueryService = null)
|
||||
IPublishStatusQueryService? publishStatusQueryService = null,
|
||||
IDocumentUrlService? documentUrlService = null)
|
||||
{
|
||||
if (requestHandlerSettingsMonitor == null)
|
||||
{
|
||||
@@ -145,6 +147,7 @@ public class DeliveryApiTests
|
||||
requestHandlerSettingsMonitor,
|
||||
contentCache ?? Mock.Of<IPublishedContentCache>(),
|
||||
navigationQueryService ?? Mock.Of<IDocumentNavigationQueryService>(),
|
||||
publishStatusQueryService ?? PublishStatusQueryService);
|
||||
publishStatusQueryService ?? PublishStatusQueryService,
|
||||
documentUrlService ?? Mock.Of<IDocumentUrlService>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual(PublishedContent.Name, result.First().Name);
|
||||
Assert.AreEqual(PublishedContent.Key, result.First().Id);
|
||||
Assert.AreEqual("/the-page-url", result.First().Route.Path);
|
||||
Assert.AreEqual("/the-page-url/", result.First().Route.Path);
|
||||
Assert.AreEqual("TheContentType", result.First().ContentType);
|
||||
Assert.IsEmpty(result.First().Properties);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest
|
||||
|
||||
Assert.AreEqual(PublishedContent.Name, result.First().Name);
|
||||
Assert.AreEqual(PublishedContent.Key, result.First().Id);
|
||||
Assert.AreEqual("/the-page-url", result.First().Route.Path);
|
||||
Assert.AreEqual("/the-page-url/", result.First().Route.Path);
|
||||
Assert.AreEqual("TheContentType", result.First().ContentType);
|
||||
|
||||
Assert.AreEqual("The other page", result.Last().Name);
|
||||
@@ -130,7 +130,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual("The page", result.First().Name);
|
||||
Assert.AreEqual(key, result.First().Id);
|
||||
Assert.AreEqual("/page-url-segment", result.First().Route.Path);
|
||||
Assert.AreEqual("/page-url-segment/", result.First().Route.Path);
|
||||
Assert.AreEqual("TheContentType", result.First().ContentType);
|
||||
Assert.AreEqual(2, result.First().Properties.Count);
|
||||
Assert.AreEqual("Delivery API value", result.First().Properties[DeliveryApiPropertyType.Alias]);
|
||||
@@ -204,7 +204,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest
|
||||
Assert.AreEqual(1, result.Count());
|
||||
Assert.AreEqual(PublishedContent.Name, result.First().Name);
|
||||
Assert.AreEqual(PublishedContent.Key, result.First().Id);
|
||||
Assert.AreEqual("/the-page-url", result.First().Route.Path);
|
||||
Assert.AreEqual("/the-page-url/", result.First().Route.Path);
|
||||
Assert.AreEqual("TheContentType", result.First().ContentType);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class MultiUrlPickerValueConverterTests : PropertyValueConverterTests
|
||||
Assert.Null(link.Target);
|
||||
var route = link.Route;
|
||||
Assert.NotNull(route);
|
||||
Assert.AreEqual("/the-page-url", route.Path);
|
||||
Assert.AreEqual("/the-page-url/", route.Path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -221,7 +221,7 @@ public class MultiUrlPickerValueConverterTests : PropertyValueConverterTests
|
||||
var link = result.First();
|
||||
Assert.AreEqual("Custom link name", link.Title);
|
||||
Assert.AreEqual(PublishedContent.Key, link.DestinationId);
|
||||
Assert.AreEqual("/the-page-url", link.Route!.Path);
|
||||
Assert.AreEqual("/the-page-url/", link.Route!.Path);
|
||||
Assert.AreEqual(LinkType.Content, link.LinkType);
|
||||
Assert.AreEqual("_blank", link.Target);
|
||||
Assert.AreEqual("?something=true", link.QueryString);
|
||||
@@ -252,7 +252,7 @@ public class MultiUrlPickerValueConverterTests : PropertyValueConverterTests
|
||||
var link = result.First();
|
||||
Assert.AreEqual(PublishedContent.Name, link.Title);
|
||||
Assert.AreEqual(PublishedContent.Key, link.DestinationId);
|
||||
Assert.AreEqual("/the-page-url", link.Route!.Path);
|
||||
Assert.AreEqual("/the-page-url/", link.Route!.Path);
|
||||
Assert.AreEqual(LinkType.Content, link.LinkType);
|
||||
Assert.Null(link.Target);
|
||||
Assert.Null(link.Url);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Cms.Core.DeliveryApi;
|
||||
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.Services.Navigation;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi;
|
||||
@@ -127,7 +128,8 @@ public class PropertyValueConverterTests : DeliveryApiTests
|
||||
IOptionsMonitor<RequestHandlerSettings>? requestHandlerSettingsMonitor = null,
|
||||
IPublishedContentCache? contentCache = null,
|
||||
IDocumentNavigationQueryService? navigationQueryService = null,
|
||||
IPublishStatusQueryService? publishStatusQueryService = null)
|
||||
IPublishStatusQueryService? publishStatusQueryService = null,
|
||||
IDocumentUrlService? documentUrlService = null)
|
||||
{
|
||||
contentCache ??= PublishedContentCacheMock.Object;
|
||||
navigationQueryService ??= DocumentNavigationQueryServiceMock.Object;
|
||||
@@ -140,6 +142,7 @@ public class PropertyValueConverterTests : DeliveryApiTests
|
||||
requestHandlerSettingsMonitor,
|
||||
contentCache,
|
||||
navigationQueryService,
|
||||
publishStatusQueryService);
|
||||
publishStatusQueryService,
|
||||
documentUrlService);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user