Merge branch 'v16/dev' into v15/feature/select-segment
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
<PackageVersion Include="MailKit" Version="4.10.0" />
|
||||
<PackageVersion Include="Markdown" Version="2.2.1" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.192" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.3.8" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
|
||||
<PackageVersion Include="MiniProfiler.Shared" Version="4.5.4" />
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
<PackageVersion Include="NPoco" Version="5.7.1" />
|
||||
@@ -100,4 +100,4 @@
|
||||
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
[DeliveryApiAccess]
|
||||
[VersionedDeliveryApiRoute("content")]
|
||||
[ApiExplorerSettings(GroupName = "Content")]
|
||||
[LocalizeFromAcceptLanguageHeader]
|
||||
[ContextualizeFromAcceptHeaders]
|
||||
[ValidateStartItem]
|
||||
[AddVaryHeader]
|
||||
[OutputCache(PolicyName = Constants.DeliveryApi.OutputCache.ContentCachePolicy)]
|
||||
|
||||
@@ -49,6 +49,7 @@ public static class UmbracoBuilderExtensions
|
||||
: provider.GetRequiredService<RequestContextOutputExpansionStrategyV2>();
|
||||
});
|
||||
builder.Services.AddSingleton<IRequestCultureService, RequestCultureService>();
|
||||
builder.Services.AddSingleton<IRequestSegmmentService, RequestSegmentService>();
|
||||
builder.Services.AddSingleton<IRequestRoutingService, RequestRoutingService>();
|
||||
builder.Services.AddSingleton<IRequestRedirectService, RequestRedirectService>();
|
||||
builder.Services.AddSingleton<IRequestPreviewService, RequestPreviewService>();
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
internal sealed class ContextualizeFromAcceptHeadersAttribute : TypeFilterAttribute
|
||||
{
|
||||
public ContextualizeFromAcceptHeadersAttribute()
|
||||
: base(typeof(LocalizeFromAcceptLanguageHeaderAttributeFilter))
|
||||
{
|
||||
}
|
||||
|
||||
private class LocalizeFromAcceptLanguageHeaderAttributeFilter : IActionFilter
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
private readonly IRequestSegmmentService _requestSegmentService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public LocalizeFromAcceptLanguageHeaderAttributeFilter(
|
||||
IRequestCultureService requestCultureService,
|
||||
IRequestSegmmentService requestSegmentService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
{
|
||||
_requestCultureService = requestCultureService;
|
||||
_requestSegmentService = requestSegmentService;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var requestedCulture = _requestCultureService.GetRequestedCulture().NullOrWhiteSpaceAsNull();
|
||||
var requestedSegment = _requestSegmentService.GetRequestedSegment().NullOrWhiteSpaceAsNull();
|
||||
if (requestedCulture.IsNullOrWhiteSpace() && requestedSegment.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// contextualize the request
|
||||
// NOTE: request culture or segment can be null here (but not both), so make sure to retain any existing
|
||||
// context by means of fallback to current variation context (if available)
|
||||
_variationContextAccessor.VariationContext = new VariationContext(
|
||||
requestedCulture ?? _variationContextAccessor.VariationContext?.Culture,
|
||||
requestedSegment ?? _variationContextAccessor.VariationContext?.Segment);
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Filters;
|
||||
|
||||
internal sealed class LocalizeFromAcceptLanguageHeaderAttribute : TypeFilterAttribute
|
||||
{
|
||||
public LocalizeFromAcceptLanguageHeaderAttribute()
|
||||
: base(typeof(LocalizeFromAcceptLanguageHeaderAttributeFilter))
|
||||
{
|
||||
}
|
||||
|
||||
private class LocalizeFromAcceptLanguageHeaderAttributeFilter : IActionFilter
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
|
||||
public LocalizeFromAcceptLanguageHeaderAttributeFilter(IRequestCultureService requestCultureService)
|
||||
=> _requestCultureService = requestCultureService;
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var requestedCulture = _requestCultureService.GetRequestedCulture();
|
||||
if (requestedCulture.IsNullOrWhiteSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_requestCultureService.SetRequestCulture(requestedCulture);
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,20 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
|
||||
}
|
||||
});
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Name = "Accept-Segment",
|
||||
In = ParameterLocation.Header,
|
||||
Required = false,
|
||||
Description = "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Examples = new Dictionary<string, OpenApiExample>
|
||||
{
|
||||
{ "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
|
||||
{ "Segment One", new OpenApiExample { Value = new OpenApiString("segment-one") } }
|
||||
}
|
||||
});
|
||||
|
||||
AddApiKey(operation);
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed partial class RequestCultureService : RequestHeaderHandler, IRequestCultureService
|
||||
{
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public RequestCultureService(IHttpContextAccessor httpContextAccessor, IVariationContextAccessor variationContextAccessor)
|
||||
: base(httpContextAccessor) =>
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
public RequestCultureService(IHttpContextAccessor httpContextAccessor)
|
||||
: base(httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetRequestedCulture()
|
||||
@@ -21,15 +19,10 @@ internal sealed partial class RequestCultureService : RequestHeaderHandler, IReq
|
||||
return ValidLanguageHeaderRegex().IsMatch(acceptLanguage) ? acceptLanguage : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use IVariationContextAccessor to manipulate the variation context. Scheduled for removal in V17.")]
|
||||
public void SetRequestCulture(string culture)
|
||||
{
|
||||
if (_variationContextAccessor.VariationContext?.Culture == culture)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_variationContextAccessor.VariationContext = new VariationContext(culture);
|
||||
// no-op
|
||||
}
|
||||
|
||||
// at the time of writing we're introducing this to get rid of accept-language header values like "en-GB,en-US;q=0.9,en;q=0.8",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -11,14 +10,19 @@ namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutingService
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public RequestRoutingService(
|
||||
IDomainCache domainCache,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
|
||||
IRequestCultureService requestCultureService)
|
||||
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor) =>
|
||||
IRequestCultureService requestCultureService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: base(domainCache, httpContextAccessor, requestStartItemProviderAccessor)
|
||||
{
|
||||
_requestCultureService = requestCultureService;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetContentRoute(string requestedPath)
|
||||
@@ -50,9 +54,14 @@ internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutin
|
||||
}
|
||||
|
||||
// the Accept-Language header takes precedence over configured domain culture
|
||||
if (domainAndUri.Culture != null && _requestCultureService.GetRequestedCulture().IsNullOrWhiteSpace())
|
||||
if (domainAndUri.Culture != null
|
||||
&& _requestCultureService.GetRequestedCulture().IsNullOrWhiteSpace()
|
||||
&& _variationContextAccessor.VariationContext?.Culture != domainAndUri.Culture)
|
||||
{
|
||||
_requestCultureService.SetRequestCulture(domainAndUri.Culture);
|
||||
// update the variation context to match the requested domain culture while retaining any contextualized segment
|
||||
_variationContextAccessor.VariationContext = new VariationContext(
|
||||
culture: domainAndUri.Culture,
|
||||
segment: _variationContextAccessor.VariationContext?.Segment);
|
||||
}
|
||||
|
||||
// when resolving content from a configured domain, the content cache expects the content route
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed class RequestSegmentService : RequestHeaderHandler, IRequestSegmmentService
|
||||
{
|
||||
public RequestSegmentService(IHttpContextAccessor httpContextAccessor)
|
||||
: base(httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetRequestedSegment()
|
||||
=> GetHeaderValue("Accept-Segment");
|
||||
}
|
||||
@@ -7,9 +7,6 @@ public interface IRequestCultureService
|
||||
/// </summary>
|
||||
string? GetRequestedCulture();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current request culture if applicable.
|
||||
/// </summary>
|
||||
/// <param name="culture">The culture to use for the current request.</param>
|
||||
[Obsolete("Use IVariationContextAccessor to manipulate the variation context. Scheduled for removal in V17.")]
|
||||
void SetRequestCulture(string culture);
|
||||
}
|
||||
|
||||
9
src/Umbraco.Core/DeliveryApi/IRequestSegmentService.cs
Normal file
9
src/Umbraco.Core/DeliveryApi/IRequestSegmentService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface IRequestSegmmentService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the requested segment from the "Accept-Segment" header, if present.
|
||||
/// </summary>
|
||||
string? GetRequestedSegment();
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UMB_WORKSPACE_CONTENT_TYPE_ALIAS_CONDITION_ALIAS } from '@umbraco-cms/backoffice/content-type';
|
||||
|
||||
const workspace: UmbExtensionManifest = {
|
||||
type: 'workspaceView',
|
||||
alias: 'Example.WorkspaceView.EntityContentTypeCondition',
|
||||
@@ -10,7 +12,7 @@ const workspace: UmbExtensionManifest = {
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
alias: 'Umb.Condition.WorkspaceContentTypeAlias',
|
||||
alias: UMB_WORKSPACE_CONTENT_TYPE_ALIAS_CONDITION_ALIAS,
|
||||
//match : 'blogPost'
|
||||
oneOf: ['blogPost', 'mediaType1'],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Workspace Content Type Alias condition alias
|
||||
*/
|
||||
export const UMB_WORKSPACE_CONTENT_TYPE_ALIAS_CONDITION_ALIAS = 'Umb.Condition.WorkspaceContentTypeAlias';
|
||||
@@ -1,20 +1,22 @@
|
||||
import type { UMB_WORKSPACE_CONTENT_TYPE_ALIAS_CONDITION_ALIAS } from './constants.js';
|
||||
import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export type UmbWorkspaceContentTypeAliasConditionConfig =
|
||||
UmbConditionConfigBase<'Umb.Condition.WorkspaceContentTypeAlias'> & {
|
||||
/**
|
||||
* Define a content type alias in which workspace this extension should be available
|
||||
* @example
|
||||
* Depends on implementation, but i.e. "article", "image", "blockPage"
|
||||
*/
|
||||
match?: string;
|
||||
/**
|
||||
* Define one or more content type aliases in which workspace this extension should be available
|
||||
* @example
|
||||
* ["article", "image", "blockPage"]
|
||||
*/
|
||||
oneOf?: Array<string>;
|
||||
};
|
||||
export type UmbWorkspaceContentTypeAliasConditionConfig = UmbConditionConfigBase<
|
||||
typeof UMB_WORKSPACE_CONTENT_TYPE_ALIAS_CONDITION_ALIAS
|
||||
> & {
|
||||
/**
|
||||
* Define a content type alias in which workspace this extension should be available
|
||||
* @example
|
||||
* Depends on implementation, but i.e. "article", "image", "blockPage"
|
||||
*/
|
||||
match?: string;
|
||||
/**
|
||||
* Define one or more content type aliases in which workspace this extension should be available
|
||||
* @example
|
||||
* ["article", "image", "blockPage"]
|
||||
*/
|
||||
oneOf?: Array<string>;
|
||||
};
|
||||
/**
|
||||
* @deprecated Use `UmbWorkspaceContentTypeAliasConditionConfig` instead. This will be removed in Umbraco 17.
|
||||
*/
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './modals/constants.js';
|
||||
export * from './workspace/constants.js';
|
||||
export * from './conditions/constants.js';
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { CompositionTypeModel } from '@umbraco-cms/backoffice/external/back
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
|
||||
export type * from './composition/types.js';
|
||||
export type * from './conditions/types.js';
|
||||
|
||||
export type UmbPropertyContainerTypes = 'Group' | 'Tab';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { UmbDocumentTypeItemModel, UmbDocumentTypeTreeItemModel } from '../
|
||||
import { UMB_DOCUMENT_TYPE_WORKSPACE_MODAL } from '../../constants.js';
|
||||
import { UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../../paths.js';
|
||||
import { UmbDocumentTypePickerInputContext } from './input-document-type.context.js';
|
||||
import { css, html, customElement, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, property, state, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -105,6 +105,10 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
public override get value(): string | undefined {
|
||||
return this.selection.length > 0 ? this.selection.join(',') : undefined;
|
||||
}
|
||||
|
||||
@property({ type: Boolean, attribute: 'readonly' })
|
||||
readonly?: boolean;
|
||||
|
||||
@state()
|
||||
private _items?: Array<UmbDocumentTypeItemModel>;
|
||||
|
||||
@@ -176,7 +180,7 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
}
|
||||
|
||||
#renderAddButton() {
|
||||
if (this.max > 0 && this.selection.length >= this.max) return nothing;
|
||||
if (this.readonly || (this.max > 0 && this.selection.length >= this.max)) return nothing;
|
||||
return html`
|
||||
<uui-button
|
||||
id="btn-add"
|
||||
@@ -206,7 +210,14 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
<uui-ref-node-document-type id=${item.unique} name=${this.localize.string(item.name)} href=${href}>
|
||||
${this.#renderIcon(item)}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${() => this.#removeItem(item)} label=${this.localize.term('general_remove')}></uui-button>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(item)}></uui-button>
|
||||
`,
|
||||
)}
|
||||
</uui-action-bar>
|
||||
</uui-ref-node-document-type>
|
||||
`;
|
||||
|
||||
@@ -9,6 +9,7 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
label: 'Document Type Picker',
|
||||
icon: 'icon-document-dashed-line',
|
||||
group: 'advanced',
|
||||
supportsReadOnly: true,
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
|
||||
@@ -23,6 +23,9 @@ export class UmbPropertyEditorUIDocumentTypePickerElement extends UmbLitElement
|
||||
this.onlyElementTypes = config.getValueByAlias('onlyPickElementTypes') ?? false;
|
||||
}
|
||||
|
||||
@property({ type: Boolean, attribute: 'readonly' })
|
||||
readonly = false;
|
||||
|
||||
@state()
|
||||
min = 0;
|
||||
|
||||
@@ -43,6 +46,7 @@ export class UmbPropertyEditorUIDocumentTypePickerElement extends UmbLitElement
|
||||
.min=${this.min}
|
||||
.max=${this.max}
|
||||
.value=${this.value}
|
||||
.readonly=${this.readonly}
|
||||
.elementTypesOnly=${this.onlyElementTypes ?? false}
|
||||
@change=${this.#onChange}>
|
||||
</umb-input-document-type>
|
||||
|
||||
@@ -249,6 +249,22 @@ internal sealed class OpenApiContractTest : UmbracoTestServerTestBase
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Accept-Segment",
|
||||
"in": "header",
|
||||
"description": "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"Default": {
|
||||
"value": ""
|
||||
},
|
||||
"Segment One": {
|
||||
"value": "segment-one"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Api-Key",
|
||||
"in": "header",
|
||||
@@ -388,6 +404,22 @@ internal sealed class OpenApiContractTest : UmbracoTestServerTestBase
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Accept-Segment",
|
||||
"in": "header",
|
||||
"description": "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"Default": {
|
||||
"value": ""
|
||||
},
|
||||
"Segment One": {
|
||||
"value": "segment-one"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Api-Key",
|
||||
"in": "header",
|
||||
@@ -519,6 +551,22 @@ internal sealed class OpenApiContractTest : UmbracoTestServerTestBase
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Accept-Segment",
|
||||
"in": "header",
|
||||
"description": "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"Default": {
|
||||
"value": ""
|
||||
},
|
||||
"Segment One": {
|
||||
"value": "segment-one"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Api-Key",
|
||||
"in": "header",
|
||||
@@ -653,6 +701,22 @@ internal sealed class OpenApiContractTest : UmbracoTestServerTestBase
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Accept-Segment",
|
||||
"in": "header",
|
||||
"description": "Defines the segment to return. Use this when querying segment variant content items.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"Default": {
|
||||
"value": ""
|
||||
},
|
||||
"Segment One": {
|
||||
"value": "segment-one"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Api-Key",
|
||||
"in": "header",
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Delivery.Services;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class RequestRoutingServiceTests
|
||||
{
|
||||
[TestCase(null)]
|
||||
[TestCase("")]
|
||||
public void Empty_Path_Yields_Nothing(string? path)
|
||||
{
|
||||
var subject = new RequestRoutingService(
|
||||
Mock.Of<IDomainCache>(),
|
||||
Mock.Of<IHttpContextAccessor>(),
|
||||
Mock.Of<IRequestStartItemProviderAccessor>(),
|
||||
Mock.Of<IRequestCultureService>(),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
|
||||
var result = subject.GetContentRoute(path!);
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Explicit_Start_Item_Yields_Path_Prefixed_With_Start_Item_Id()
|
||||
{
|
||||
var startItem = new Mock<IPublishedContent>();
|
||||
startItem.SetupGet(m => m.Id).Returns(1234);
|
||||
|
||||
var requestStartItemProviderMock = new Mock<IRequestStartItemProvider>();
|
||||
requestStartItemProviderMock.Setup(m => m.GetStartItem()).Returns(startItem.Object);
|
||||
|
||||
var requestStartItemProvider = requestStartItemProviderMock.Object;
|
||||
var requestStartItemProviderAccessorMock = new Mock<IRequestStartItemProviderAccessor>();
|
||||
requestStartItemProviderAccessorMock.Setup(m => m.TryGetValue(out requestStartItemProvider)).Returns(true);
|
||||
|
||||
var subject = new RequestRoutingService(
|
||||
Mock.Of<IDomainCache>(),
|
||||
Mock.Of<IHttpContextAccessor>(),
|
||||
requestStartItemProviderAccessorMock.Object,
|
||||
Mock.Of<IRequestCultureService>(),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
|
||||
var result = subject.GetContentRoute("/some/where");
|
||||
Assert.AreEqual("1234/some/where", result);
|
||||
}
|
||||
|
||||
[TestCase("some/where")]
|
||||
[TestCase("/some/where")]
|
||||
public void Without_Domain_Binding_Yields_Path_Prefixed_With_Slash(string requestedPath)
|
||||
{
|
||||
var requestStartItemProviderMock = new Mock<IRequestStartItemProvider>();
|
||||
var requestStartItemProvider = requestStartItemProviderMock.Object;
|
||||
var requestStartItemProviderAccessorMock = new Mock<IRequestStartItemProviderAccessor>();
|
||||
requestStartItemProviderAccessorMock.Setup(m => m.TryGetValue(out requestStartItemProvider)).Returns(true);
|
||||
|
||||
var httpContextAccessorMock = CreateHttpContextAccessorMock();
|
||||
|
||||
var subject = new RequestRoutingService(
|
||||
Mock.Of<IDomainCache>(),
|
||||
httpContextAccessorMock.Object,
|
||||
requestStartItemProviderAccessorMock.Object,
|
||||
Mock.Of<IRequestCultureService>(),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
|
||||
var result = subject.GetContentRoute(requestedPath);
|
||||
Assert.AreEqual("/some/where", result);
|
||||
}
|
||||
|
||||
[TestCase("some/where")]
|
||||
[TestCase("/some/where")]
|
||||
public void With_Domain_Binding_Yields_Path_Prefixed_With_Domain_Content_Id(string requestedPath)
|
||||
{
|
||||
var requestStartItemProviderMock = new Mock<IRequestStartItemProvider>();
|
||||
var requestStartItemProvider = requestStartItemProviderMock.Object;
|
||||
var requestStartItemProviderAccessorMock = new Mock<IRequestStartItemProviderAccessor>();
|
||||
requestStartItemProviderAccessorMock.Setup(m => m.TryGetValue(out requestStartItemProvider)).Returns(true);
|
||||
|
||||
var httpContextAccessorMock = CreateHttpContextAccessorMock();
|
||||
|
||||
var domainCacheMock = GetDomainCacheMock(null);
|
||||
|
||||
var subject = new RequestRoutingService(
|
||||
domainCacheMock.Object,
|
||||
httpContextAccessorMock.Object,
|
||||
requestStartItemProviderAccessorMock.Object,
|
||||
Mock.Of<IRequestCultureService>(),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
|
||||
var result = subject.GetContentRoute(requestedPath);
|
||||
Assert.AreEqual("1234/some/where", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Domain_Binding_Culture_Sets_Variation_Context()
|
||||
{
|
||||
var requestStartItemProviderMock = new Mock<IRequestStartItemProvider>();
|
||||
var requestStartItemProvider = requestStartItemProviderMock.Object;
|
||||
var requestStartItemProviderAccessorMock = new Mock<IRequestStartItemProviderAccessor>();
|
||||
requestStartItemProviderAccessorMock.Setup(m => m.TryGetValue(out requestStartItemProvider)).Returns(true);
|
||||
|
||||
var httpContextAccessorMock = CreateHttpContextAccessorMock();
|
||||
|
||||
var domainCacheMock = GetDomainCacheMock("da-DK");
|
||||
|
||||
var variationContextAccessor = new TestVariationContextAccessor();
|
||||
|
||||
var subject = new RequestRoutingService(
|
||||
domainCacheMock.Object,
|
||||
httpContextAccessorMock.Object,
|
||||
requestStartItemProviderAccessorMock.Object,
|
||||
Mock.Of<IRequestCultureService>(),
|
||||
variationContextAccessor);
|
||||
|
||||
var result = subject.GetContentRoute("/some/where");
|
||||
Assert.AreEqual("1234/some/where", result);
|
||||
Assert.IsNotNull(variationContextAccessor.VariationContext);
|
||||
Assert.AreEqual("da-DK", variationContextAccessor.VariationContext.Culture);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Domain_Binding_Culture_Does_Not_Overwrite_Existing_Segment_Context()
|
||||
{
|
||||
var requestStartItemProviderMock = new Mock<IRequestStartItemProvider>();
|
||||
var requestStartItemProvider = requestStartItemProviderMock.Object;
|
||||
var requestStartItemProviderAccessorMock = new Mock<IRequestStartItemProviderAccessor>();
|
||||
requestStartItemProviderAccessorMock.Setup(m => m.TryGetValue(out requestStartItemProvider)).Returns(true);
|
||||
|
||||
var httpContextAccessorMock = CreateHttpContextAccessorMock();
|
||||
|
||||
var domainCacheMock = GetDomainCacheMock("da-DK");
|
||||
|
||||
var variationContextAccessor = new TestVariationContextAccessor
|
||||
{
|
||||
VariationContext = new VariationContext("it-IT", "some-segment")
|
||||
};
|
||||
|
||||
var subject = new RequestRoutingService(
|
||||
domainCacheMock.Object,
|
||||
httpContextAccessorMock.Object,
|
||||
requestStartItemProviderAccessorMock.Object,
|
||||
Mock.Of<IRequestCultureService>(),
|
||||
variationContextAccessor);
|
||||
|
||||
var result = subject.GetContentRoute("/some/where");
|
||||
Assert.AreEqual("1234/some/where", result);
|
||||
Assert.IsNotNull(variationContextAccessor.VariationContext);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual("da-DK", variationContextAccessor.VariationContext.Culture);
|
||||
Assert.AreEqual("some-segment", variationContextAccessor.VariationContext.Segment);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Explicit_Request_Culture_Overrides_Domain_Binding_Culture()
|
||||
{
|
||||
var requestStartItemProviderMock = new Mock<IRequestStartItemProvider>();
|
||||
var requestStartItemProvider = requestStartItemProviderMock.Object;
|
||||
var requestStartItemProviderAccessorMock = new Mock<IRequestStartItemProviderAccessor>();
|
||||
requestStartItemProviderAccessorMock.Setup(m => m.TryGetValue(out requestStartItemProvider)).Returns(true);
|
||||
|
||||
var httpContextAccessorMock = CreateHttpContextAccessorMock();
|
||||
|
||||
var domainCacheMock = GetDomainCacheMock("da-DK");
|
||||
|
||||
const string expectedCulture = "it-IT";
|
||||
const string expectedSegment = "some-segment";
|
||||
var variationContextAccessor = new TestVariationContextAccessor
|
||||
{
|
||||
VariationContext = new VariationContext(expectedCulture, expectedSegment)
|
||||
};
|
||||
|
||||
var requestCultureServiceMock = new Mock<IRequestCultureService>();
|
||||
requestCultureServiceMock
|
||||
.Setup(m => m.GetRequestedCulture())
|
||||
.Returns(expectedCulture);
|
||||
|
||||
var subject = new RequestRoutingService(
|
||||
domainCacheMock.Object,
|
||||
httpContextAccessorMock.Object,
|
||||
requestStartItemProviderAccessorMock.Object,
|
||||
requestCultureServiceMock.Object,
|
||||
variationContextAccessor);
|
||||
|
||||
var result = subject.GetContentRoute("/some/where");
|
||||
Assert.AreEqual("1234/some/where", result);
|
||||
Assert.IsNotNull(variationContextAccessor.VariationContext);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(expectedCulture, variationContextAccessor.VariationContext.Culture);
|
||||
Assert.AreEqual(expectedSegment, variationContextAccessor.VariationContext.Segment);
|
||||
});
|
||||
}
|
||||
|
||||
private static Mock<IHttpContextAccessor> CreateHttpContextAccessorMock()
|
||||
{
|
||||
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
|
||||
httpContextAccessorMock
|
||||
.SetupGet(m => m.HttpContext)
|
||||
.Returns(
|
||||
new DefaultHttpContext
|
||||
{
|
||||
Request =
|
||||
{
|
||||
Scheme = "https",
|
||||
Host = new HostString("some.host"),
|
||||
Path = "/",
|
||||
QueryString = new QueryString(string.Empty)
|
||||
}
|
||||
});
|
||||
return httpContextAccessorMock;
|
||||
}
|
||||
|
||||
private static Mock<IDomainCache> GetDomainCacheMock(string? culture)
|
||||
{
|
||||
var domainCacheMock = new Mock<IDomainCache>();
|
||||
domainCacheMock
|
||||
.Setup(m => m.GetAll(It.IsAny<bool>()))
|
||||
.Returns([
|
||||
new Domain(1, "some.host", 1234, culture, false, 1)
|
||||
]);
|
||||
return domainCacheMock;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user