Merge branch 'main' into v17/dev
# Conflicts: # src/Umbraco.Core/Services/PropertyValidationService.cs # src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs # src/Umbraco.Infrastructure/PublishedContentQuery.cs # src/Umbraco.Web.UI.Client/package-lock.json # src/Umbraco.Web.UI.Client/package.json # src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.context.ts # src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts # templates/UmbracoProject/.template.config/template.json # version.json
This commit is contained in:
18
tests/Umbraco.Tests.AcceptanceTest/package-lock.json
generated
18
tests/Umbraco.Tests.AcceptanceTest/package-lock.json
generated
@@ -7,8 +7,8 @@
|
||||
"name": "acceptancetest",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.38",
|
||||
"@umbraco/playwright-testhelpers": "^16.0.42",
|
||||
"@umbraco/json-models-builders": "^2.0.40",
|
||||
"@umbraco/playwright-testhelpers": "^16.0.47",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"node-fetch": "^2.6.7"
|
||||
@@ -58,21 +58,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/json-models-builders": {
|
||||
"version": "2.0.38",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.38.tgz",
|
||||
"integrity": "sha512-6nC1Y1xn+8zyqU3iqHubRo18L53TdZkhHIY4z68VSLcA6YoAzdxtjw+zx7yDIMV+epoQ4NCG2ooAa0gBhHqQgg==",
|
||||
"version": "2.0.40",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.40.tgz",
|
||||
"integrity": "sha512-Yqojp/0akRgXsnjg18+MjMdkRvFrmlUNbfITgZ3d1h/PIRbWXPNKY1YAfZmdUv+g1SRSHrbIRpPPtSy+gNOjHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/playwright-testhelpers": {
|
||||
"version": "16.0.42",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.42.tgz",
|
||||
"integrity": "sha512-ePKl8gtELoIMEV57E3N4VumfKNkuOTFo/LYH7ePhseCcm5oUh1Cc/RVqvlXYsdfBTiMfZ7x7Nu4lOSv15D2Z3Q==",
|
||||
"version": "16.0.47",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.47.tgz",
|
||||
"integrity": "sha512-N88UCvjqCwJMRCu5wUmW2xxPVqEMR0sKGDlUsko9EejvyyJBFSE00PRGyWo6lPuYxAy4LkkONwIWBATWiry7xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "2.0.38",
|
||||
"@umbraco/json-models-builders": "2.0.40",
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.38",
|
||||
"@umbraco/playwright-testhelpers": "^16.0.42",
|
||||
"@umbraco/json-models-builders": "^2.0.40",
|
||||
"@umbraco/playwright-testhelpers": "^16.0.47",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"node-fetch": "^2.6.7"
|
||||
|
||||
@@ -44,14 +44,42 @@ export default defineConfig({
|
||||
testMatch: '**/*.setup.ts',
|
||||
},
|
||||
{
|
||||
name: 'chromium',
|
||||
name: 'defaultConfig',
|
||||
testMatch: 'DefaultConfig/**',
|
||||
dependencies: ['setup'],
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// Use prepared auth state.
|
||||
ignoreHTTPSErrors: true,
|
||||
storageState: STORAGE_STATE,
|
||||
storageState: STORAGE_STATE
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'deliveryApi',
|
||||
testMatch: 'DeliveryApi/**',
|
||||
dependencies: ['setup'],
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// Use prepared auth state.
|
||||
ignoreHTTPSErrors: true,
|
||||
storageState: STORAGE_STATE
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalLoginAzureADB2C',
|
||||
testMatch: 'ExternalLogin/AzureADB2C/**',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
ignoreHTTPSErrors: true,
|
||||
}
|
||||
},
|
||||
// This project is used to test the install steps, for that we do not need to authenticate.
|
||||
{
|
||||
name: 'unattendedInstallConfig',
|
||||
testMatch: 'UnattendedInstallConfig/**',
|
||||
use: {
|
||||
...devices['Desktop Chrome']
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
@@ -85,3 +85,54 @@ test('can publish content with RTE Tiptap property editor', async ({umbracoApi,
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values[0].value.markup).toEqual('<p>' + inputText + '</p>');
|
||||
});
|
||||
|
||||
// This is a test for the regression issue #19763
|
||||
test('can save a variant content node after removing embedded block in RTE', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
// Language
|
||||
const danishIsoCode = 'da';
|
||||
await umbracoApi.language.createDanishLanguage();
|
||||
// Content Names
|
||||
const englishContentName = 'English Content';
|
||||
const danishContentName = 'Danish Content';
|
||||
// Element Type
|
||||
const elementTypeName = 'Default Element Type';
|
||||
const elementTypeGroupName = 'Content';
|
||||
const elementTypeDataTypeName = 'Textstring';
|
||||
const elementTypeDataType = await umbracoApi.dataType.getByName(elementTypeDataTypeName);
|
||||
// Rich Text Editor
|
||||
const richTextEditorDataTypeName = 'Rich Text Editor with a block';
|
||||
const textStringValue = 'Block Content';
|
||||
const elementTypeId = await umbracoApi.documentType.createDefaultElementTypeWithVaryByCulture(elementTypeName, elementTypeGroupName, elementTypeDataTypeName, elementTypeDataType.id, true, false);
|
||||
const richTextEditorId = await umbracoApi.dataType.createRichTextEditorWithABlock(richTextEditorDataTypeName, elementTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, richTextEditorDataTypeName, richTextEditorId, 'TestGroup', true, false);
|
||||
const cultures = [{isoCode: 'en-US', name: englishContentName}, {isoCode: 'da', name: danishContentName}];
|
||||
await umbracoApi.document.createDocumentWithMultipleVariantsWithSharedProperty(contentName, documentTypeId, AliasHelper.toAlias(richTextEditorDataTypeName), 'Umbraco.RichText', cultures, '');
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
await umbracoUi.content.goToContentWithName(englishContentName);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickInsertBlockButton();
|
||||
await umbracoUi.content.clickLinkWithName(elementTypeName);
|
||||
await umbracoUi.content.enterTextstring(textStringValue);
|
||||
await umbracoUi.content.clickCreateModalButton();
|
||||
await umbracoUi.content.clickSaveButtonForContent();
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
const contentData = await umbracoApi.document.getByName(englishContentName);
|
||||
expect(contentData.values[0].value.blocks.contentData[0].values[0].value).toBe(textStringValue);
|
||||
await umbracoUi.content.clearTipTapEditor();
|
||||
await umbracoUi.content.clickSaveButtonForContent();
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isErrorNotificationVisible(false);
|
||||
await umbracoUi.content.waitForContentToBeCreated();
|
||||
expect(await umbracoApi.document.doesNameExist(englishContentName)).toBeTruthy();
|
||||
|
||||
// Clean
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(elementTypeName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(richTextEditorDataTypeName);
|
||||
await umbracoApi.language.ensureIsoCodeNotExists(danishIsoCode);
|
||||
});
|
||||
|
||||
@@ -248,7 +248,7 @@ test('can add a thumbnail to a block', {tag: '@smoke'}, async ({umbracoApi, umbr
|
||||
const textStringData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const contentElementTypeId = await umbracoApi.documentType.createDefaultElementType(elementTypeName, groupName, dataTypeName, textStringData.id);
|
||||
await umbracoApi.dataType.createBlockGridWithABlock(blockGridEditorName, contentElementTypeId);
|
||||
const mediaUrl = await umbracoApi.media.getMediaUrl(mediaId);
|
||||
const mediaUrl = await umbracoApi.media.getFullMediaUrl(mediaId);
|
||||
|
||||
// Act
|
||||
await umbracoUi.dataType.goToDataType(blockGridEditorName);
|
||||
|
||||
@@ -422,7 +422,7 @@ test('can add a thumbnail to a block', {tag: '@release'}, async ({umbracoApi, um
|
||||
const textStringData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const contentElementTypeId = await umbracoApi.documentType.createDefaultElementType(elementTypeName, groupName, dataTypeName, textStringData.id);
|
||||
await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListEditorName, contentElementTypeId);
|
||||
const mediaUrl = await umbracoApi.media.getMediaUrl(mediaId);
|
||||
const mediaUrl = await umbracoApi.media.getFullMediaUrl(mediaId);
|
||||
|
||||
// Act
|
||||
await umbracoUi.dataType.goToDataType(blockListEditorName);
|
||||
|
||||
@@ -6,16 +6,37 @@ const tipTapPropertyEditorName = 'Rich Text Editor [Tiptap] Property Editor UI';
|
||||
const tipTapAlias = 'Umbraco.RichText';
|
||||
const tipTapUiAlias = 'Umb.PropertyEditorUi.Tiptap';
|
||||
const extensionsDefaultValue = [
|
||||
"Umb.Tiptap.RichTextEssentials",
|
||||
"Umb.Tiptap.Anchor",
|
||||
"Umb.Tiptap.Blockquote",
|
||||
"Umb.Tiptap.Bold",
|
||||
"Umb.Tiptap.BulletList",
|
||||
"Umb.Tiptap.CodeBlock",
|
||||
"Umb.Tiptap.Embed",
|
||||
"Umb.Tiptap.Link",
|
||||
"Umb.Tiptap.Figure",
|
||||
"Umb.Tiptap.Heading",
|
||||
"Umb.Tiptap.HorizontalRule",
|
||||
"Umb.Tiptap.HtmlAttributeClass",
|
||||
"Umb.Tiptap.HtmlAttributeDataset",
|
||||
"Umb.Tiptap.HtmlAttributeId",
|
||||
"Umb.Tiptap.HtmlAttributeStyle",
|
||||
"Umb.Tiptap.HtmlTagDiv",
|
||||
"Umb.Tiptap.HtmlTagSpan",
|
||||
"Umb.Tiptap.Image",
|
||||
"Umb.Tiptap.Italic",
|
||||
"Umb.Tiptap.Link",
|
||||
"Umb.Tiptap.MediaUpload",
|
||||
"Umb.Tiptap.OrderedList",
|
||||
"Umb.Tiptap.Strike",
|
||||
"Umb.Tiptap.Subscript",
|
||||
"Umb.Tiptap.Superscript",
|
||||
"Umb.Tiptap.Table",
|
||||
"Umb.Tiptap.Underline",
|
||||
"Umb.Tiptap.TextAlign",
|
||||
"Umb.Tiptap.MediaUpload"
|
||||
"Umb.Tiptap.TextDirection",
|
||||
"Umb.Tiptap.TextIndent",
|
||||
"Umb.Tiptap.TrailingNode",
|
||||
"Umb.Tiptap.Underline",
|
||||
"Umb.Tiptap.WordCount"
|
||||
];
|
||||
|
||||
const toolbarDefaultValue = [
|
||||
|
||||
@@ -73,7 +73,7 @@ for (const mediaFileType of mediaFileTypes) {
|
||||
// Assert
|
||||
await umbracoUi.media.waitForMediaItemToBeCreated();
|
||||
const mediaData = await umbracoApi.media.getByName(mediaFileType.fileName);
|
||||
const mediaUrl = await umbracoApi.media.getMediaUrl(mediaData.id);
|
||||
const mediaUrl = await umbracoApi.media.getFullMediaUrl(mediaData.id);
|
||||
await umbracoUi.media.doesMediaHaveThumbnail(mediaData.id, mediaFileType.thumbnail, mediaUrl);
|
||||
await umbracoUi.media.isMediaTreeItemVisible(mediaFileType.fileName);
|
||||
expect(await umbracoApi.media.doesNameExist(mediaFileType.fileName)).toBeTruthy();
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -0,0 +1,27 @@
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.CreateUmbracoBuilder()
|
||||
.AddBackOffice()
|
||||
.AddWebsite()
|
||||
.AddDeliveryApi()
|
||||
.AddComposers()
|
||||
.Build();
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
await app.BootUmbracoAsync();
|
||||
|
||||
|
||||
app.UseUmbraco()
|
||||
.WithMiddleware(u =>
|
||||
{
|
||||
u.UseBackOffice();
|
||||
u.UseWebsite();
|
||||
})
|
||||
.WithEndpoints(u =>
|
||||
{
|
||||
u.UseBackOfficeEndpoints();
|
||||
u.UseWebsiteEndpoints();
|
||||
});
|
||||
|
||||
await app.RunAsync();
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$schema": "appsettings-schema.json",
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"Configure": [
|
||||
{
|
||||
"Name": "Console"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"DeliveryApi": {
|
||||
"Enabled": true,
|
||||
"Media": {
|
||||
"Enabled": true
|
||||
}
|
||||
},
|
||||
"Unattended": {
|
||||
"InstallUnattended": true,
|
||||
"UnattendedUserName": "Playwright Test",
|
||||
"UnattendedUserEmail": "playwright@umbraco.com",
|
||||
"UnattendedUserPassword": "UmbracoAcceptance123!"
|
||||
},
|
||||
"Content": {
|
||||
"ContentVersionCleanupPolicy": {
|
||||
"EnableCleanup": false
|
||||
}
|
||||
},
|
||||
"Global": {
|
||||
"DisableElectionForSingleServer": true,
|
||||
"InstallMissingDatabase": true,
|
||||
"Id": "00000000-0000-0000-0000-000000000042",
|
||||
"VersionCheckPeriod": 0,
|
||||
"UseHttps": true
|
||||
},
|
||||
"HealthChecks": {
|
||||
"Notification": {
|
||||
"Enabled": false
|
||||
}
|
||||
},
|
||||
"KeepAlive": {
|
||||
"DisableKeepAliveTask": true
|
||||
},
|
||||
"WebRouting": {
|
||||
"UmbracoApplicationUrl": "https://localhost:44331/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import {expect} from '@playwright/test';
|
||||
import {AliasHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
test('can get content from delivery api', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const documentTypeName = 'TestDocumentType';
|
||||
const contentName = 'TestContent';
|
||||
const dataTypeName = 'Textstring';
|
||||
const textStringValue = 'This is a test text string value';
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
const dataType = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataType.id, 'TestGroup');
|
||||
const documentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, textStringValue, dataTypeName);
|
||||
const propertyValue = {
|
||||
dataTypeName: AliasHelper.toAlias(dataTypeName),
|
||||
dataTypeValue: textStringValue
|
||||
}
|
||||
|
||||
// Act
|
||||
await umbracoApi.document.publish(documentId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.contentDeliveryApi.doesContentItemWithIdContainValues(documentId, contentName, AliasHelper.toAlias(documentTypeName), [propertyValue])).toBeTruthy();
|
||||
|
||||
// Clean
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test('can get media image from delivery api', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const mediaName = 'TestMedia';
|
||||
const mediaTypeName = 'File';
|
||||
await umbracoApi.media.ensureNameNotExists(mediaName);
|
||||
const mediaId = await umbracoApi.media.createDefaultMediaFile(mediaName);
|
||||
const mediaUrl = await umbracoApi.media.getMediaUrlWithoutBaseUrl(mediaId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.mediaDeliveryApi.doesMediaItemWithIdContainValues(mediaId, mediaName, mediaTypeName, mediaUrl)).toBeTruthy();
|
||||
|
||||
// Clean
|
||||
await umbracoApi.media.ensureNameNotExists(mediaName);
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$schema": "appsettings-schema.json",
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"Configure": [
|
||||
{
|
||||
"Name": "Console"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"DeliveryApi": {
|
||||
"Enabled": true,
|
||||
"Media": {
|
||||
"Enabled": true
|
||||
}
|
||||
},
|
||||
"Unattended": {
|
||||
"InstallUnattended": true,
|
||||
"UnattendedUserName": "Playwright Test",
|
||||
"UnattendedUserEmail": "playwright@umbraco.com",
|
||||
"UnattendedUserPassword": "UmbracoAcceptance123!"
|
||||
},
|
||||
"Content": {
|
||||
"ContentVersionCleanupPolicy": {
|
||||
"EnableCleanup": false
|
||||
}
|
||||
},
|
||||
"Global": {
|
||||
"DisableElectionForSingleServer": true,
|
||||
"InstallMissingDatabase": true,
|
||||
"Id": "00000000-0000-0000-0000-000000000042",
|
||||
"VersionCheckPeriod": 0,
|
||||
"UseHttps": true
|
||||
},
|
||||
"HealthChecks": {
|
||||
"Notification": {
|
||||
"Enabled": false
|
||||
}
|
||||
},
|
||||
"KeepAlive": {
|
||||
"DisableKeepAliveTask": true
|
||||
},
|
||||
"WebRouting": {
|
||||
"UmbracoApplicationUrl": "https://localhost:44331/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "../../umbraco-package-schema.json",
|
||||
"name": "Azure B2C Login",
|
||||
"allowPublicAccess": true,
|
||||
"extensions": [
|
||||
{
|
||||
"type": "authProvider",
|
||||
"alias": "Test.AzureB2C",
|
||||
"name": "Azure AD B2C",
|
||||
"forProviderName": "Umbraco.AzureB2C",
|
||||
"meta": {
|
||||
"label": "Sign in with Azure AD B2C",
|
||||
"defaultView": {
|
||||
"icon": "icon-cloud"
|
||||
},
|
||||
"behavior": {
|
||||
"autoRedirect": false
|
||||
},
|
||||
"linking": {
|
||||
"allowManualLinking": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
|
||||
{
|
||||
public static class AzureB2CAuthenticationExtensions
|
||||
{
|
||||
public static IUmbracoBuilder ConfigureAuthentication(this IUmbracoBuilder builder,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
var b2cSettings = new AzureB2CSettings();
|
||||
|
||||
builder.AddBackOfficeExternalLogins(logins =>
|
||||
{
|
||||
const string schemeName = AzureB2COptions.SchemeName;
|
||||
var backOfficeScheme = Constants.Security.BackOfficeExternalAuthenticationTypePrefix + schemeName;
|
||||
|
||||
logins.AddBackOfficeLogin(backOfficeAuth =>
|
||||
{
|
||||
backOfficeAuth.AddOpenIdConnect(backOfficeScheme, options =>
|
||||
{
|
||||
options.RequireHttpsMetadata = true;
|
||||
options.SaveTokens = true;
|
||||
options.ClientId = b2cSettings.ClientId;
|
||||
options.ClientSecret = b2cSettings.ClientSecret;
|
||||
options.CallbackPath = "/umbraco-b2c-users-signin";
|
||||
options.MetadataAddress =
|
||||
$"https://{b2cSettings.Domain}/{b2cSettings.Tenant}/{b2cSettings.Policy}/v2.0/.well-known/openid-configuration";
|
||||
|
||||
options.ResponseType = OpenIdConnectResponseType.Code;
|
||||
options.TokenValidationParameters.SaveSigninToken = true;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.TokenValidationParameters.NameClaimType = "name";
|
||||
options.TokenValidationParameters.RoleClaimType = "role";
|
||||
|
||||
options.Events = new OpenIdConnectEvents
|
||||
{
|
||||
OnTokenResponseReceived = context =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.TokenEndpointResponse.AccessToken))
|
||||
{
|
||||
context.TokenEndpointResponse.AccessToken = "empty_access_token";
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
|
||||
OnTokenValidated = context =>
|
||||
{
|
||||
var identity = context.Principal!.Identities.First();
|
||||
|
||||
var email = identity.FindFirst("emails")?.Value
|
||||
?? identity.FindFirst(ClaimTypes.Email)?.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
identity.AddClaim(new Claim(ClaimTypes.Email, email));
|
||||
identity.AddClaim(new Claim("email", email));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
|
||||
{
|
||||
public class AzureB2CComposer : IComposer
|
||||
{
|
||||
public void Compose(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.ConfigureOptions<AzureB2COptions>();
|
||||
|
||||
builder.ConfigureAuthentication(builder.Config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
|
||||
{
|
||||
public class AzureB2COptions : IConfigureNamedOptions<BackOfficeExternalLoginProviderOptions>
|
||||
{
|
||||
public const string SchemeName = "AzureB2C";
|
||||
|
||||
public void Configure(string? name, BackOfficeExternalLoginProviderOptions options)
|
||||
{
|
||||
if (name != Constants.Security.BackOfficeExternalAuthenticationTypePrefix + SchemeName)
|
||||
return;
|
||||
|
||||
options.AutoLinkOptions = new ExternalSignInAutoLinkOptions(
|
||||
autoLinkExternalAccount: true,
|
||||
defaultUserGroups: [Constants.Security.AdminGroupAlias],
|
||||
defaultCulture: "en-US",
|
||||
allowManualLinking: true
|
||||
)
|
||||
{
|
||||
OnAutoLinking = (user, loginInfo) => { user.IsApproved = true; },
|
||||
OnExternalLogin = (user, loginInfo) => { return true; }
|
||||
};
|
||||
}
|
||||
|
||||
public void Configure(BackOfficeExternalLoginProviderOptions options) =>
|
||||
Configure(Constants.Security.BackOfficeExternalAuthenticationTypePrefix + SchemeName, options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Tests.AcceptanceTest.ExternalLogin.AzureADB2C
|
||||
{
|
||||
public class AzureB2CSettings
|
||||
{
|
||||
public string Domain { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CDOMAIN") ?? string.Empty;
|
||||
public string Tenant { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CTENANT") ?? string.Empty;
|
||||
public string Policy { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CPOLICY") ?? string.Empty;
|
||||
public string ClientId { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CCLIENTID") ?? string.Empty;
|
||||
public string ClientSecret { get; set; } = Environment.GetEnvironmentVariable("AZUREADB2CCLIENTSECRET") ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"$schema": "appsettings-schema.json",
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"Configure": [
|
||||
{
|
||||
"Name": "Console"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"Unattended": {
|
||||
"InstallUnattended": true,
|
||||
"UnattendedUserName": "Playwright Test",
|
||||
"UnattendedUserEmail": "playwright@umbraco.com",
|
||||
"UnattendedUserPassword": "UmbracoAcceptance123!"
|
||||
},
|
||||
"Content": {
|
||||
"ContentVersionCleanupPolicy": {
|
||||
"EnableCleanup": false
|
||||
}
|
||||
},
|
||||
"Global": {
|
||||
"DisableElectionForSingleServer": true,
|
||||
"InstallMissingDatabase": true,
|
||||
"Id": "00000000-0000-0000-0000-000000000042",
|
||||
"VersionCheckPeriod": 0,
|
||||
"UseHttps": true
|
||||
},
|
||||
"HealthChecks": {
|
||||
"Notification": {
|
||||
"Enabled": false
|
||||
}
|
||||
},
|
||||
"KeepAlive": {
|
||||
"DisableKeepAliveTask": true
|
||||
},
|
||||
"WebRouting": {
|
||||
"UmbracoApplicationUrl": "https://localhost:44331/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
const azureEmail = process.env.AZUREADB2CTESTUSEREMAIL;
|
||||
const azurePassword = process.env.AZUREADB2CTESTUSERPASSWORD;
|
||||
|
||||
// Really simple test to check if we can log in using Azure AD B2C
|
||||
test('Log in to Umbraco using Azure AD B2C', async ({umbracoUi}) => {
|
||||
test.slow();
|
||||
// Arrange
|
||||
await umbracoUi.goToBackOffice();
|
||||
|
||||
// Act
|
||||
await umbracoUi.externalLogin.clickSignInWithAzureADB2CButton();
|
||||
await umbracoUi.externalLogin.enterAzureADB2CEmail(azureEmail);
|
||||
await umbracoUi.externalLogin.enterAzureADB2CPassword(azurePassword);
|
||||
await umbracoUi.externalLogin.clickSignInButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "appsettings-schema.json",
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"Configure": [
|
||||
{
|
||||
"Name": "Console"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
"Unattended": {
|
||||
"InstallUnattended": false
|
||||
},
|
||||
"Content": {
|
||||
"ContentVersionCleanupPolicy": {
|
||||
"EnableCleanup": false
|
||||
}
|
||||
},
|
||||
"Global": {
|
||||
"DisableElectionForSingleServer": true,
|
||||
"Id": "00000000-0000-0000-0000-000000000042",
|
||||
"VersionCheckPeriod": 0,
|
||||
"UseHttps": true
|
||||
},
|
||||
"HealthChecks": {
|
||||
"Notification": {
|
||||
"Enabled": false
|
||||
}
|
||||
},
|
||||
"KeepAlive": {
|
||||
"DisableKeepAliveTask": true
|
||||
},
|
||||
"WebRouting": {
|
||||
"UmbracoApplicationUrl": "https://localhost:44331/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// To be able to test different databases, we need to set an additional UnattendedInstallConfig up because we would have to start from scratch, otherwise we would be using the same database.
|
||||
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
const name = 'TestName';
|
||||
const email = process.env.UMBRACO_USER_LOGIN;
|
||||
const password = process.env.UMBRACO_USER_PASSWORD;
|
||||
|
||||
test('Install Umbraco using SQLServer Express', async ({umbracoUi}) => {
|
||||
test.slow();
|
||||
// Arrange
|
||||
await umbracoUi.install.goToInstallPage();
|
||||
|
||||
// Act
|
||||
await umbracoUi.install.enterName(name);
|
||||
await umbracoUi.install.enterEmail(email);
|
||||
await umbracoUi.install.enterPassword(password);
|
||||
await umbracoUi.install.clickNextButton();
|
||||
await umbracoUi.install.clickNextButton();
|
||||
await umbracoUi.install.setDatabaseType('SQL Server Express LocalDB');
|
||||
await umbracoUi.install.doesDatabaseHaveType('SQL Server Express LocalDB');
|
||||
await umbracoUi.install.clickInstallButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.login.enterEmail(email);
|
||||
await umbracoUi.login.enterPassword(password);
|
||||
await umbracoUi.login.clickLoginButton();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
// To be able to test different databases, we need to set an additional UnattendedInstallConfig up because we would have to start from scratch, otherwise we would be using the same database.
|
||||
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
const name = 'TestName';
|
||||
const email = process.env.UMBRACO_USER_LOGIN;
|
||||
const password = process.env.UMBRACO_USER_PASSWORD;
|
||||
|
||||
test('Install Umbraco using SQLite', async ({umbracoUi}) => {
|
||||
test.slow();
|
||||
// Arrange
|
||||
await umbracoUi.install.goToInstallPage();
|
||||
|
||||
// Act
|
||||
await umbracoUi.install.enterName(name);
|
||||
await umbracoUi.install.enterEmail(email);
|
||||
await umbracoUi.install.enterPassword(password);
|
||||
await umbracoUi.install.clickNextButton();
|
||||
await umbracoUi.install.clickNextButton();
|
||||
await umbracoUi.install.doesDatabaseHaveType('SQLite');
|
||||
await umbracoUi.install.clickInstallButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.login.enterEmail(email);
|
||||
await umbracoUi.login.enterPassword(password);
|
||||
await umbracoUi.login.clickLoginButton();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Tests.Common.TestHelpers;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services.Trees;
|
||||
|
||||
public abstract class FileSystemTreeServiceTestsBase : UmbracoIntegrationTest
|
||||
{
|
||||
protected FileSystems FileSystems { get; private set; }
|
||||
|
||||
protected IFileSystem TestFileSystem { get; private set; }
|
||||
|
||||
protected abstract string FileSystemPath { get; }
|
||||
|
||||
protected IHostingEnvironment HostingEnvironment => GetRequiredService<IHostingEnvironment>();
|
||||
|
||||
[SetUp]
|
||||
public void SetUpFileSystem()
|
||||
{
|
||||
TestFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, LoggerFactory.CreateLogger<PhysicalFileSystem>(), HostingEnvironment.MapPathWebRoot(FileSystemPath), HostingEnvironment.ToAbsolute(FileSystemPath));
|
||||
|
||||
FileSystems = FileSystemsCreator.CreateTestFileSystems(
|
||||
LoggerFactory,
|
||||
IOHelper,
|
||||
GetRequiredService<IOptions<GlobalSettings>>(),
|
||||
HostingEnvironment,
|
||||
GetPartialViewsFileSystem(),
|
||||
GetStylesheetsFileSystem(),
|
||||
GetScriptsFileSystem(),
|
||||
null);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
using var stream = CreateStream(Path.Join("tests"));
|
||||
TestFileSystem.AddFile($"file{i}", stream);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream CreateStream(string contents = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contents))
|
||||
{
|
||||
contents = "/* test */";
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(contents);
|
||||
return new MemoryStream(bytes);
|
||||
}
|
||||
|
||||
protected virtual IFileSystem? GetPartialViewsFileSystem() => null;
|
||||
|
||||
protected virtual IFileSystem? GetStylesheetsFileSystem() => null;
|
||||
|
||||
protected virtual IFileSystem? GetScriptsFileSystem() => null;
|
||||
|
||||
[TearDown]
|
||||
public void TearDownFileSystem()
|
||||
{
|
||||
Purge(TestFileSystem, string.Empty);
|
||||
FileSystems = null;
|
||||
}
|
||||
|
||||
private static void Purge(IFileSystem fs, string path)
|
||||
{
|
||||
var files = fs.GetFiles(path, "*");
|
||||
foreach (var file in files)
|
||||
{
|
||||
fs.DeleteFile(file);
|
||||
}
|
||||
|
||||
var dirs = fs.GetDirectories(path);
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
Purge(fs, dir);
|
||||
fs.DeleteDirectory(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Management.Services.FileSystem;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services.Trees;
|
||||
|
||||
public class PartialViewTreeServiceTests : FileSystemTreeServiceTestsBase
|
||||
{
|
||||
protected override string FileSystemPath => Constants.SystemDirectories.PartialViews;
|
||||
|
||||
protected override IFileSystem? GetPartialViewsFileSystem() => TestFileSystem;
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Siblings_From_PartialView_Tree_Service()
|
||||
{
|
||||
var service = new PartialViewTreeService(FileSystems);
|
||||
|
||||
FileSystemTreeItemPresentationModel[] treeModel = service.GetSiblingsViewModels("file5", 1, 1, out long before, out var after);
|
||||
int index = Array.FindIndex(treeModel, item => item.Name == "file5");
|
||||
|
||||
Assert.AreEqual(treeModel[index].Name, "file5");
|
||||
Assert.AreEqual(treeModel[index - 1].Name, "file4");
|
||||
Assert.AreEqual(treeModel[index + 1].Name, "file6");
|
||||
Assert.That(treeModel.Length == 3);
|
||||
Assert.AreEqual(after, 3);
|
||||
Assert.AreEqual(before, 4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Ancestors_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new PartialViewTreeService(FileSystems);
|
||||
|
||||
var path = Path.Join("tests", "file5");
|
||||
FileSystemTreeItemPresentationModel[] treeModel = service.GetAncestorModels(path, true);
|
||||
|
||||
Assert.IsNotEmpty(treeModel);
|
||||
Assert.AreEqual(treeModel.Length, 2);
|
||||
Assert.AreEqual(treeModel[0].Name, "tests");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_PathViewModels_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new PartialViewTreeService(FileSystems);
|
||||
|
||||
FileSystemTreeItemPresentationModel[] treeModels = service.GetPathViewModels(string.Empty, 0, Int32.MaxValue, out var totalItems);
|
||||
|
||||
Assert.IsNotEmpty(treeModels);
|
||||
Assert.AreEqual(treeModels.Length, totalItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Management.Services.FileSystem;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services.Trees;
|
||||
|
||||
public class ScriptTreeServiceTests : FileSystemTreeServiceTestsBase
|
||||
{
|
||||
protected override string FileSystemPath => GlobalSettings.UmbracoScriptsPath;
|
||||
|
||||
protected override IFileSystem? GetScriptsFileSystem() => TestFileSystem;
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Siblings_From_Script_Tree_Service()
|
||||
{
|
||||
var service = new ScriptTreeService(FileSystems);
|
||||
|
||||
FileSystemTreeItemPresentationModel[] treeModel = service.GetSiblingsViewModels("file5", 1, 1, out long before, out var after);
|
||||
int index = Array.FindIndex(treeModel, item => item.Name == "file5");
|
||||
|
||||
Assert.AreEqual(treeModel[index].Name, "file5");
|
||||
Assert.AreEqual(treeModel[index - 1].Name, "file4");
|
||||
Assert.AreEqual(treeModel[index + 1].Name, "file6");
|
||||
Assert.That(treeModel.Length == 3);
|
||||
Assert.AreEqual(after, 3);
|
||||
Assert.AreEqual(before, 4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Ancestors_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new ScriptTreeService(FileSystems);
|
||||
|
||||
var path = Path.Join("tests", "file5");
|
||||
FileSystemTreeItemPresentationModel[] treeModel = service.GetAncestorModels(path, true);
|
||||
|
||||
Assert.IsNotEmpty(treeModel);
|
||||
Assert.AreEqual(treeModel.Length, 2);
|
||||
Assert.AreEqual(treeModel[0].Name, "tests");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_PathViewModels_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new ScriptTreeService(FileSystems);
|
||||
|
||||
FileSystemTreeItemPresentationModel[] treeModels = service.GetPathViewModels(string.Empty, 0, Int32.MaxValue, out var totalItems);
|
||||
|
||||
Assert.IsNotEmpty(treeModels);
|
||||
Assert.AreEqual(treeModels.Length, totalItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Management.Services.FileSystem;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services.Trees;
|
||||
|
||||
public class StyleSheetTreeServiceTests : FileSystemTreeServiceTestsBase
|
||||
{
|
||||
protected override string FileSystemPath => GlobalSettings.UmbracoCssPath;
|
||||
|
||||
protected override IFileSystem? GetStylesheetsFileSystem() => TestFileSystem;
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Siblings_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new StyleSheetTreeService(FileSystems);
|
||||
|
||||
FileSystemTreeItemPresentationModel[] treeModel = service.GetSiblingsViewModels("file5", 1, 1, out long before, out var after);
|
||||
int index = Array.FindIndex(treeModel, item => item.Name == "file5");
|
||||
|
||||
Assert.AreEqual(treeModel[index].Name, "file5");
|
||||
Assert.AreEqual(treeModel[index - 1].Name, "file4");
|
||||
Assert.AreEqual(treeModel[index + 1].Name, "file6");
|
||||
Assert.That(treeModel.Length == 3);
|
||||
Assert.AreEqual(after, 3);
|
||||
Assert.AreEqual(before, 4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Ancestors_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new StyleSheetTreeService(FileSystems);
|
||||
|
||||
var path = Path.Join("tests", "file5");
|
||||
FileSystemTreeItemPresentationModel[] treeModel = service.GetAncestorModels(path, true);
|
||||
|
||||
Assert.IsNotEmpty(treeModel);
|
||||
Assert.AreEqual(treeModel.Length, 2);
|
||||
Assert.AreEqual(treeModel[0].Name, "tests");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_PathViewModels_From_StyleSheet_Tree_Service()
|
||||
{
|
||||
var service = new StyleSheetTreeService(FileSystems);
|
||||
|
||||
FileSystemTreeItemPresentationModel[] treeModels = service.GetPathViewModels(string.Empty, 0, Int32.MaxValue, out var totalItems);
|
||||
|
||||
Assert.IsNotEmpty(treeModels);
|
||||
Assert.AreEqual(treeModels.Length, totalItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Net.Http.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Trees;
|
||||
|
||||
[TestFixture]
|
||||
internal sealed class DocumentTypeSiblingControllerTests : ManagementApiTest<SiblingsDocumentTypeTreeController>
|
||||
{
|
||||
private IContentTypeContainerService ContentTypeContainerService => GetRequiredService<IContentTypeContainerService>();
|
||||
|
||||
private ContentTypeService ContentTypeService => (ContentTypeService)GetRequiredService<IContentTypeService>();
|
||||
|
||||
protected override Expression<Func<SiblingsDocumentTypeTreeController, object>> MethodSelector =>
|
||||
x => x.Siblings(CancellationToken.None, Guid.Empty, 0, 0, false);
|
||||
|
||||
[Test]
|
||||
public async Task Document_Type_Siblings_Under_Folder_Have_Correct_Parent()
|
||||
{
|
||||
// create folder
|
||||
Attempt<EntityContainer, EntityContainerOperationStatus> folderResult =
|
||||
await ContentTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey);
|
||||
|
||||
// create contentTypeOne
|
||||
IContentType contentTypeOne = ContentTypeBuilder.CreateBasicContentType();
|
||||
contentTypeOne.Alias = "contentTypeOne";
|
||||
contentTypeOne.ParentId = folderResult.Result.Id;
|
||||
contentTypeOne.Variations = ContentVariation.Nothing;
|
||||
ContentTypeService.Save(contentTypeOne);
|
||||
|
||||
// create contentTypeTwo
|
||||
IContentType contentTypeTwo = ContentTypeBuilder.CreateBasicContentType();
|
||||
contentTypeTwo.Alias = "contentTypeTwo";
|
||||
contentTypeTwo.ParentId = folderResult.Result.Id;
|
||||
contentTypeTwo.Variations = ContentVariation.Nothing;
|
||||
ContentTypeService.Save(contentTypeTwo);
|
||||
|
||||
// get siblings of doctype one
|
||||
await AuthenticateClientAsync(Client, "test@test.test", "test@test.test", true);
|
||||
var siblingsResponse = await GetManagementApiResponseAsync(contentTypeOne.Key);
|
||||
var responseModel = await siblingsResponse.Content.ReadFromJsonAsync<SubsetViewModel<DocumentTreeItemResponseModel>>();
|
||||
|
||||
Assert.IsTrue(responseModel.Items.All(i => i.Parent!.Id == folderResult.Result.Key));
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> GetManagementApiResponseAsync(Guid target)
|
||||
{
|
||||
var url = GetManagementApiUrl<SiblingsDocumentTypeTreeController>(x =>
|
||||
x.Siblings(CancellationToken.None, target, 10, 10, false));
|
||||
return await Client.GetAsync(url);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Api.Delivery.Controllers.Content;
|
||||
@@ -132,7 +133,9 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
|
||||
}
|
||||
|
||||
|
||||
methodParams["version"] = method?.GetCustomAttribute<MapToApiVersionAttribute>()?.Versions?.First().MajorVersion.ToString();
|
||||
methodParams["version"] =
|
||||
method?.GetCustomAttribute<MapToApiVersionAttribute>()?.Versions?.First().MajorVersion.ToString() // get it from the attribute
|
||||
?? Factory.Services.GetRequiredService<IOptions<ApiVersioningOptions>>()?.Value.DefaultApiVersion.MajorVersion.ToString(); // or use the default version from options
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException(
|
||||
|
||||
@@ -398,4 +398,35 @@ internal sealed partial class UserServiceCrudTests
|
||||
Assert.IsNotNull(updatedUser.StartMediaIds);
|
||||
Assert.IsEmpty(updatedUser.StartMediaIds);
|
||||
}
|
||||
|
||||
[TestCase(false, false)]
|
||||
[TestCase(true, true)]
|
||||
public async Task Cannot_Remove_Admin_Group_From_Only_Admin_User(bool createAdditionalAdminUser, bool expectSuccess)
|
||||
{
|
||||
var userService = CreateUserService(securitySettings: new SecuritySettings { UsernameIsEmail = false });
|
||||
|
||||
if (createAdditionalAdminUser)
|
||||
{
|
||||
var (updateModel, _) = await CreateUserForUpdate(userService);
|
||||
updateModel.UserGroupKeys = new HashSet<Guid> { Constants.Security.AdminGroupKey };
|
||||
var updateResult = await userService.UpdateAsync(Constants.Security.SuperUserKey, updateModel);
|
||||
Assert.IsTrue(updateResult.Success);
|
||||
}
|
||||
|
||||
var adminUser = await userService.GetAsync(Constants.Security.SuperUserKey);
|
||||
var adminUserUpdateModel = await MapUserToUpdateModel(adminUser);
|
||||
adminUserUpdateModel.Email = "admin@test.com";
|
||||
adminUserUpdateModel.UserGroupKeys = new HashSet<Guid> { Constants.Security.EditorGroupKey };
|
||||
var adminUserUpdateResult = await userService.UpdateAsync(Constants.Security.SuperUserKey, adminUserUpdateModel);
|
||||
|
||||
if (expectSuccess)
|
||||
{
|
||||
Assert.IsTrue(adminUserUpdateResult.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsFalse(adminUserUpdateResult.Success);
|
||||
Assert.AreEqual(UserOperationStatus.AdminUserGroupMustNotBeEmpty, adminUserUpdateResult.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,6 +972,27 @@ internal sealed class EntityServiceTests : UmbracoIntegrationTest
|
||||
Assert.IsTrue(result[2].Key == children[3].Key);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EntityService_Siblings_Returns_Trashed_Siblings()
|
||||
{
|
||||
ContentService.EmptyRecycleBin();
|
||||
var children = CreateDocumentSiblingsTestData();
|
||||
|
||||
for (int i = 0; i <= 3; i++)
|
||||
{
|
||||
ContentService.MoveToRecycleBin(children[i]);
|
||||
}
|
||||
|
||||
var result = EntityService.GetTrashedSiblings(children[1].Key, [UmbracoObjectTypes.Document], 1, 1, out long totalBefore, out long totalAfter).ToArray();
|
||||
Assert.AreEqual(0, totalBefore);
|
||||
Assert.AreEqual(1, totalAfter);
|
||||
Assert.AreEqual(3, result.Length);
|
||||
Assert.IsTrue(result[0].Key == children[0].Key);
|
||||
Assert.IsTrue(result[1].Key == children[1].Key);
|
||||
Assert.IsTrue(result[2].Key == children[2].Key);
|
||||
Assert.IsFalse(result.Any(x => x.Key == children[3].Key));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EntityService_Siblings_SkipsFilteredEntities_UsingFilterWithSet()
|
||||
{
|
||||
|
||||
@@ -1019,6 +1019,42 @@ internal sealed class UserServiceTests : UmbracoIntegrationTest
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Assign_And_Get_Groups_For_User()
|
||||
{
|
||||
// Arrange
|
||||
var (user, userGroup1) = await CreateTestUserAndGroup();
|
||||
var userGroup2 = await CreateTestUserGroup("testGroup2", "Test Group 2");
|
||||
|
||||
// Act & Assert
|
||||
user = UserService.GetByUsername(user.Username);
|
||||
|
||||
Assert.IsNotNull(user);
|
||||
Assert.AreEqual(1, user.Groups.Count());
|
||||
Assert.AreEqual(userGroup1.Alias, user.Groups.First().Alias);
|
||||
|
||||
// - add second group
|
||||
user.AddGroup(userGroup2);
|
||||
UserService.Save(user);
|
||||
user = UserService.GetByUsername(user.Username);
|
||||
Assert.AreEqual(2, user.Groups.Count());
|
||||
|
||||
// - remove first group
|
||||
user.RemoveGroup(userGroup1.Alias);
|
||||
UserService.Save(user);
|
||||
user = UserService.GetByUsername(user.Username);
|
||||
Assert.AreEqual(1, user.Groups.Count());
|
||||
Assert.AreEqual(userGroup2.Alias, user.Groups.First().Alias);
|
||||
|
||||
// - remove second group and add first
|
||||
user.RemoveGroup(userGroup2.Alias);
|
||||
user.AddGroup(userGroup1.ToReadOnlyGroup());
|
||||
UserService.Save(user);
|
||||
user = UserService.GetByUsername(user.Username);
|
||||
Assert.AreEqual(1, user.Groups.Count());
|
||||
Assert.AreEqual(userGroup1.Alias, user.Groups.First().Alias);
|
||||
}
|
||||
|
||||
[TestCase(UserKind.Default, UserClientCredentialsOperationStatus.InvalidUser)]
|
||||
[TestCase(UserKind.Api, UserClientCredentialsOperationStatus.Success)]
|
||||
public async Task Can_Assign_ClientId_To_Api_User(UserKind userKind, UserClientCredentialsOperationStatus expectedResult)
|
||||
|
||||
@@ -26,8 +26,10 @@ public class DecimalPropertyValueEditorTests
|
||||
{ 123, 123m },
|
||||
{ -123, -123m },
|
||||
{ 123.45d, 123.45m },
|
||||
{ 123.45f, 123.45m },
|
||||
{ "123.45", 123.45m },
|
||||
{ "1234.56", 1234.56m },
|
||||
{ "1,234.56", 1234.56m },
|
||||
{ "123,45", 12345m },
|
||||
{ "1.234,56", null },
|
||||
{ "123 45", null },
|
||||
@@ -49,6 +51,18 @@ public class DecimalPropertyValueEditorTests
|
||||
}
|
||||
}
|
||||
|
||||
[SetCulture("it-IT")]
|
||||
[SetUICulture("it-IT")]
|
||||
[TestCase("123,45", 123.45)]
|
||||
[TestCase("1.234,56", 1234.56)]
|
||||
[TestCase("123.45", 12345)]
|
||||
[TestCase("1,234.56", null)]
|
||||
public void Can_Parse_Values_From_Editor_Using_Culture_With_Non_EnUs_Decimal_Separator(object value, decimal? expected)
|
||||
{
|
||||
var fromEditor = FromEditor(value);
|
||||
Assert.AreEqual(expected, fromEditor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Parse_Values_To_Editor()
|
||||
{
|
||||
|
||||
@@ -128,6 +128,7 @@ public class MultiValuePropertyEditorTests
|
||||
Assert.AreEqual("Item 3", result.Items[2]);
|
||||
}
|
||||
|
||||
[TestCase("", true, "")]
|
||||
[TestCase("Red", true, "")]
|
||||
[TestCase("Yellow", false, "notOneOfOptions")]
|
||||
[TestCase("Red,Green", true, "")]
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Templates;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers.Objects;
|
||||
|
||||
@@ -216,18 +217,204 @@ public class HtmlLocalLinkParserTests
|
||||
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor();
|
||||
|
||||
var umbracoContextFactory = TestUmbracoContextFactory.Create(
|
||||
umbracoContextAccessor: umbracoContextAccessor);
|
||||
|
||||
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
var contentCache = Mock.Get(reference.UmbracoContext.Content);
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(publishedContent.Object);
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent.Object);
|
||||
|
||||
var mediaCache = Mock.Get(reference.UmbracoContext.Media);
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
|
||||
|
||||
var publishedUrlProvider = CreatePublishedUrlProvider(
|
||||
contentUrlProvider,
|
||||
mediaUrlProvider,
|
||||
umbracoContextAccessor);
|
||||
|
||||
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
|
||||
|
||||
var output = linkParser.EnsureInternalLinks(input);
|
||||
|
||||
Assert.AreEqual(result, output);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseLocalLinks_WithUrlMode_RespectsUrlMode()
|
||||
{
|
||||
// Arrange
|
||||
var input = "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world";
|
||||
|
||||
// Setup content URL provider that returns different URLs based on UrlMode
|
||||
var contentUrlProvider = new Mock<IUrlProvider>();
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Relative,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Absolute,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("http://example.com/absolute-url"));
|
||||
|
||||
var contentType = new PublishedContentType(
|
||||
Guid.NewGuid(),
|
||||
666,
|
||||
"alias",
|
||||
PublishedItemType.Content,
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<PublishedPropertyType>(),
|
||||
ContentVariation.Nothing);
|
||||
var publishedContent = new Mock<IPublishedContent>();
|
||||
publishedContent.Setup(x => x.Id).Returns(1234);
|
||||
publishedContent.Setup(x => x.ContentType).Returns(contentType);
|
||||
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor();
|
||||
var umbracoContextFactory = TestUmbracoContextFactory.Create(
|
||||
umbracoContextAccessor: umbracoContextAccessor);
|
||||
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
|
||||
var navigationQueryService = new Mock<IDocumentNavigationQueryService>();
|
||||
// Guid? parentKey = null;
|
||||
// navigationQueryService.Setup(x => x.TryGetParentKey(It.IsAny<Guid>(), out parentKey)).Returns(true);
|
||||
IEnumerable<Guid> ancestorKeys = [];
|
||||
navigationQueryService.Setup(x => x.TryGetAncestorsKeys(It.IsAny<Guid>(), out ancestorKeys)).Returns(true);
|
||||
var publishedUrlProvider = CreatePublishedUrlProvider(
|
||||
contentUrlProvider,
|
||||
new Mock<IMediaUrlProvider>(),
|
||||
umbracoContextAccessor);
|
||||
|
||||
var publishedContentStatusFilteringService = new Mock<IPublishedContentStatusFilteringService>();
|
||||
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
var contentCache = Mock.Get(reference.UmbracoContext.Content);
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent.Object);
|
||||
|
||||
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
|
||||
|
||||
// Act
|
||||
var relativeOutput = linkParser.EnsureInternalLinks(input, UrlMode.Relative);
|
||||
var absoluteOutput = linkParser.EnsureInternalLinks(input, UrlMode.Absolute);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("hello href=\"/relative-url\" world", relativeOutput);
|
||||
Assert.AreEqual("hello href=\"http://example.com/absolute-url\" world", absoluteOutput);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(UrlMode.Default, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Relative, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Absolute, "hello href=\"{localLink:1234}\" world ", "hello href=\"https://example.com/absolute-url\" world ")]
|
||||
[TestCase(UrlMode.Auto, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Default, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Relative, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Absolute, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"https://example.com/absolute-url\" world ")]
|
||||
[TestCase(UrlMode.Auto, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Default, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
|
||||
[TestCase(UrlMode.Relative, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
|
||||
[TestCase(UrlMode.Absolute, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"https://example.com/media/absolute/image.jpg\" world ")]
|
||||
[TestCase(UrlMode.Auto, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
|
||||
public void ParseLocalLinks_WithVariousUrlModes_ReturnsCorrectUrls(UrlMode urlMode, string input, string expectedResult)
|
||||
{
|
||||
// Setup content URL provider that returns different URLs based on UrlMode
|
||||
var contentUrlProvider = new Mock<IUrlProvider>();
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Default,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Relative,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Absolute,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("https://example.com/absolute-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Auto,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
|
||||
var contentType = new PublishedContentType(
|
||||
Guid.NewGuid(),
|
||||
666,
|
||||
"alias",
|
||||
PublishedItemType.Content,
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<PublishedPropertyType>(),
|
||||
ContentVariation.Nothing);
|
||||
var publishedContent = new Mock<IPublishedContent>();
|
||||
publishedContent.Setup(x => x.Id).Returns(1234);
|
||||
publishedContent.Setup(x => x.ContentType).Returns(contentType);
|
||||
|
||||
// Setup media URL provider that returns different URLs based on UrlMode
|
||||
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Default,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/media/relative/image.jpg"));
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Relative,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/media/relative/image.jpg"));
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Absolute,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("https://example.com/media/absolute/image.jpg"));
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Auto,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/media/relative/image.jpg"));
|
||||
|
||||
var mediaType = new PublishedContentType(
|
||||
Guid.NewGuid(),
|
||||
777,
|
||||
"image",
|
||||
PublishedItemType.Media,
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<PublishedPropertyType>(),
|
||||
ContentVariation.Nothing);
|
||||
var media = new Mock<IPublishedContent>();
|
||||
media.Setup(x => x.ContentType).Returns(mediaType);
|
||||
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor();
|
||||
var umbracoContextFactory = TestUmbracoContextFactory.Create(
|
||||
umbracoContextAccessor: umbracoContextAccessor);
|
||||
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
|
||||
var publishedUrlProvider = CreatePublishedUrlProvider(
|
||||
contentUrlProvider,
|
||||
mediaUrlProvider,
|
||||
umbracoContextAccessor);
|
||||
|
||||
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
@@ -239,25 +426,35 @@ public class HtmlLocalLinkParserTests
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
|
||||
|
||||
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
|
||||
publishStatusQueryService
|
||||
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
var publishedUrlProvider = new UrlProvider(
|
||||
umbracoContextAccessor,
|
||||
Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(() => new[] { contentUrlProvider.Object }),
|
||||
new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
|
||||
Mock.Of<IVariationContextAccessor>(),
|
||||
navigationQueryService.Object,
|
||||
publishedContentStatusFilteringService.Object);
|
||||
|
||||
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
|
||||
|
||||
var output = linkParser.EnsureInternalLinks(input);
|
||||
var output = linkParser.EnsureInternalLinks(input, urlMode);
|
||||
|
||||
Assert.AreEqual(result, output);
|
||||
Assert.AreEqual(expectedResult, output);
|
||||
}
|
||||
}
|
||||
|
||||
private static UrlProvider CreatePublishedUrlProvider(
|
||||
Mock<IUrlProvider> contentUrlProvider,
|
||||
Mock<IMediaUrlProvider> mediaUrlProvider,
|
||||
TestUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
var navigationQueryService = new Mock<IDocumentNavigationQueryService>();
|
||||
IEnumerable<Guid> ancestorKeys = [];
|
||||
navigationQueryService.Setup(x => x.TryGetAncestorsKeys(It.IsAny<Guid>(), out ancestorKeys)).Returns(true);
|
||||
|
||||
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
|
||||
publishStatusQueryService
|
||||
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
return new UrlProvider(
|
||||
umbracoContextAccessor,
|
||||
Options.Create(new WebRoutingSettings()),
|
||||
new UrlProviderCollection(() => new[] { contentUrlProvider.Object }),
|
||||
new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
|
||||
Mock.Of<IVariationContextAccessor>(),
|
||||
navigationQueryService.Object,
|
||||
new Mock<IPublishedContentStatusFilteringService>().Object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Dictionary;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -27,16 +28,14 @@ public class PropertyValidationServiceTests
|
||||
private void MockObjects(out PropertyValidationService validationService, out IDataType dt)
|
||||
{
|
||||
var dataTypeService = new Mock<IDataTypeService>();
|
||||
var dataType = Mock.Of<IDataType>(
|
||||
x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
|
||||
&& x.DatabaseType == ValueStorageType.Nvarchar
|
||||
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
var dataType = Mock.Of<IDataType>(x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
|
||||
&& x.DatabaseType == ValueStorageType.Nvarchar
|
||||
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
dataTypeService.Setup(x => x.GetDataType(It.IsAny<int>())).Returns(() => dataType);
|
||||
dt = dataType;
|
||||
|
||||
// new data editor that returns a TextOnlyValueEditor which will do the validation for the properties
|
||||
var dataEditor = Mock.Of<IDataEditor>(
|
||||
x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
var dataEditor = Mock.Of<IDataEditor>(x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny<object>()))
|
||||
.Returns(new CustomTextOnlyValueEditor(
|
||||
new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox),
|
||||
@@ -44,7 +43,15 @@ public class PropertyValidationServiceTests
|
||||
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
|
||||
Mock.Of<IIOHelper>()));
|
||||
|
||||
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor }));
|
||||
var languageService = new Mock<ILanguageService>();
|
||||
languageService
|
||||
.Setup(s => s.GetDefaultIsoCodeAsync())
|
||||
.ReturnsAsync(() => "en-US");
|
||||
|
||||
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => [dataEditor]));
|
||||
|
||||
var contentSettings = new Mock<IOptions<ContentSettings>>();
|
||||
contentSettings.Setup(x => x.Value).Returns(new ContentSettings());
|
||||
|
||||
validationService = new PropertyValidationService(
|
||||
propEditors,
|
||||
@@ -52,8 +59,8 @@ public class PropertyValidationServiceTests
|
||||
Mock.Of<ILocalizedTextService>(),
|
||||
new ValueEditorCache(),
|
||||
Mock.Of<ICultureDictionary>(),
|
||||
Mock.Of<ILanguageService>(),
|
||||
Mock.Of<IOptions<ContentSettings>>());
|
||||
languageService.Object,
|
||||
contentSettings.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -279,6 +286,23 @@ public class PropertyValidationServiceTests
|
||||
Assert.AreEqual(4, invalid.Length);
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase(24)]
|
||||
[TestCase("test")]
|
||||
[TestCase("{\"test\": true}")]
|
||||
public void ValidatePropertyValue_Always_Returns_No_Validation_Errors_For_Missing_Editor(object? value)
|
||||
{
|
||||
MockObjects(out var validationService, out _);
|
||||
|
||||
var p1 = new PropertyType(ShortStringHelper, "Missing.Alias", ValueStorageType.Ntext)
|
||||
{
|
||||
Variations = ContentVariation.Nothing,
|
||||
};
|
||||
|
||||
var result = validationService.ValidatePropertyValue(p1, value, PropertyValidationContext.Empty());
|
||||
Assert.AreEqual(0, result.Count());
|
||||
}
|
||||
|
||||
// used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed
|
||||
// in to create the Requried and Regex validators so we aren't using singletons
|
||||
private class CustomTextOnlyValueEditor : TextOnlyValueEditor
|
||||
|
||||
Reference in New Issue
Block a user