Merge branch 'v16/dev' into v15/feature/select-segment

This commit is contained in:
Mads Rasmussen
2025-04-16 09:29:28 +02:00
2043 changed files with 46559 additions and 37407 deletions

View File

@@ -78,6 +78,20 @@
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentNavigationServiceTests.Bin_Structure_Can_Rebuild</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.DocumentNavigationServiceTests.Structure_Can_Rebuild</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Core.Services.UserServiceCrudTests.Cannot_Request_Disabled_If_Hidden(Umbraco.Cms.Core.Models.Membership.UserState)</Target>
@@ -92,6 +106,13 @@
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.EntityServiceTests.CreateTestData</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.MemberEditingServiceTests.Cannot_Change_IsApproved_Without_Access</Target>
@@ -113,4 +134,11 @@
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.TrackedReferencesServiceTests.Does_not_return_references_if_item_is_not_referenced</Target>
<Left>lib/net9.0/Umbraco.Tests.Integration.dll</Left>
<Right>lib/net9.0/Umbraco.Tests.Integration.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>

View File

@@ -108,7 +108,6 @@ public static class UmbracoBuilderExtensions
/// </summary>
private static ILocalizedTextService GetLocalizedTextService(IServiceProvider factory)
{
var globalSettings = factory.GetRequiredService<IOptions<GlobalSettings>>();
var loggerFactory = factory.GetRequiredService<ILoggerFactory>();
var appCaches = factory.GetRequiredService<AppCaches>();
@@ -133,7 +132,7 @@ public static class UmbracoBuilderExtensions
uiProject.Create();
}
var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, Constants.System.DefaultUmbracoPath.TrimStart("~/"), "config", "lang"));
var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, Constants.System.DefaultUmbracoPath.TrimStart(Constants.CharArrays.TildeForwardSlash), "config", "lang"));
return new LocalizedTextServiceFileSources(
loggerFactory.CreateLogger<LocalizedTextServiceFileSources>(),

View File

@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Mapping.Permissions;
@@ -7,6 +7,7 @@ using Umbraco.Cms.Api.Management.ViewModels.UserGroup;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership.Permissions;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.ContentTypeEditing;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -20,11 +21,14 @@ namespace Umbraco.Cms.Tests.Integration.ManagementApi.Factories;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
internal sealed class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
{
public IUserGroupPresentationFactory UserGroupPresentationFactory => GetRequiredService<IUserGroupPresentationFactory>();
public IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
public ITemplateService TemplateService => GetRequiredService<ITemplateService>();
public IContentTypeEditingService ContentTypeEditingService => GetRequiredService<IContentTypeEditingService>();
public IContentEditingService ContentEditingService => GetRequiredService<IContentEditingService>();
@@ -33,27 +37,27 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
{
services.AddTransient<IUserGroupPresentationFactory, UserGroupPresentationFactory>();
services.AddSingleton<IPermissionPresentationFactory, PermissionPresentationFactory>();
services.AddSingleton<DocumentPermissionMapper>();
services.AddSingleton<IPermissionMapper>(x=>x.GetRequiredService<DocumentPermissionMapper>());
services.AddSingleton<IPermissionPresentationMapper>(x=>x.GetRequiredService<DocumentPermissionMapper>());
services.AddSingleton<IPermissionMapper, DocumentPermissionMapper>();
services.AddSingleton<IPermissionPresentationMapper, DocumentPermissionMapper>();
services.AddSingleton<IPermissionMapper, DocumentPropertyValuePermissionMapper>();
services.AddSingleton<IPermissionPresentationMapper, DocumentPropertyValuePermissionMapper>();
}
[Test]
public async Task Can_Map_Create_Model_And_Create()
{
var updateModel = new CreateUserGroupRequestModel()
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new [] {"Umb.Section.Content"},
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>()
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
@@ -70,6 +74,39 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
[Test]
public async Task Cannot_Create_UserGroup_With_Unexisting_Document_Reference()
{
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>()
{
new DocumentPermissionPresentationModel()
{
Document = new ReferenceByIdModel(Guid.NewGuid()),
Verbs = new HashSet<string>()
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Assert.Multiple(() =>
{
Assert.IsFalse(userGroupCreateAttempt.Success);
Assert.AreEqual(UserGroupOperationStatus.DocumentPermissionKeyNotFound, userGroupCreateAttempt.Status);
});
}
[Test]
public async Task Cannot_Create_UserGroup_With_Unexisting_DocumentType_Reference()
{
var updateModel = new CreateUserGroupRequestModel()
{
@@ -78,12 +115,13 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new [] {"Umb.Section.Content"},
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>()
{
new DocumentPermissionPresentationModel()
new DocumentPropertyValuePermissionPresentationModel()
{
Document = new ReferenceByIdModel(Guid.NewGuid()),
DocumentType = new ReferenceByIdModel(Guid.NewGuid()),
PropertyType = new ReferenceByIdModel(Guid.NewGuid()),
Verbs = new HashSet<string>()
}
}
@@ -97,23 +135,23 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
Assert.Multiple(() =>
{
Assert.IsFalse(userGroupCreateAttempt.Success);
Assert.AreEqual(UserGroupOperationStatus.DocumentPermissionKeyNotFound, userGroupCreateAttempt.Status);
Assert.AreEqual(UserGroupOperationStatus.DocumentTypePermissionKeyNotFound, userGroupCreateAttempt.Status);
});
}
[Test]
public async Task Can_Create_Usergroup_With_Empty_Granluar_Permissions_For_Document()
public async Task Can_Create_Usergroup_With_Empty_Granular_Permissions_For_Document()
{
var contentKey = await CreateContent();
var updateModel = new CreateUserGroupRequestModel()
var createModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new [] {"Umb.Section.Content"},
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPermissionPresentationModel()
@@ -124,7 +162,7 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var attempt = await UserGroupPresentationFactory.CreateAsync(createModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
@@ -140,6 +178,170 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
});
}
[Test]
public async Task Can_Create_Usergroup_With_Granular_Permissions_For_Document_PropertyValue()
{
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
var contentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var propertyType = contentType.PropertyTypes.First();
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType.Key),
PropertyType = new ReferenceByIdModel(propertyType.Key),
Verbs = new HashSet<string>(["Some", "Another"])
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
var userGroup = userGroupCreateAttempt.Result;
Assert.Multiple(() =>
{
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.IsNotNull(userGroup);
});
Assert.AreEqual(2, userGroup.GranularPermissions.Count);
var documentTypeGranularPermissions = userGroup.GranularPermissions.OfType<DocumentPropertyValueGranularPermission>().ToArray();
Assert.AreEqual(2, documentTypeGranularPermissions.Length);
Assert.Multiple(() =>
{
Assert.IsTrue(documentTypeGranularPermissions.All(x => x.Key == contentType.Key));
Assert.AreEqual($"{propertyType.Key}|Some", documentTypeGranularPermissions.First().Permission);
Assert.AreEqual($"{propertyType.Key}|Another", documentTypeGranularPermissions.Last().Permission);
});
}
[Test]
public async Task Can_Create_Usergroup_With_Granular_Permissions_For_Document_PropertyValue_Without_Verbs()
{
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
var contentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var propertyType = contentType.PropertyTypes.First();
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType.Key),
PropertyType = new ReferenceByIdModel(propertyType.Key),
Verbs = new HashSet<string>()
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
Assert.IsTrue(attempt.Success);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
var userGroup = userGroupCreateAttempt.Result;
Assert.Multiple(() =>
{
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.IsNotNull(userGroup);
});
Assert.AreEqual(1, userGroup.GranularPermissions.Count);
var documentTypeGranularPermissions = userGroup.GranularPermissions.OfType<DocumentPropertyValueGranularPermission>().ToArray();
Assert.AreEqual(1, documentTypeGranularPermissions.Length);
Assert.Multiple(() =>
{
Assert.IsTrue(documentTypeGranularPermissions.All(x => x.Key == contentType.Key));
Assert.AreEqual($"{propertyType.Key}|", documentTypeGranularPermissions.First().Permission);
});
}
[Test]
public async Task Usergroup_Granular_Permissions_For_Document_PropertyValue_Are_Cleaned_Up_When_DocumentType_Is_Deleted()
{
var template = TemplateBuilder.CreateTextPageTemplate("defaultTemplate");
await TemplateService.CreateAsync(template, Constants.Security.SuperUserKey);
var contentType1 = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var contentType2 = (await ContentTypeEditingService.CreateAsync(
ContentTypeEditingBuilder.CreateSimpleContentType(alias: "anotherAlias", defaultTemplateKey: template.Key),
Constants.Security.SuperUserKey)).Result!;
var propertyType1 = contentType1.PropertyTypes.First();
var propertyType2 = contentType2.PropertyTypes.First();
var updateModel = new CreateUserGroupRequestModel()
{
Alias = "testAlias",
FallbackPermissions = new HashSet<string>(),
HasAccessToAllLanguages = true,
Languages = new List<string>(),
Name = "Test Name",
Sections = new[] { "Umb.Section.Content" },
Permissions = new HashSet<IPermissionPresentationModel>
{
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType1.Key),
PropertyType = new ReferenceByIdModel(propertyType1.Key),
Verbs = new HashSet<string>(["Some", "Another"])
},
new DocumentPropertyValuePermissionPresentationModel
{
DocumentType = new ReferenceByIdModel(contentType2.Key),
PropertyType = new ReferenceByIdModel(propertyType2.Key),
Verbs = new HashSet<string>(["Even", "More"])
}
}
};
var attempt = await UserGroupPresentationFactory.CreateAsync(updateModel);
var userGroupCreateAttempt = await UserGroupService.CreateAsync(attempt.Result, Constants.Security.SuperUserKey);
Assert.IsTrue(userGroupCreateAttempt.Success);
Assert.AreEqual(4, userGroupCreateAttempt.Result!.GranularPermissions.Count);
var deleteResult = await GetRequiredService<IContentTypeService>().DeleteAsync(contentType1.Key, Constants.Security.SuperUserKey);
Assert.AreEqual(ContentTypeOperationStatus.Success, deleteResult);
var userGroup = await UserGroupService.GetAsync(userGroupCreateAttempt.Result!.Key);
Assert.IsNotNull(userGroup);
Assert.AreEqual(2, userGroup.GranularPermissions.Count);
}
private async Task<Guid> CreateContent()
{
// NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested.
@@ -151,7 +353,8 @@ public class UserGroupPresentationFactoryTests : UmbracoIntegrationTest
Assert.IsTrue(contentTypeAttempt.Success);
var contentTypeResult = contentTypeAttempt.Result;
var contentTypeUpdateModel = ContentTypeUpdateHelper.CreateContentTypeUpdateModel(contentTypeResult); contentTypeUpdateModel.AllowedContentTypes = new[]
var contentTypeUpdateModel = ContentTypeUpdateHelper.CreateContentTypeUpdateModel(contentTypeResult);
contentTypeUpdateModel.AllowedContentTypes = new[]
{
new ContentTypeSort(contentTypeResult.Key, 0, contentTypeCreateModel.Alias),
};

View File

@@ -0,0 +1,291 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Mapping.Permissions;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.UserGroup.Permissions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Models.Membership.Permissions;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Factories;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class UserPresentationFactoryTests : UmbracoIntegrationTestWithContent
{
public IUserPresentationFactory UserPresentationFactory => GetRequiredService<IUserPresentationFactory>();
public IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
public IUserService UserService => GetRequiredService<IUserService>();
public ILanguageService LanguageService => GetRequiredService<ILanguageService>();
public IMediaService MediaService => GetRequiredService<IMediaService>();
protected override void ConfigureTestServices(IServiceCollection services)
{
services.AddTransient<IUserPresentationFactory, UserPresentationFactory>();
services.AddTransient<IUserGroupPresentationFactory, UserGroupPresentationFactory>();
services.AddSingleton<IAbsoluteUrlBuilder, DefaultAbsoluteUrlBuilder>();
services.AddSingleton<IUrlAssembler, DefaultUrlAssembler>();
services.AddSingleton<IPasswordConfigurationPresentationFactory, PasswordConfigurationPresentationFactory>();
services.AddSingleton<IPermissionPresentationFactory, PermissionPresentationFactory>();
services.AddSingleton<IPermissionMapper, DocumentPermissionMapper>();
services.AddSingleton<IPermissionPresentationMapper, DocumentPermissionMapper>();
services.AddSingleton<IPermissionMapper, DocumentPropertyValuePermissionMapper>();
services.AddSingleton<IPermissionPresentationMapper, DocumentPropertyValuePermissionMapper>();
}
[Test]
public async Task Can_Create_Current_User_Response_Model()
{
var daLanguage = new LanguageBuilder()
.WithCultureInfo("da-DK")
.Build();
await LanguageService.CreateAsync(daLanguage, Constants.Security.SuperUserKey);
var enUsLanguage = await LanguageService.GetAsync("en-US");
var daDkLanguage = await LanguageService.GetAsync("da-DK");
var rootMediaFolder = MediaService.CreateMedia("Pictures Folder", Constants.System.Root, "Folder");
MediaService.Save(rootMediaFolder);
var groupOne = await CreateUserGroup(
"Group One",
"groupOne",
[enUsLanguage.Id],
[],
[],
rootMediaFolder.Id);
var groupTwo = await CreateUserGroup(
"Group Two",
"groupTwo",
[daDkLanguage.Id],
[],
[],
rootMediaFolder.Id);
var user = await CreateUser([groupOne.Key, groupTwo.Key]);
var model = await UserPresentationFactory.CreateCurrentUserResponseModelAsync(user);
Assert.AreEqual(user.Key, model.Id);
Assert.AreEqual("test@test.com", model.Email);
Assert.AreEqual("Test User", model.Name);
Assert.AreEqual("test@test.com", model.UserName);
Assert.AreEqual(2, model.UserGroupIds.Count);
Assert.IsTrue(model.UserGroupIds.Select(x => x.Id).ContainsAll([groupOne.Key, groupTwo.Key]));
Assert.IsFalse(model.HasAccessToAllLanguages);
Assert.AreEqual(2, model.Languages.Count());
Assert.IsTrue(model.Languages.ContainsAll(["en-US", "da-DK"]));
Assert.IsTrue(model.HasDocumentRootAccess);
Assert.AreEqual(0, model.DocumentStartNodeIds.Count);
Assert.IsFalse(model.HasMediaRootAccess);
Assert.AreEqual(1, model.MediaStartNodeIds.Count);
Assert.AreEqual(rootMediaFolder.Key, model.MediaStartNodeIds.First().Id);
Assert.IsFalse(model.HasAccessToSensitiveData);
}
[Test]
public async Task Can_Create_Current_User_Response_Model_With_Aggregated_Document_Permissions()
{
var rootContentKey = Guid.Parse(TextpageKey);
var subPageContentKey = Guid.Parse(SubPageKey);
var subPage2ContentKey = Guid.Parse(SubPage2Key);
var rootMediaFolder = MediaService.CreateMedia("Pictures Folder", Constants.System.Root, "Folder");
MediaService.Save(rootMediaFolder);
var groupOne = await CreateUserGroup(
"Group One",
"groupOne",
[],
["A", "B", "C"],
[
new DocumentGranularPermission
{
Key = rootContentKey,
Permission = "A",
},
new DocumentGranularPermission
{
Key = rootContentKey,
Permission = "E",
},
new DocumentGranularPermission
{
Key = subPageContentKey,
Permission = "F",
},
new DocumentGranularPermission
{
Key = subPage2ContentKey,
Permission = "F",
}
],
rootMediaFolder.Id);
var groupTwo = await CreateUserGroup(
"Group Two",
"groupTwo",
[],
["A", "B", "D"],
[
new DocumentGranularPermission
{
Key = subPage2ContentKey,
Permission = "G",
},
new DocumentGranularPermission
{
Key = subPage2ContentKey,
Permission = "H",
}
],
rootMediaFolder.Id);
var user = await CreateUser([groupOne.Key, groupTwo.Key]);
var model = await UserPresentationFactory.CreateCurrentUserResponseModelAsync(user);
Assert.AreEqual(4, model.FallbackPermissions.Count);
Assert.IsTrue(model.FallbackPermissions.ContainsAll(["A", "B", "C", "D"]));
// When aggregated, we expect one permission per document (we have several granular permissions assigned, for three unique documents).
Assert.AreEqual(3, model.Permissions.Count);
// User has two user groups, one of which provides granular permissions for the root content item.
// As such we expect the aggregated permissions to be the union of the specific permissions coming from the user group with them assigned to the document,
// and the fallback permissions from the other.
var rootContentPermissions = model.Permissions.Cast<DocumentPermissionPresentationModel>().Single(x => x.Document.Id == rootContentKey);
Assert.AreEqual(4, rootContentPermissions.Verbs.Count);
Assert.IsTrue(rootContentPermissions.Verbs.ContainsAll(["A", "B", "D", "E"]));
// The sub-page and it's parent have specific granular permissions from one user group.
// So we expect the aggregated permissions to include those from the sub-page and the other user's groups fallback permissions.
var subPageContentPermissions = model.Permissions.Cast<DocumentPermissionPresentationModel>().Single(x => x.Document.Id == subPageContentKey);
Assert.AreEqual(4, subPageContentPermissions.Verbs.Count);
Assert.IsTrue(subPageContentPermissions.Verbs.ContainsAll(["A", "B", "D", "F"]));
// Both user groups provide granular permissions for the second sub-page content item.
// Here we expect the aggregated permissions to be the union of the granular permissions on the document from both user groups.
var subPage2ContentPermissions = model.Permissions.Cast<DocumentPermissionPresentationModel>().Single(x => x.Document.Id == subPage2ContentKey);
Assert.AreEqual(3, subPage2ContentPermissions.Verbs.Count);
Assert.IsTrue(subPage2ContentPermissions.Verbs.ContainsAll(["F", "G", "H"]));
}
[Test]
public async Task Can_Create_Current_User_Response_Model_With_Aggregated_Document_Property_Value_Permissions()
{
var propertyTypeKey = Guid.NewGuid();
var propertyTypeKey2 = Guid.NewGuid();
var groupOne = await CreateUserGroup(
"Group One",
"groupOne",
[],
[],
[
new DocumentGranularPermission
{
Key = Guid.Parse(TextpageKey),
Permission = "A",
},
new DocumentPropertyValueGranularPermission
{
Key = ContentType.Key,
Permission = $"{propertyTypeKey}|C",
},
new DocumentPropertyValueGranularPermission
{
Key = ContentType.Key,
Permission = $"{propertyTypeKey2}|D",
},
],
Constants.System.Root);
var groupTwo = await CreateUserGroup(
"Group Two",
"groupTwo",
[],
[],
[
new DocumentPropertyValueGranularPermission
{
Key = ContentType.Key,
Permission = $"{propertyTypeKey}|B",
},
],
Constants.System.Root);
var user = await CreateUser([groupOne.Key, groupTwo.Key]);
var model = await UserPresentationFactory.CreateCurrentUserResponseModelAsync(user);
Assert.AreEqual(3, model.Permissions.Count);
var documentPermissions = model.Permissions
.Where(x => x is DocumentPermissionPresentationModel)
.Cast<DocumentPermissionPresentationModel>()
.Single(x => x.Document.Id == Guid.Parse(TextpageKey));
Assert.AreEqual(1, documentPermissions.Verbs.Count);
Assert.IsTrue(documentPermissions.Verbs.ContainsAll(["A"]));
var documentPropertyValuePermissions = model.Permissions
.Where(x => x is DocumentPropertyValuePermissionPresentationModel)
.Cast<DocumentPropertyValuePermissionPresentationModel>()
.Where(x => x.DocumentType.Id == ContentType.Key);
Assert.AreEqual(2, documentPropertyValuePermissions.Count());
var propertyTypePermission1 = documentPropertyValuePermissions
.Single(x => x.PropertyType.Id == propertyTypeKey);
Assert.AreEqual(2, propertyTypePermission1.Verbs.Count);
Assert.IsTrue(propertyTypePermission1.Verbs.ContainsAll(["B", "C"]));
var propertyTypePermission2 = documentPropertyValuePermissions
.Single(x => x.PropertyType.Id == propertyTypeKey2);
Assert.AreEqual(1, propertyTypePermission2.Verbs.Count);
Assert.IsTrue(propertyTypePermission2.Verbs.ContainsAll(["D"]));
}
private async Task<IUserGroup> CreateUserGroup(
string name,
string alias,
int[] allowedLanguages,
string[] permissions,
INodeGranularPermission[] granularPermissions,
int startMediaId)
{
var userGroup = new UserGroupBuilder()
.WithName(name)
.WithAlias(alias)
.WithAllowedLanguages(allowedLanguages)
.WithStartMediaId(startMediaId)
.WithPermissions(permissions.ToHashSet())
.WithGranularPermissions(granularPermissions)
.Build();
var createUserGroupResult = await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
Assert.IsTrue(createUserGroupResult.Success);
return userGroup;
}
private async Task<IUser> CreateUser(Guid[] userGroupKeys)
{
var createUserAttempt = await UserService.CreateAsync(Constants.Security.SuperUserKey, new UserCreateModel
{
Email = "test@test.com",
Name = "Test User",
UserName = "test@test.com",
UserGroupKeys = userGroupKeys.ToHashSet(),
});
Assert.IsTrue(createUserAttempt.Success);
return await UserService.GetAsync(createUserAttempt.Result.CreatedUser.Key);
}
}

View File

@@ -31,11 +31,12 @@ public abstract class ManagementApiTest<T> : UmbracoTestServerTestBase
where T : ManagementApiControllerBase
{
[SetUp]
public async Task Setup()
public Task Setup()
{
Client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
return Task.CompletedTask;
}
protected override void CustomTestAuthSetup(IServiceCollection services)

View File

@@ -9,13 +9,13 @@ namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies;
///
/// </summary>
[TestFixture]
public class AllCultureControllerTests : ManagementApiTest<AllCultureController>
internal sealed class AllCultureControllerTests : ManagementApiTest<AllCultureController>
{
protected override Expression<Func<AllCultureController, object>> MethodSelector =>
x => x.GetAll(CancellationToken.None, 0, 100);
[Test]
public virtual async Task As_Admin_I_Have_Access()
public async Task As_Admin_I_Have_Access()
{
await AuthenticateClientAsync(Client, "admin@umbraco.com", "1234567890", true);
@@ -25,7 +25,7 @@ public class AllCultureControllerTests : ManagementApiTest<AllCultureController>
}
[Test]
public virtual async Task As_Editor_I_Have_Access()
public async Task As_Editor_I_Have_Access()
{
await AuthenticateClientAsync(Client, "editor@umbraco.com", "1234567890", false);
@@ -35,7 +35,7 @@ public class AllCultureControllerTests : ManagementApiTest<AllCultureController>
}
[Test]
public virtual async Task Unauthourized_when_no_token_is_provided()
public async Task Unauthourized_when_no_token_is_provided()
{
var response = await Client.GetAsync(Url);

View File

@@ -18,7 +18,7 @@ using Umbraco.Cms.Tests.Common.TestHelpers;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies;
[TestFixture]
public class CreateDocumentTests : ManagementApiTest<CreateDocumentController>
internal sealed class CreateDocumentTests : ManagementApiTest<CreateDocumentController>
{
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();

View File

@@ -8,7 +8,6 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
@@ -19,7 +18,7 @@ using Umbraco.Cms.Tests.Common.TestHelpers;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Policies;
public class UpdateDocumentTests : ManagementApiTest<UpdateDocumentController>
internal sealed class UpdateDocumentTests : ManagementApiTest<UpdateDocumentController>
{
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();

View File

@@ -1,4 +1,4 @@
using System.Linq.Expressions;
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.Preview;
@@ -6,14 +6,14 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Preview;
public class EndPreviewTests : ManagementApiTest<EndPreviewController>
internal sealed class EndPreviewTests : ManagementApiTest<EndPreviewController>
{
protected override Expression<Func<EndPreviewController, object>> MethodSelector =>
x => x.End(CancellationToken.None);
[Test]
public virtual async Task As_Anonymous_I_Can_End_Preview_Mode()
public async Task As_Anonymous_I_Can_End_Preview_Mode()
{
var response = await Client.DeleteAsync(Url);

View File

@@ -1,4 +1,4 @@
using System.Linq.Expressions;
using System.Linq.Expressions;
using System.Net;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.Preview;
@@ -6,14 +6,14 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Preview;
public class EnterPreviewTests : ManagementApiTest<EnterPreviewController>
internal sealed class EnterPreviewTests : ManagementApiTest<EnterPreviewController>
{
protected override Expression<Func<EnterPreviewController, object>> MethodSelector =>
x => x.Enter(CancellationToken.None);
[Test]
public virtual async Task As_Editor_I_Can_Enter_Preview_Mode()
public async Task As_Editor_I_Can_Enter_Preview_Mode()
{
await AuthenticateClientAsync(Client, "admin@umbraco.com", "1234567890", false);

View File

@@ -0,0 +1,85 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services;
public partial class UserStartNodeEntitiesServiceMediaTests
{
[Test]
public async Task RootUserAccessEntities_FirstAndLastRoot_YieldsBoth_AsAllowed()
{
var contentStartNodeIds = await CreateUserAndGetStartNodeIds(_mediaByName["1"].Id, _mediaByName["5"].Id);
var roots = UserStartNodeEntitiesService
.RootUserAccessEntities(
UmbracoObjectTypes.Media,
contentStartNodeIds)
.ToArray();
// expected total is 2, because only two items at root ("1" amd "10") are allowed
Assert.AreEqual(2, roots.Length);
Assert.Multiple(() =>
{
// first and last content items are the ones allowed
Assert.AreEqual(_mediaByName["1"].Key, roots[0].Entity.Key);
Assert.AreEqual(_mediaByName["5"].Key, roots[1].Entity.Key);
// explicitly verify the entity sort order, both so we know sorting works,
// and so we know it's actually the first and last item at root
Assert.AreEqual(0, roots[0].Entity.SortOrder);
Assert.AreEqual(4, roots[1].Entity.SortOrder);
// both are allowed (they are the actual start nodes)
Assert.IsTrue(roots[0].HasAccess);
Assert.IsTrue(roots[1].HasAccess);
});
}
[Test]
public async Task RootUserAccessEntities_ChildrenAsStartNode_YieldsChildRoots_AsNotAllowed()
{
var contentStartNodeIds = await CreateUserAndGetStartNodeIds(_mediaByName["1-3"].Id, _mediaByName["3-3"].Id, _mediaByName["5-3"].Id);
var roots = UserStartNodeEntitiesService
.RootUserAccessEntities(
UmbracoObjectTypes.Media,
contentStartNodeIds)
.ToArray();
Assert.AreEqual(3, roots.Length);
Assert.Multiple(() =>
{
// the three start nodes are the children of the "1", "3" and "5" roots, respectively, so these are expected as roots
Assert.AreEqual(_mediaByName["1"].Key, roots[0].Entity.Key);
Assert.AreEqual(_mediaByName["3"].Key, roots[1].Entity.Key);
Assert.AreEqual(_mediaByName["5"].Key, roots[2].Entity.Key);
// all are disallowed - only the children (the actual start nodes) are allowed
Assert.IsTrue(roots.All(r => r.HasAccess is false));
});
}
[Test]
public async Task RootUserAccessEntities_GrandchildrenAsStartNode_YieldsGrandchildRoots_AsNotAllowed()
{
var contentStartNodeIds = await CreateUserAndGetStartNodeIds(_mediaByName["1-2-3"].Id, _mediaByName["2-3-4"].Id, _mediaByName["3-4-5"].Id);
var roots = UserStartNodeEntitiesService
.RootUserAccessEntities(
UmbracoObjectTypes.Media,
contentStartNodeIds)
.ToArray();
Assert.AreEqual(3, roots.Length);
Assert.Multiple(() =>
{
// the three start nodes are the grandchildren of the "1", "2" and "3" roots, respectively, so these are expected as roots
Assert.AreEqual(_mediaByName["1"].Key, roots[0].Entity.Key);
Assert.AreEqual(_mediaByName["2"].Key, roots[1].Entity.Key);
Assert.AreEqual(_mediaByName["3"].Key, roots[2].Entity.Key);
// all are disallowed - only the grandchildren (the actual start nodes) are allowed
Assert.IsTrue(roots.All(r => r.HasAccess is false));
});
}
}

View File

@@ -0,0 +1,336 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services;
public partial class UserStartNodeEntitiesServiceMediaTests
{
[Test]
public async Task ChildUserAccessEntities_FirstAndLastChildOfRoot_YieldsBothInFirstPage_AsAllowed()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["1-1"].Id, _mediaByName["1-10"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["1"].Key,
0,
3,
BySortOrder,
out var totalItems)
.ToArray();
// expected total is 2, because only two items under "1" are allowed (note the page size is 3 for good measure)
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// first and last media items are the ones allowed
Assert.AreEqual(_mediaByName["1-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["1-10"].Key, children[1].Entity.Key);
// explicitly verify the entity sort order, both so we know sorting works,
// and so we know it's actually the first and last item below "1"
Assert.AreEqual(0, children[0].Entity.SortOrder);
Assert.AreEqual(9, children[1].Entity.SortOrder);
// both are allowed (they are the actual start nodes)
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_InAndOutOfScope_YieldsOnlyChildrenInScope()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["1-5"].Id, _mediaByName["2-10"].Id);
Assert.AreEqual(2, mediaStartNodePaths.Length);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["2"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(1, totalItems);
Assert.AreEqual(1, children.Length);
Assert.Multiple(() =>
{
// only the "2-10" media item is returned, as "1-5" is out of scope
Assert.AreEqual(_mediaByName["2-10"].Key, children[0].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_OutOfScope_YieldsNothing()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["1-5"].Id, _mediaByName["2-10"].Id);
Assert.AreEqual(2, mediaStartNodePaths.Length);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(0, totalItems);
Assert.AreEqual(0, children.Length);
}
[Test]
public async Task ChildUserAccessEntities_SpanningMultipleResultPages_CanPaginate()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(
_mediaByName["1-1"].Id,
_mediaByName["1-3"].Id,
_mediaByName["1-5"].Id,
_mediaByName["1-7"].Id,
_mediaByName["1-9"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["1"].Key,
0,
2,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
// page size is 2
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_mediaByName["1-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["1-3"].Key, children[1].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
// next result page
children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["1"].Key,
2,
2,
BySortOrder,
out totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
// page size is still 2
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_mediaByName["1-5"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["1-7"].Key, children[1].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
// next result page
children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["1"].Key,
4,
2,
BySortOrder,
out totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
// page size is still 2, but this is the last result page
Assert.AreEqual(1, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_mediaByName["1-9"].Key, children[0].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_ChildrenAsStartNode_YieldsAllGrandchildren_AsAllowed()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["3-3"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3-3"].Key,
0,
100,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
Assert.AreEqual(5, children.Length);
Assert.Multiple(() =>
{
// all children of "3-3" should be allowed because "3-3" is a start node
foreach (var childNumber in Enumerable.Range(1, 5))
{
var child = children[childNumber - 1];
Assert.AreEqual(_mediaByName[$"3-3-{childNumber}"].Key, child.Entity.Key);
Assert.IsTrue(child.HasAccess);
}
});
}
[Test]
public async Task ChildUserAccessEntities_GrandchildrenAsStartNode_YieldsGrandchildren_AsAllowed()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["3-3-3"].Id, _mediaByName["3-3-4"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3-3"].Key,
0,
3,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// the two items are the children of "3-3" - that is, the actual start nodes
Assert.AreEqual(_mediaByName["3-3-3"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["3-3-4"].Key, children[1].Entity.Key);
// both are allowed (they are the actual start nodes)
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_GrandchildrenAsStartNode_YieldsChildren_AsNotAllowed()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["3-3-3"].Id, _mediaByName["3-5-3"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3"].Key,
0,
3,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// the two items are the children of "3" - that is, the parents of the actual start nodes
Assert.AreEqual(_mediaByName["3-3"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["3-5"].Key, children[1].Entity.Key);
// both are disallowed - only the two children (the actual start nodes) are allowed
Assert.IsFalse(children[0].HasAccess);
Assert.IsFalse(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_ChildAndGrandchildAsStartNode_AllowsOnlyGrandchild()
{
// NOTE: this test proves that start node paths are *not* additive when structural inheritance is in play.
// if one has a start node that is a descendant to another start node, the descendant start node "wins"
// and the ancestor start node is ignored. this differs somewhat from the norm; we normally consider
// permissions additive (which in this case would mean ignoring the descendant rather than the ancestor).
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["3-3"].Id, _mediaByName["3-3-1"].Id, _mediaByName["3-3-5"].Id);
Assert.AreEqual(2, mediaStartNodePaths.Length);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(1, totalItems);
Assert.AreEqual(1, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_mediaByName["3-3"].Key, children[0].Entity.Key);
Assert.IsFalse(children[0].HasAccess);
});
children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3-3"].Key,
0,
10,
BySortOrder,
out totalItems)
.ToArray();
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// the two items are the children of "3-3" - that is, the actual start nodes
Assert.AreEqual(_mediaByName["3-3-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["3-3-5"].Key, children[1].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_ReverseStartNodeOrder_DoesNotAffectResultOrder()
{
var mediaStartNodePaths = await CreateUserAndGetStartNodePaths(_mediaByName["3-3"].Id, _mediaByName["3-2"].Id, _mediaByName["3-1"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Media,
mediaStartNodePaths,
_mediaByName["3"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(3, totalItems);
Assert.AreEqual(3, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_mediaByName["3-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_mediaByName["3-2"].Key, children[1].Entity.Key);
Assert.AreEqual(_mediaByName["3-3"].Key, children[2].Entity.Key);
});
}
}

View File

@@ -0,0 +1,134 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public partial class UserStartNodeEntitiesServiceMediaTests : UmbracoIntegrationTest
{
private Dictionary<string, IMedia> _mediaByName = new ();
private IUserGroup _userGroup;
private IMediaService MediaService => GetRequiredService<IMediaService>();
private IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
private IUserService UserService => GetRequiredService<IUserService>();
private IEntityService EntityService => GetRequiredService<IEntityService>();
private IUserStartNodeEntitiesService UserStartNodeEntitiesService => GetRequiredService<IUserStartNodeEntitiesService>();
protected readonly Ordering BySortOrder = Ordering.By("sortOrder");
protected override void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureTestServices(services);
services.AddTransient<IUserStartNodeEntitiesService, UserStartNodeEntitiesService>();
}
[SetUp]
public async Task SetUpTestAsync()
{
if (_mediaByName.Any())
{
return;
}
var mediaType = new MediaTypeBuilder()
.WithAlias("theMediaType")
.Build();
mediaType.AllowedAsRoot = true;
await MediaTypeService.CreateAsync(mediaType, Constants.Security.SuperUserKey);
mediaType.AllowedContentTypes = [new() { Alias = mediaType.Alias, Key = mediaType.Key }];
await MediaTypeService.UpdateAsync(mediaType, Constants.Security.SuperUserKey);
foreach (var rootNumber in Enumerable.Range(1, 5))
{
var root = new MediaBuilder()
.WithMediaType(mediaType)
.WithName($"{rootNumber}")
.Build();
MediaService.Save(root);
_mediaByName[root.Name!] = root;
foreach (var childNumber in Enumerable.Range(1, 10))
{
var child = new MediaBuilder()
.WithMediaType(mediaType)
.WithName($"{rootNumber}-{childNumber}")
.Build();
child.SetParent(root);
MediaService.Save(child);
_mediaByName[child.Name!] = child;
foreach (var grandChildNumber in Enumerable.Range(1, 5))
{
var grandchild = new MediaBuilder()
.WithMediaType(mediaType)
.WithName($"{rootNumber}-{childNumber}-{grandChildNumber}")
.Build();
grandchild.SetParent(child);
MediaService.Save(grandchild);
_mediaByName[grandchild.Name!] = grandchild;
}
}
}
_userGroup = new UserGroupBuilder()
.WithAlias("theGroup")
.WithAllowedSections(["media"])
.Build();
_userGroup.StartMediaId = null;
await UserGroupService.CreateAsync(_userGroup, Constants.Security.SuperUserKey);
}
private async Task<string[]> CreateUserAndGetStartNodePaths(params int[] startNodeIds)
{
var user = await CreateUser(startNodeIds);
var mediaStartNodePaths = user.GetMediaStartNodePaths(EntityService, AppCaches.NoCache);
Assert.IsNotNull(mediaStartNodePaths);
return mediaStartNodePaths;
}
private async Task<int[]> CreateUserAndGetStartNodeIds(params int[] startNodeIds)
{
var user = await CreateUser(startNodeIds);
var mediaStartNodeIds = user.CalculateMediaStartNodeIds(EntityService, AppCaches.NoCache);
Assert.IsNotNull(mediaStartNodeIds);
return mediaStartNodeIds;
}
private async Task<User> CreateUser(int[] startNodeIds)
{
var user = new UserBuilder()
.WithName(Guid.NewGuid().ToString("N"))
.WithStartMediaIds(startNodeIds)
.Build();
UserService.Save(user);
var attempt = await UserGroupService.AddUsersToUserGroupAsync(
new UsersToUserGroupManipulationModel(_userGroup.Key, [user.Key]),
Constants.Security.SuperUserKey);
Assert.IsTrue(attempt.Success);
return user;
}
}

View File

@@ -0,0 +1,336 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services;
public partial class UserStartNodeEntitiesServiceTests
{
[Test]
public async Task ChildUserAccessEntities_FirstAndLastChildOfRoot_YieldsBothInFirstPage_AsAllowed()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["1-1"].Id, _contentByName["1-10"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["1"].Key,
0,
3,
BySortOrder,
out var totalItems)
.ToArray();
// expected total is 2, because only two items under "1" are allowed (note the page size is 3 for good measure)
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// first and last content items are the ones allowed
Assert.AreEqual(_contentByName["1-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["1-10"].Key, children[1].Entity.Key);
// explicitly verify the entity sort order, both so we know sorting works,
// and so we know it's actually the first and last item below "1"
Assert.AreEqual(0, children[0].Entity.SortOrder);
Assert.AreEqual(9, children[1].Entity.SortOrder);
// both are allowed (they are the actual start nodes)
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_InAndOutOfScope_YieldsOnlyChildrenInScope()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["1-5"].Id, _contentByName["2-10"].Id);
Assert.AreEqual(2, contentStartNodePaths.Length);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["2"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(1, totalItems);
Assert.AreEqual(1, children.Length);
Assert.Multiple(() =>
{
// only the "2-10" content item is returned, as "1-5" is out of scope
Assert.AreEqual(_contentByName["2-10"].Key, children[0].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_OutOfScope_YieldsNothing()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["1-5"].Id, _contentByName["2-10"].Id);
Assert.AreEqual(2, contentStartNodePaths.Length);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(0, totalItems);
Assert.AreEqual(0, children.Length);
}
[Test]
public async Task ChildUserAccessEntities_SpanningMultipleResultPages_CanPaginate()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(
_contentByName["1-1"].Id,
_contentByName["1-3"].Id,
_contentByName["1-5"].Id,
_contentByName["1-7"].Id,
_contentByName["1-9"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["1"].Key,
0,
2,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
// page size is 2
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_contentByName["1-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["1-3"].Key, children[1].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
// next result page
children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["1"].Key,
2,
2,
BySortOrder,
out totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
// page size is still 2
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_contentByName["1-5"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["1-7"].Key, children[1].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
// next result page
children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["1"].Key,
4,
2,
BySortOrder,
out totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
// page size is still 2, but this is the last result page
Assert.AreEqual(1, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_contentByName["1-9"].Key, children[0].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_ChildrenAsStartNode_YieldsAllGrandchildren_AsAllowed()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["3-3"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3-3"].Key,
0,
100,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(5, totalItems);
Assert.AreEqual(5, children.Length);
Assert.Multiple(() =>
{
// all children of "3-3" should be allowed because "3-3" is a start node
foreach (var childNumber in Enumerable.Range(1, 5))
{
var child = children[childNumber - 1];
Assert.AreEqual(_contentByName[$"3-3-{childNumber}"].Key, child.Entity.Key);
Assert.IsTrue(child.HasAccess);
}
});
}
[Test]
public async Task ChildUserAccessEntities_GrandchildrenAsStartNode_YieldsGrandchildren_AsAllowed()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["3-3-3"].Id, _contentByName["3-3-4"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3-3"].Key,
0,
3,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// the two items are the children of "3-3" - that is, the actual start nodes
Assert.AreEqual(_contentByName["3-3-3"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["3-3-4"].Key, children[1].Entity.Key);
// both are allowed (they are the actual start nodes)
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_GrandchildrenAsStartNode_YieldsChildren_AsNotAllowed()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["3-3-3"].Id, _contentByName["3-5-3"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3"].Key,
0,
3,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// the two items are the children of "3" - that is, the parents of the actual start nodes
Assert.AreEqual(_contentByName["3-3"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["3-5"].Key, children[1].Entity.Key);
// both are disallowed - only the two children (the actual start nodes) are allowed
Assert.IsFalse(children[0].HasAccess);
Assert.IsFalse(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_ChildAndGrandchildAsStartNode_AllowsOnlyGrandchild()
{
// NOTE: this test proves that start node paths are *not* additive when structural inheritance is in play.
// if one has a start node that is a descendant to another start node, the descendant start node "wins"
// and the ancestor start node is ignored. this differs somewhat from the norm; we normally consider
// permissions additive (which in this case would mean ignoring the descendant rather than the ancestor).
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["3-3"].Id, _contentByName["3-3-1"].Id, _contentByName["3-3-5"].Id);
Assert.AreEqual(2, contentStartNodePaths.Length);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(1, totalItems);
Assert.AreEqual(1, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_contentByName["3-3"].Key, children[0].Entity.Key);
Assert.IsFalse(children[0].HasAccess);
});
children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3-3"].Key,
0,
10,
BySortOrder,
out totalItems)
.ToArray();
Assert.AreEqual(2, totalItems);
Assert.AreEqual(2, children.Length);
Assert.Multiple(() =>
{
// the two items are the children of "3-3" - that is, the actual start nodes
Assert.AreEqual(_contentByName["3-3-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["3-3-5"].Key, children[1].Entity.Key);
Assert.IsTrue(children[0].HasAccess);
Assert.IsTrue(children[1].HasAccess);
});
}
[Test]
public async Task ChildUserAccessEntities_ReverseStartNodeOrder_DoesNotAffectResultOrder()
{
var contentStartNodePaths = await CreateUserAndGetStartNodePaths(_contentByName["3-3"].Id, _contentByName["3-2"].Id, _contentByName["3-1"].Id);
var children = UserStartNodeEntitiesService
.ChildUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodePaths,
_contentByName["3"].Key,
0,
10,
BySortOrder,
out var totalItems)
.ToArray();
Assert.AreEqual(3, totalItems);
Assert.AreEqual(3, children.Length);
Assert.Multiple(() =>
{
Assert.AreEqual(_contentByName["3-1"].Key, children[0].Entity.Key);
Assert.AreEqual(_contentByName["3-2"].Key, children[1].Entity.Key);
Assert.AreEqual(_contentByName["3-3"].Key, children[2].Entity.Key);
});
}
}

View File

@@ -0,0 +1,85 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services;
public partial class UserStartNodeEntitiesServiceTests
{
[Test]
public async Task RootUserAccessEntities_FirstAndLastRoot_YieldsBoth_AsAllowed()
{
var contentStartNodeIds = await CreateUserAndGetStartNodeIds(_contentByName["1"].Id, _contentByName["5"].Id);
var roots = UserStartNodeEntitiesService
.RootUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodeIds)
.ToArray();
// expected total is 2, because only two items at root ("1" amd "10") are allowed
Assert.AreEqual(2, roots.Length);
Assert.Multiple(() =>
{
// first and last content items are the ones allowed
Assert.AreEqual(_contentByName["1"].Key, roots[0].Entity.Key);
Assert.AreEqual(_contentByName["5"].Key, roots[1].Entity.Key);
// explicitly verify the entity sort order, both so we know sorting works,
// and so we know it's actually the first and last item at root
Assert.AreEqual(0, roots[0].Entity.SortOrder);
Assert.AreEqual(4, roots[1].Entity.SortOrder);
// both are allowed (they are the actual start nodes)
Assert.IsTrue(roots[0].HasAccess);
Assert.IsTrue(roots[1].HasAccess);
});
}
[Test]
public async Task RootUserAccessEntities_ChildrenAsStartNode_YieldsChildRoots_AsNotAllowed()
{
var contentStartNodeIds = await CreateUserAndGetStartNodeIds(_contentByName["1-3"].Id, _contentByName["3-3"].Id, _contentByName["5-3"].Id);
var roots = UserStartNodeEntitiesService
.RootUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodeIds)
.ToArray();
Assert.AreEqual(3, roots.Length);
Assert.Multiple(() =>
{
// the three start nodes are the children of the "1", "3" and "5" roots, respectively, so these are expected as roots
Assert.AreEqual(_contentByName["1"].Key, roots[0].Entity.Key);
Assert.AreEqual(_contentByName["3"].Key, roots[1].Entity.Key);
Assert.AreEqual(_contentByName["5"].Key, roots[2].Entity.Key);
// all are disallowed - only the children (the actual start nodes) are allowed
Assert.IsTrue(roots.All(r => r.HasAccess is false));
});
}
[Test]
public async Task RootUserAccessEntities_GrandchildrenAsStartNode_YieldsGrandchildRoots_AsNotAllowed()
{
var contentStartNodeIds = await CreateUserAndGetStartNodeIds(_contentByName["1-2-3"].Id, _contentByName["2-3-4"].Id, _contentByName["3-4-5"].Id);
var roots = UserStartNodeEntitiesService
.RootUserAccessEntities(
UmbracoObjectTypes.Document,
contentStartNodeIds)
.ToArray();
Assert.AreEqual(3, roots.Length);
Assert.Multiple(() =>
{
// the three start nodes are the grandchildren of the "1", "2" and "3" roots, respectively, so these are expected as roots
Assert.AreEqual(_contentByName["1"].Key, roots[0].Entity.Key);
Assert.AreEqual(_contentByName["2"].Key, roots[1].Entity.Key);
Assert.AreEqual(_contentByName["3"].Key, roots[2].Entity.Key);
// all are disallowed - only the grandchildren (the actual start nodes) are allowed
Assert.IsTrue(roots.All(r => r.HasAccess is false));
});
}
}

View File

@@ -0,0 +1,134 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.ManagementApi.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public partial class UserStartNodeEntitiesServiceTests : UmbracoIntegrationTest
{
private Dictionary<string, IContent> _contentByName = new ();
private IUserGroup _userGroup;
private IContentService ContentService => GetRequiredService<IContentService>();
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
private IUserService UserService => GetRequiredService<IUserService>();
private IEntityService EntityService => GetRequiredService<IEntityService>();
private IUserStartNodeEntitiesService UserStartNodeEntitiesService => GetRequiredService<IUserStartNodeEntitiesService>();
protected readonly Ordering BySortOrder = Ordering.By("sortOrder");
protected override void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureTestServices(services);
services.AddTransient<IUserStartNodeEntitiesService, UserStartNodeEntitiesService>();
}
[SetUp]
public async Task SetUpTestAsync()
{
if (_contentByName.Any())
{
return;
}
var contentType = new ContentTypeBuilder()
.WithAlias("theContentType")
.Build();
contentType.AllowedAsRoot = true;
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
foreach (var rootNumber in Enumerable.Range(1, 5))
{
var root = new ContentBuilder()
.WithContentType(contentType)
.WithName($"{rootNumber}")
.Build();
ContentService.Save(root);
_contentByName[root.Name!] = root;
foreach (var childNumber in Enumerable.Range(1, 10))
{
var child = new ContentBuilder()
.WithContentType(contentType)
.WithParent(root)
.WithName($"{rootNumber}-{childNumber}")
.Build();
ContentService.Save(child);
_contentByName[child.Name!] = child;
foreach (var grandChildNumber in Enumerable.Range(1, 5))
{
var grandchild = new ContentBuilder()
.WithContentType(contentType)
.WithParent(child)
.WithName($"{rootNumber}-{childNumber}-{grandChildNumber}")
.Build();
ContentService.Save(grandchild);
_contentByName[grandchild.Name!] = grandchild;
}
}
}
_userGroup = new UserGroupBuilder()
.WithAlias("theGroup")
.WithAllowedSections(["content"])
.Build();
_userGroup.StartContentId = null;
await UserGroupService.CreateAsync(_userGroup, Constants.Security.SuperUserKey);
}
private async Task<string[]> CreateUserAndGetStartNodePaths(params int[] startNodeIds)
{
var user = await CreateUser(startNodeIds);
var contentStartNodePaths = user.GetContentStartNodePaths(EntityService, AppCaches.NoCache);
Assert.IsNotNull(contentStartNodePaths);
return contentStartNodePaths;
}
private async Task<int[]> CreateUserAndGetStartNodeIds(params int[] startNodeIds)
{
var user = await CreateUser(startNodeIds);
var contentStartNodeIds = user.CalculateContentStartNodeIds(EntityService, AppCaches.NoCache);
Assert.IsNotNull(contentStartNodeIds);
return contentStartNodeIds;
}
private async Task<User> CreateUser(int[] startNodeIds)
{
var user = new UserBuilder()
.WithName(Guid.NewGuid().ToString("N"))
.WithStartContentIds(startNodeIds)
.Build();
UserService.Save(user);
var attempt = await UserGroupService.AddUsersToUserGroupAsync(
new UsersToUserGroupManipulationModel(_userGroup.Key, [user.Key]),
Constants.Security.SuperUserKey);
Assert.IsTrue(attempt.Success);
return user;
}
}

View File

@@ -40,10 +40,10 @@ internal sealed class OpenAPIContractTest : UmbracoTestServerTestBase
public async Task Validate_OpenApi_Contract_is_implemented()
{
string[] keysToIgnore = { "servers", "x-generator" };
var officePath = GlobalSettings.GetBackOfficePath(HostingEnvironment);
var backOfficePath = HostingEnvironment.GetBackOfficePath();
var urlToContract = $"{officePath}/management/api/openapi.json";
var swaggerPath = $"{officePath}/swagger/management/swagger.json";
var urlToContract = $"{backOfficePath}/management/api/openapi.json";
var swaggerPath = $"{backOfficePath}/swagger/management/swagger.json";
var apiContract = JsonNode.Parse(await Client.GetStringAsync(urlToContract)).AsObject();
var generatedJsonString = await Client.GetStringAsync(swaggerPath);

View File

@@ -13,7 +13,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Delivery.Controllers.Content;
using Umbraco.Cms.Api.Management.Controllers;
using Umbraco.Cms.Api.Management.Controllers.ModelsBuilder;
@@ -31,6 +30,7 @@ using Umbraco.Cms.Tests.Integration.DependencyInjection;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.Integration.TestServerTest
{
@@ -237,11 +237,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
{
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment();
TypeLoader typeLoader = services.AddTypeLoader(
GetType().Assembly,
hostingEnvironment,
TestHelper.ConsoleLoggerFactory,
AppCaches.NoCache,
Configuration,
@@ -249,7 +246,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
services.AddLogger(TestHelper.GetWebHostEnvironment(), Configuration);
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment);
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache);
builder.Services.AddTransient<IHostedService>(sp => new TestDatabaseHostedLifecycleService(() => UseTestDatabase(sp)));
builder
@@ -281,7 +278,6 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
CustomMvcSetup(mvcBuilder);
})
.AddWebServer()
.AddWebsite()
.AddUmbracoSqlServerSupport()
.AddUmbracoSqliteSupport()

View File

@@ -151,12 +151,11 @@ public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase
var hostingEnvironment = TestHelper.GetHostingEnvironment();
var typeLoader = services.AddTypeLoader(
GetType().Assembly,
hostingEnvironment,
TestHelper.ConsoleLoggerFactory,
AppCaches.NoCache,
Configuration,
TestHelper.Profiler);
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment);
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache);
builder.AddConfiguration()
.AddUmbracoCore()

View File

@@ -57,7 +57,7 @@ public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
Subpage = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 1", Textpage.Id);
Subpage.Key = new Guid(SubPageKey);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(Subpage, -1, contentSchedule);
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1055

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
using Umbraco.Cms.Core.Models.PublishedContent;
@@ -12,7 +12,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Cache;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class PublishedContentTypeCacheTests : UmbracoIntegrationTestWithContentEditing
internal sealed class PublishedContentTypeCacheTests : UmbracoIntegrationTestWithContentEditing
{
protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache();

View File

@@ -0,0 +1,258 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
public class ApiContentPathResolverInvariantTests : ApiContentPathResolverTestBase
{
private Dictionary<string, IContent> _contentByName = new ();
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
[SetUp]
public async Task SetUpTest()
{
UmbracoContextFactory.EnsureUmbracoContext();
SetRequestHost("localhost");
if (_contentByName.Any())
{
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
// predictable state with each test run.
RefreshContentCache();
return;
}
await DocumentUrlService.InitAsync(true, CancellationToken.None);
var contentType = new ContentTypeBuilder()
.WithAlias("theContentType")
.Build();
contentType.AllowedAsRoot = true;
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
foreach (var rootNumber in Enumerable.Range(1, 3))
{
var root = new ContentBuilder()
.WithContentType(contentType)
.WithName($"Root {rootNumber}")
.Build();
ContentService.Save(root);
ContentService.Publish(root, ["*"]);
_contentByName[root.Name!] = root;
foreach (var childNumber in Enumerable.Range(1, 3))
{
var child = new ContentBuilder()
.WithContentType(contentType)
.WithParent(root)
.WithName($"Child {childNumber}")
.Build();
ContentService.Save(child);
ContentService.Publish(child, ["*"]);
_contentByName[$"{root.Name!}/{child.Name!}"] = child;
foreach (var grandchildNumber in Enumerable.Range(1, 3))
{
var grandchild = new ContentBuilder()
.WithContentType(contentType)
.WithParent(child)
.WithName($"Grandchild {grandchildNumber}")
.Build();
ContentService.Save(grandchild);
ContentService.Publish(grandchild, ["*"]);
_contentByName[$"{root.Name!}/{child.Name!}/{grandchild.Name!}"] = grandchild;
}
}
}
}
[Test]
public void First_Root_Without_StartItem()
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
}
[Test]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void First_Root_Without_StartItem_With_Top_Level_Node_Included()
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void First_Root_Child_Without_StartItem(int child)
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root 1/Child {child}"].Key, content.Key);
}
[TestCase(1, 1)]
[TestCase(2, 2)]
[TestCase(3, 3)]
[TestCase(1, 2)]
[TestCase(2, 3)]
[TestCase(3, 1)]
public void First_Root_Grandchild_Without_StartItem(int child, int grandchild)
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}/grandchild-{grandchild}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root 1/Child {child}/Grandchild {grandchild}"].Key, content.Key);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void Root_With_StartItem(int root)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void Root_With_StartItem_With_Top_Level_Node_Included(int root)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase(1, 1)]
[TestCase(2, 2)]
[TestCase(3, 3)]
[TestCase(1, 2)]
[TestCase(2, 3)]
[TestCase(3, 1)]
public void Child_With_StartItem(int root, int child)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
}
[TestCase(1, 1)]
[TestCase(2, 2)]
[TestCase(3, 3)]
[TestCase(1, 2)]
[TestCase(2, 3)]
[TestCase(3, 1)]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void Child_With_StartItem_With_Top_Level_Node_Included(int root, int child)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
}
[TestCase(1, 1, 1)]
[TestCase(2, 2, 2)]
[TestCase(3, 3, 3)]
[TestCase(1, 2, 3)]
[TestCase(2, 3, 1)]
[TestCase(3, 1, 2)]
public void Grandchild_With_StartItem(int root, int child, int grandchild)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}/grandchild-{grandchild}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}/Grandchild {grandchild}"].Key, content.Key);
}
[TestCase("/", 1)]
[TestCase("/root-2", 2)]
[TestCase("/root-3", 3)]
public void Root_By_Path_With_StartItem(string path, int root)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase("/", 1)]
[TestCase("/root-2", 2)]
[TestCase("/root-3", 3)]
public void Root_By_Path_Without_StartItem(string path, int root)
{
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public async Task Root_With_Domain_Bindings(int root)
{
await SetContentHost(_contentByName[$"Root {root}"], "some.host", "en-US");
SetRequestHost("some.host");
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase("/a", 1)]
[TestCase("/123", 2)]
[TestCase("/no-such-child", 3)]
[TestCase("/a/b", 1)]
[TestCase("/123/456", 2)]
[TestCase("/no-such-child/no-such-grandchild", 3)]
public void Non_Existant_Descendant_By_Path_With_StartItem(string path, int root)
{
SetRequestStartItem($"root-{root}");
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNull(content);
}
[TestCase("/a")]
[TestCase("/123")]
[TestCase("/a/b")]
[TestCase("/123/456")]
public void Non_Existant_Descendant_By_Path_Without_StartItem(string path)
{
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNull(content);
}
}

View File

@@ -0,0 +1,15 @@
using NUnit.Framework;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public abstract class ApiContentPathResolverTestBase : ApiContentRequestTestBase
{
protected IApiContentPathResolver ApiContentPathResolver => GetRequiredService<IApiContentPathResolver>();
protected IDocumentUrlService DocumentUrlService => GetRequiredService<IDocumentUrlService>();
}

View File

@@ -0,0 +1,326 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
public class ApiContentPathResolverVariantTests : ApiContentPathResolverTestBase
{
private readonly Dictionary<string, IContent> _contentByName = new ();
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
[SetUp]
public async Task SetUpTest()
{
UmbracoContextFactory.EnsureUmbracoContext();
SetRequestHost("localhost");
if (_contentByName.Any())
{
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
// predictable state with each test run.
RefreshContentCache();
return;
}
await DocumentUrlService.InitAsync(true, CancellationToken.None);
await GetRequiredService<ILanguageService>().CreateAsync(new Language("da-DK", "Danish"), Constants.Security.SuperUserKey);
var contentType = new ContentTypeBuilder()
.WithAlias("theContentType")
.WithContentVariation(ContentVariation.Culture)
.Build();
contentType.AllowedAsRoot = true;
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
foreach (var rootNumber in Enumerable.Range(1, 3))
{
var root = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName("en-US", $"Root {rootNumber} en-US")
.WithCultureName("da-DK", $"Root {rootNumber} da-DK")
.Build();
ContentService.Save(root);
ContentService.Publish(root, ["*"]);
_contentByName[$"Root {rootNumber}"] = root;
foreach (var childNumber in Enumerable.Range(1, 3))
{
var child = new ContentBuilder()
.WithContentType(contentType)
.WithParent(root)
.WithCultureName("en-US", $"Child {childNumber} en-US")
.WithCultureName("da-DK", $"Child {childNumber} da-DK")
.Build();
ContentService.Save(child);
ContentService.Publish(child, ["*"]);
_contentByName[$"Root {rootNumber}/Child {childNumber}"] = child;
foreach (var grandchildNumber in Enumerable.Range(1, 3))
{
var grandchild = new ContentBuilder()
.WithContentType(contentType)
.WithParent(child)
.WithCultureName("en-US", $"Grandchild {grandchildNumber} en-US")
.WithCultureName("da-DK", $"Grandchild {grandchildNumber} da-DK")
.Build();
ContentService.Save(grandchild);
ContentService.Publish(grandchild, ["*"]);
_contentByName[$"Root {rootNumber}/Child {childNumber}/Grandchild {grandchildNumber}"] = grandchild;
}
}
}
}
[TestCase("en-US")]
[TestCase("da-DK")]
public void First_Root_Without_StartItem(string culture)
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
}
[TestCase("en-US")]
[TestCase("da-DK")]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void First_Root_Without_StartItem_With_Top_Level_Node_Included(string culture)
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName["Root 1"].Key, content.Key);
}
[TestCase(1, "en-US")]
[TestCase(2, "en-US")]
[TestCase(3, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "da-DK")]
[TestCase(3, "da-DK")]
public void First_Root_Child_Without_StartItem(int child, string culture)
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root 1/Child {child}"].Key, content.Key);
}
[TestCase(1, 1, "en-US")]
[TestCase(2, 2, "en-US")]
[TestCase(3, 3, "en-US")]
[TestCase(1, 2, "en-US")]
[TestCase(2, 3, "en-US")]
[TestCase(3, 1, "en-US")]
[TestCase(1, 1, "da-DK")]
[TestCase(2, 2, "da-DK")]
[TestCase(3, 3, "da-DK")]
[TestCase(1, 2, "da-DK")]
[TestCase(2, 3, "da-DK")]
[TestCase(3, 1, "da-DK")]
public void First_Root_Grandchild_Without_StartItem(int child, int grandchild, string culture)
{
Assert.IsEmpty(GetRequiredService<IHttpContextAccessor>().HttpContext!.Request.Headers["Start-Item"].ToString());
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}/grandchild-{grandchild}-{culture.ToLowerInvariant()}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root 1/Child {child}/Grandchild {grandchild}"].Key, content.Key);
}
[TestCase(1, "en-US")]
[TestCase(2, "en-US")]
[TestCase(3, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "da-DK")]
[TestCase(3, "da-DK")]
public void Root_With_StartItem(int root, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase(1, "en-US")]
[TestCase(2, "en-US")]
[TestCase(3, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "da-DK")]
[TestCase(3, "da-DK")]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void Root_With_StartItem_With_Top_Level_Node_Included(int root, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase(1, 1, "en-US")]
[TestCase(2, 2, "en-US")]
[TestCase(3, 3, "en-US")]
[TestCase(1, 2, "en-US")]
[TestCase(2, 3, "en-US")]
[TestCase(3, 1, "en-US")]
[TestCase(1, 1, "da-DK")]
[TestCase(2, 2, "da-DK")]
[TestCase(3, 3, "da-DK")]
[TestCase(1, 2, "da-DK")]
[TestCase(2, 3, "da-DK")]
[TestCase(3, 1, "da-DK")]
public void Child_With_StartItem(int root, int child, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
}
[TestCase(1, 1, "en-US")]
[TestCase(2, 2, "en-US")]
[TestCase(3, 3, "en-US")]
[TestCase(1, 2, "en-US")]
[TestCase(2, 3, "en-US")]
[TestCase(3, 1, "en-US")]
[TestCase(1, 1, "da-DK")]
[TestCase(2, 2, "da-DK")]
[TestCase(3, 3, "da-DK")]
[TestCase(1, 2, "da-DK")]
[TestCase(2, 3, "da-DK")]
[TestCase(3, 1, "da-DK")]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void Child_With_StartItem_With_Top_Level_Node_Included(int root, int child, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}"].Key, content.Key);
}
[TestCase(1, 1, 1, "en-US")]
[TestCase(2, 2, 2, "en-US")]
[TestCase(3, 3, 3, "en-US")]
[TestCase(1, 2, 3, "en-US")]
[TestCase(2, 3, 1, "en-US")]
[TestCase(3, 1, 2, "en-US")]
[TestCase(1, 1, 1, "da-DK")]
[TestCase(2, 2, 2, "da-DK")]
[TestCase(3, 3, 3, "da-DK")]
[TestCase(1, 2, 3, "da-DK")]
[TestCase(2, 3, 1, "da-DK")]
[TestCase(3, 1, 2, "da-DK")]
public void Grandchild_With_StartItem(int root, int child, int grandchild, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath($"/child-{child}-{culture.ToLowerInvariant()}/grandchild-{grandchild}-{culture.ToLowerInvariant()}");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}/Child {child}/Grandchild {grandchild}"].Key, content.Key);
}
[TestCase("/", 1, "en-US")]
[TestCase("/root-2-en-us", 2, "en-US")]
[TestCase("/root-3-en-us", 3, "en-US")]
[TestCase("/", 1, "da-DK")]
[TestCase("/root-2-da-dk", 2, "da-DK")]
[TestCase("/root-3-da-dk", 3, "da-DK")]
public void Root_By_Path_With_StartItem(string path, int root, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase("/", 1, "en-US")]
[TestCase("/root-2-en-us", 2, "en-US")]
[TestCase("/root-3-en-us", 3, "en-US")]
[TestCase("/", 1, "da-DK")]
[TestCase("/root-2-da-dk", 2, "da-DK")]
[TestCase("/root-3-da-dk", 3, "da-DK")]
public void Root_By_Path_Without_StartItem(string path, int root, string culture)
{
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase(1, "en-US")]
[TestCase(2, "en-US")]
[TestCase(3, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "da-DK")]
[TestCase(3, "da-DK")]
public async Task Root_With_Domain_Bindings(int root, string culture)
{
await SetContentHost(_contentByName[$"Root {root}"], "some.host", "en-US");
SetRequestHost("some.host");
SetVariationContext(culture);
var content = ApiContentPathResolver.ResolveContentPath("/");
Assert.IsNotNull(content);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, content.Key);
}
[TestCase("/a", 1, "en-US")]
[TestCase("/b", 1, "da-DK")]
[TestCase("/123", 2, "en-US")]
[TestCase("/456", 2, "da-DK")]
[TestCase("/no-such-child", 3, "en-US")]
[TestCase("/not-at-all", 3, "da-DK")]
[TestCase("/a/b", 1, "en-US")]
[TestCase("/c/d", 1, "da-DK")]
[TestCase("/123/456", 2, "en-US")]
[TestCase("/789/012", 2, "da-DK")]
[TestCase("/no-such-child/no-such-grandchild", 3, "en-US")]
[TestCase("/not-at-all/aint-no-way", 3, "da-DK")]
public void Non_Existant_Descendant_By_Path_With_StartItem(string path, int root, string culture)
{
SetRequestStartItem($"root-{root}-{culture.ToLowerInvariant()}");
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNull(content);
}
[TestCase("/a")]
[TestCase("/123")]
[TestCase("/a/b")]
[TestCase("/123/456")]
public void Non_Existant_Descendant_By_Path_Without_StartItem(string path)
{
var content = ApiContentPathResolver.ResolveContentPath(path);
Assert.IsNull(content);
}
}

View File

@@ -0,0 +1,93 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
public abstract class ApiContentRequestTestBase : UmbracoIntegrationTest
{
protected IContentService ContentService => GetRequiredService<IContentService>();
protected IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
protected IApiContentRouteBuilder ApiContentRouteBuilder => GetRequiredService<IApiContentRouteBuilder>();
protected IVariationContextAccessor VariationContextAccessor => GetRequiredService<IVariationContextAccessor>();
protected IUmbracoContextAccessor UmbracoContextAccessor => GetRequiredService<IUmbracoContextAccessor>();
protected IUmbracoContextFactory UmbracoContextFactory => GetRequiredService<IUmbracoContextFactory>();
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
builder.AddUmbracoHybridCache();
builder.AddNotificationHandler<ContentTreeChangeNotification, ContentTreeChangeDistributedCacheNotificationHandler>();
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
builder.AddDeliveryApi();
}
[TearDown]
public async Task CleanUpAfterTest()
{
var domainService = GetRequiredService<IDomainService>();
foreach (var content in ContentService.GetRootContent())
{
await domainService.UpdateDomainsAsync(content.Key, new DomainsUpdateModel { Domains = [] });
}
var httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
httpContextAccessor.HttpContext?.Request.Headers.Clear();
}
protected void SetVariationContext(string? culture)
=> VariationContextAccessor.VariationContext = new VariationContext(culture: culture);
protected async Task SetContentHost(IContent content, string host, string culture)
=> await GetRequiredService<IDomainService>().UpdateDomainsAsync(
content.Key,
new DomainsUpdateModel { Domains = [new DomainModel { DomainName = host, IsoCode = culture }] });
protected void SetRequestHost(string host)
{
var httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
httpContextAccessor.HttpContext = new DefaultHttpContext
{
Request =
{
Scheme = "https",
Host = new HostString(host),
Path = "/",
QueryString = new QueryString(string.Empty)
},
RequestServices = Services
};
}
protected void SetRequestStartItem(string startItem)
{
var httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
if (httpContextAccessor.HttpContext is null)
{
throw new InvalidOperationException("HTTP context is null");
}
httpContextAccessor.HttpContext.Request.Headers["Start-Item"] = startItem;
}
protected void RefreshContentCache()
=> Services.GetRequiredService<ContentCacheRefresher>().Refresh([new ContentCacheRefresher.JsonPayload { ChangeTypes = TreeChangeTypes.RefreshAll }]);
}

View File

@@ -0,0 +1,242 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
public class ApiContentRouteBuilderInvariantTests : ApiContentRouteBuilderTestBase
{
private readonly Dictionary<string, IContent> _contentByName = new ();
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
public static void ConfigureOmitTrailingSlash(IUmbracoBuilder builder)
=> builder.Services.Configure<RequestHandlerSettings>(config => config.AddTrailingSlash = false);
[SetUp]
public async Task SetUpTest()
{
SetRequestHost("localhost");
if (_contentByName.Any())
{
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
// predictable state with each test run.
RefreshContentCache();
return;
}
var contentType = new ContentTypeBuilder()
.WithAlias("theContentType")
.Build();
contentType.AllowedAsRoot = true;
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
foreach (var rootNumber in Enumerable.Range(1, 3))
{
var root = new ContentBuilder()
.WithContentType(contentType)
.WithName($"Root {rootNumber}")
.Build();
ContentService.Save(root);
ContentService.Publish(root, ["*"]);
_contentByName[root.Name!] = root;
foreach (var childNumber in Enumerable.Range(1, 3))
{
var child = new ContentBuilder()
.WithContentType(contentType)
.WithParent(root)
.WithName($"Child {childNumber}")
.Build();
ContentService.Save(child);
ContentService.Publish(child, ["*"]);
_contentByName[$"{root.Name!}/{child.Name!}"] = child;
foreach (var grandchildNumber in Enumerable.Range(1, 3))
{
var grandchild = new ContentBuilder()
.WithContentType(contentType)
.WithParent(child)
.WithName($"Grandchild {grandchildNumber}")
.Build();
ContentService.Save(grandchild);
ContentService.Publish(grandchild, ["*"]);
_contentByName[$"{root.Name!}/{child.Name!}/{grandchild.Name!}"] = grandchild;
}
}
}
}
[Test]
public void First_Root()
{
var publishedContent = GetPublishedContent(_contentByName["Root 1"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/", route.Path);
Assert.AreEqual("root-1", route.StartItem.Path);
Assert.AreEqual(_contentByName["Root 1"].Key, route.StartItem.Id);
});
}
[Test]
public void Last_Root()
{
var publishedContent = GetPublishedContent(_contentByName["Root 3"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/root-3/", route.Path);
Assert.AreEqual("root-3", route.StartItem.Path);
Assert.AreEqual(_contentByName["Root 3"].Key, route.StartItem.Id);
});
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void First_Child(int root)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/child-1/", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void Last_Child(int root)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/child-3/", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void First_Grandchild(int root)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1/Grandchild 1"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/child-1/grandchild-1/", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void Last_Grandchild(int root)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3/Grandchild 3"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/child-3/grandchild-3/", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void Root_With_Top_Level_Node_Included(int root)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public async Task Root_With_Domain_Bindings(int root)
{
await SetContentHost(_contentByName[$"Root {root}"], "some.host", "en-US");
SetRequestHost("some.host");
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "/")]
[TestCase(2, "/root-2")]
[TestCase(3, "/root-3")]
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
public void Root_Without_Trailing_Slash(int root, string expectedPath)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual(expectedPath, route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, 1)]
[TestCase(2, 2)]
[TestCase(3, 3)]
[TestCase(1, 2)]
[TestCase(2, 3)]
[TestCase(3, 1)]
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
public void Child_Without_Trailing_Slash(int root, int child)
{
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child {child}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/child-{child}", route.Path);
Assert.AreEqual($"root-{root}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
}

View File

@@ -0,0 +1,20 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Tests.Common.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public abstract class ApiContentRouteBuilderTestBase : ApiContentRequestTestBase
{
protected IPublishedContent GetPublishedContent(Guid key)
{
UmbracoContextAccessor.Clear();
var umbracoContext = UmbracoContextFactory.EnsureUmbracoContext().UmbracoContext;
var publishedContent = umbracoContext.Content?.GetById(key);
Assert.IsNotNull(publishedContent);
return publishedContent;
}
}

View File

@@ -0,0 +1,287 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Integration.Attributes;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.DeliveryApi.Request;
public class ApiContentRouteBuilderVariantTests : ApiContentRouteBuilderTestBase
{
private readonly Dictionary<string, IContent> _contentByName = new ();
public static void ConfigureIncludeTopLevelNodeInPath(IUmbracoBuilder builder)
=> builder.Services.Configure<GlobalSettings>(config => config.HideTopLevelNodeFromPath = false);
public static void ConfigureOmitTrailingSlash(IUmbracoBuilder builder)
=> builder.Services.Configure<RequestHandlerSettings>(config => config.AddTrailingSlash = false);
[SetUp]
public async Task SetUpTest()
{
SetRequestHost("localhost");
if (_contentByName.Any())
{
// these tests all run on the same DB to make them run faster, so we need to get the cache in a
// predictable state with each test run.
RefreshContentCache();
return;
}
await GetRequiredService<ILanguageService>().CreateAsync(new Language("da-DK", "Danish"), Constants.Security.SuperUserKey);
var contentType = new ContentTypeBuilder()
.WithAlias("theContentType")
.WithContentVariation(ContentVariation.Culture)
.Build();
contentType.AllowedAsRoot = true;
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
contentType.AllowedContentTypes = [new() { Alias = contentType.Alias, Key = contentType.Key }];
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
foreach (var rootNumber in Enumerable.Range(1, 3))
{
var root = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName("en-US", $"Root {rootNumber} en-US")
.WithCultureName("da-DK", $"Root {rootNumber} da-DK")
.Build();
ContentService.Save(root);
ContentService.Publish(root, ["*"]);
_contentByName[$"Root {rootNumber}"] = root;
foreach (var childNumber in Enumerable.Range(1, 3))
{
var child = new ContentBuilder()
.WithContentType(contentType)
.WithParent(root)
.WithCultureName("en-US", $"Child {childNumber} en-US")
.WithCultureName("da-DK", $"Child {childNumber} da-DK")
.Build();
ContentService.Save(child);
ContentService.Publish(child, ["*"]);
_contentByName[$"Root {rootNumber}/Child {childNumber}"] = child;
foreach (var grandchildNumber in Enumerable.Range(1, 3))
{
var grandchild = new ContentBuilder()
.WithContentType(contentType)
.WithParent(child)
.WithCultureName("en-US", $"Grandchild {grandchildNumber} en-US")
.WithCultureName("da-DK", $"Grandchild {grandchildNumber} da-DK")
.Build();
ContentService.Save(grandchild);
ContentService.Publish(grandchild, ["*"]);
_contentByName[$"Root {rootNumber}/Child {childNumber}/Grandchild {grandchildNumber}"] = grandchild;
}
}
}
}
[TestCase("da-DK")]
[TestCase("en-US")]
public void First_Root(string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root 1"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/", route.Path);
Assert.AreEqual($"root-1-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root 1"].Key, route.StartItem.Id);
});
}
[TestCase("da-DK")]
[TestCase("en-US")]
public void Last_Root(string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName["Root 3"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/root-3-{culture.ToLowerInvariant()}/", route.Path);
Assert.AreEqual($"root-3-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName["Root 3"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "en-US")]
[TestCase(2, "da-DK")]
[TestCase(3, "en-US")]
[TestCase(3, "da-DK")]
public void First_Child(int root, string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/child-1-{culture.ToLowerInvariant()}/", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "en-US")]
[TestCase(2, "da-DK")]
[TestCase(3, "en-US")]
[TestCase(3, "da-DK")]
public void Last_Child(int root, string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/child-3-{culture.ToLowerInvariant()}/", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "en-US")]
[TestCase(2, "da-DK")]
[TestCase(3, "en-US")]
[TestCase(3, "da-DK")]
public void First_Grandchild(int root, string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 1/Grandchild 1"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/child-1-{culture.ToLowerInvariant()}/grandchild-1-{culture.ToLowerInvariant()}/", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "en-US")]
[TestCase(2, "da-DK")]
[TestCase(3, "en-US")]
[TestCase(3, "da-DK")]
public void Last_Grandchild(int root, string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child 3/Grandchild 3"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/child-3-{culture.ToLowerInvariant()}/grandchild-3-{culture.ToLowerInvariant()}/", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "en-US")]
[TestCase(2, "da-DK")]
[TestCase(3, "en-US")]
[TestCase(3, "da-DK")]
[ConfigureBuilder(ActionName = nameof(ConfigureIncludeTopLevelNodeInPath))]
public void Root_With_Top_Level_Node_Included(int root, string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US")]
[TestCase(1, "da-DK")]
[TestCase(2, "en-US")]
[TestCase(2, "da-DK")]
[TestCase(3, "en-US")]
[TestCase(3, "da-DK")]
public async Task Root_With_Domain_Bindings(int root, string culture)
{
await SetContentHost(_contentByName[$"Root {root}"], "some.host", culture);
SetRequestHost("some.host");
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual("/", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, "en-US", "/")]
[TestCase(1, "da-DK", "/")]
[TestCase(2, "en-US", "/root-2-en-us")]
[TestCase(2, "da-DK", "/root-2-da-dk")]
[TestCase(3, "en-US", "/root-3-en-us")]
[TestCase(3, "da-DK", "/root-3-da-dk")]
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
public void Root_Without_Trailing_Slash(int root, string culture, string expectedPath)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual(expectedPath, route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
[TestCase(1, 1, "en-US")]
[TestCase(1, 1, "da-DK")]
[TestCase(2, 2, "en-US")]
[TestCase(2, 2, "da-DK")]
[TestCase(3, 3, "en-US")]
[TestCase(3, 3, "da-DK")]
[TestCase(1, 2, "en-US")]
[TestCase(1, 2, "da-DK")]
[TestCase(2, 3, "en-US")]
[TestCase(2, 3, "da-DK")]
[TestCase(3, 1, "en-US")]
[TestCase(3, 1, "da-DK")]
[ConfigureBuilder(ActionName = nameof(ConfigureOmitTrailingSlash))]
public void Child_Without_Trailing_Slash(int root, int child, string culture)
{
SetVariationContext(culture);
var publishedContent = GetPublishedContent(_contentByName[$"Root {root}/Child {child}"].Key);
var route = ApiContentRouteBuilder.Build(publishedContent);
Assert.IsNotNull(route);
Assert.Multiple(() =>
{
Assert.AreEqual($"/child-{child}-{culture.ToLowerInvariant()}", route.Path);
Assert.AreEqual($"root-{root}-{culture.ToLowerInvariant()}", route.StartItem.Path);
Assert.AreEqual(_contentByName[$"Root {root}"].Key, route.StartItem.Id);
});
}
}

View File

@@ -11,7 +11,7 @@ using Umbraco.Cms.Tests.Integration.TestServerTest;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Events;
[TestFixture]
public class EventAggregatorTests : UmbracoTestServerTestBase
internal sealed class EventAggregatorTests : UmbracoTestServerTestBase
{
protected override void ConfigureTestServices(IServiceCollection services)
{

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.IO;
[TestFixture]
[UmbracoTest]
public class FileSystemsTests : UmbracoIntegrationTest
internal sealed class FileSystemsTests : UmbracoIntegrationTest
{
[Test]
public void Can_Get_MediaFileManager()

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.IO;
[TestFixture]
[UmbracoTest]
public class ShadowFileSystemTests : UmbracoIntegrationTest
internal sealed class ShadowFileSystemTests : UmbracoIntegrationTest
{
[SetUp]
public void SetUp() => ClearFiles(HostingEnvironment);

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Packaging;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public class CreatedPackagesRepositoryTests : UmbracoIntegrationTest
internal sealed class CreatedPackagesRepositoryTests : UmbracoIntegrationTest
{
[SetUp]
public void SetupTestData() => _testBaseFolder = Guid.NewGuid();

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.PublishedContent;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class PublishContentTypeFactoryTest : UmbracoIntegrationTest
internal sealed class PublishContentTypeFactoryTest : UmbracoIntegrationTest
{
protected override void CustomTestSetup(IUmbracoBuilder builder)
{

View File

@@ -9,7 +9,7 @@ using Umbraco.Cms.Tests.Integration.TestServerTest;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core;
[TestFixture]
public class PublishedContentQueryAccessorTests : UmbracoTestServerTestBase
internal sealed class PublishedContentQueryAccessorTests : UmbracoTestServerTestBase
{
[Test]
[LongRunning]

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class RuntimeStateTests : UmbracoIntegrationTest
internal sealed class RuntimeStateTests : UmbracoIntegrationTest
{
private IRuntimeState RuntimeState => Services.GetRequiredService<IRuntimeState>();
@@ -79,7 +79,7 @@ public class RuntimeStateTests : UmbracoIntegrationTest
protected override void DefinePlan() => To<TestMigration>(TestMigrationFinalState);
}
private class TestMigration : PackageMigrationBase
private sealed class TestMigration : AsyncPackageMigrationBase
{
public TestMigration(
IPackagingService packagingService,
@@ -91,9 +91,13 @@ public class RuntimeStateTests : UmbracoIntegrationTest
IMigrationContext context,
IOptions<PackageMigrationSettings> options)
: base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, options)
{
}
{ }
protected override void Migrate() => ImportPackage.FromEmbeddedResource<TestMigration>().Do();
protected override Task MigrateAsync()
{
ImportPackage.FromEmbeddedResource<TestMigration>().Do();
return Task.CompletedTask;
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
Database = UmbracoTestOptions.Database.NewSchemaPerTest,
PublishedRepositoryEvents = true,
WithApplication = true)]
public class ContentEditingServiceTests : UmbracoIntegrationTestWithContent
internal sealed class ContentEditingServiceTests : UmbracoIntegrationTestWithContent
{
[SetUp]
public void Setup() => ContentRepositoryBase.ThrowOnWarning = true;

View File

@@ -0,0 +1,193 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
[Test]
public async Task Can_Clear_Schedule_Invariant()
{
var doctype = await SetupInvariantDoctypeAsync();
var content = await CreateInvariantContentAsync(doctype);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishInvariantAsync(content);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var clearScheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = Constants.System.InvariantCulture,
Schedule = new ContentScheduleModel(),
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(clearScheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsNull(content!.PublishDate);
Assert.AreEqual(0, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Clear_Schedule_Single_Culture()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel(),
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Release).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Expire).Any());
Assert.AreEqual(4, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Clear_Schedule_Some_Cultures()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel(),
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel(),
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Release).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Expire).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangDa.IsoCode, ContentScheduleAction.Release).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangDa.IsoCode, ContentScheduleAction.Expire).Any());
Assert.AreEqual(2, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Clear_Schedule_All_Cultures()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel(),
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel(),
},
new()
{
Culture = setupInfo.LangBe.IsoCode,
Schedule = new ContentScheduleModel(),
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(0, schedules.FullSchedule.Count);
});
}
}

View File

@@ -0,0 +1,280 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
[Test]
public async Task Can_Publish_Single_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var setupData = await CreateVariantContentAsync(langEn, langDa, langBe, contentType);
var publishAttempt = await ContentPublishingService.PublishAsync(
setupData.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
var content = ContentService.GetById(setupData.Key);
Assert.AreEqual(1, content!.PublishedCultures.Count());
}
[Test]
public async Task Can_Publish_Some_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(langEn, langDa, langBe, contentType);
var publishAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new() { Culture = langEn.IsoCode }, new() { Culture = langDa.IsoCode },
],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
content = ContentService.GetById(content.Key);
Assert.AreEqual(2, content!.PublishedCultures.Count());
}
[Test]
public async Task Can_Publish_All_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(langEn, langDa, langBe, contentType);
var publishAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new() { Culture = langEn.IsoCode },
new() { Culture = langDa.IsoCode },
new() { Culture = langBe.IsoCode },
],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
content = ContentService.GetById(content.Key);
Assert.AreEqual(3, content!.PublishedCultures.Count());
}
[Test]
public async Task Cannot_Publish_Invariant_In_Variant_Setup()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var publishAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[new() { Culture = Constants.System.InvariantCulture }],
Constants.Security.SuperUserKey);
Assert.IsFalse(publishAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.CannotPublishInvariantWhenVariant, publishAttempt.Status);
content = ContentService.GetById(content.Key);
Assert.AreEqual(0, content!.PublishedCultures.Count());
}
[Test]
public async Task Can_Publish_Invariant_In_Invariant_Setup()
{
var doctype = await SetupInvariantDoctypeAsync();
var content = await CreateInvariantContentAsync(doctype);
var publishAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[new() { Culture = Constants.System.InvariantCulture }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
content = ContentService.GetById(content.Key);
Assert.NotNull(content!.PublishDate);
}
[Test]
public async Task Cannot_Publish_Unknown_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var publishAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new() { Culture = langEn.IsoCode },
new() { Culture = langDa.IsoCode },
new() { Culture = UnknownCulture },
],
Constants.Security.SuperUserKey);
Assert.IsFalse(publishAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, publishAttempt.Status);
content = ContentService.GetById(content.Key);
Assert.AreEqual(0, content!.PublishedCultures.Count());
}
[Test]
public async Task Cannot_Publish_Scheduled_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
}
],
Constants.Security.SuperUserKey);
if (scheduleAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var publishAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsFalse(publishAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.CultureAwaitingRelease, publishAttempt.Status);
content = ContentService.GetById(content.Key);
Assert.AreEqual(0, content!.PublishedCultures.Count());
}
// TODO: The following three tests verify existing functionality that could be reconsidered.
// The existing behaviour, verified on Umbraco 13 and 15 is as follows:
// - For invariant content, if a parent is unpublished and I try to publish the child, I get a ContentPublishingOperationStatus.PathNotPublished error.
// - For variant content, if I publish the parent in English but not Danish, I can publish the child in Danish.
// This is inconsistent so we should consider if this is the desired behaviour.
// For now though, the following tests verify the existing behaviour.
[Test]
public async Task Cannot_Publish_With_Unpublished_Parent()
{
var doctype = await SetupInvariantDoctypeAsync();
var parentContent = await CreateInvariantContentAsync(doctype);
var childContent = await CreateInvariantContentAsync(doctype, parentContent.Key);
var publishAttempt = await ContentPublishingService.PublishAsync(
childContent.Key,
[new() { Culture = Constants.System.InvariantCulture }],
Constants.Security.SuperUserKey);
Assert.IsFalse(publishAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.PathNotPublished, publishAttempt.Status);
// Now publish the parent and re-try publishing the child.
publishAttempt = await ContentPublishingService.PublishAsync(
parentContent.Key,
[new() { Culture = Constants.System.InvariantCulture }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
publishAttempt = await ContentPublishingService.PublishAsync(
childContent.Key,
[new() { Culture = Constants.System.InvariantCulture }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
}
[Test]
public async Task Cannot_Publish_Culture_With_Unpublished_Parent()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var parentContent = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var childContent = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType,
parentContent.Key);
// Publish child in English, should not succeed.
var publishAttempt = await ContentPublishingService.PublishAsync(
childContent.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsFalse(publishAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.PathNotPublished, publishAttempt.Status);
// Now publish the parent and re-try publishing the child.
publishAttempt = await ContentPublishingService.PublishAsync(
parentContent.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
publishAttempt = await ContentPublishingService.PublishAsync(
childContent.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
}
[Test]
public async Task Can_Publish_Culture_With_Unpublished_Parent_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var parentContent = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var childContent = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType,
parentContent.Key);
// Publish parent in English.
var publishAttempt = await ContentPublishingService.PublishAsync(
parentContent.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
// Publish child in English, should succeed.
publishAttempt = await ContentPublishingService.PublishAsync(
childContent.Key,
[new() { Culture = langEn.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
// Publish child in Danish, should also succeed.
publishAttempt = await ContentPublishingService.PublishAsync(
childContent.Key,
[new() { Culture = langDa.IsoCode }],
Constants.Security.SuperUserKey);
Assert.IsTrue(publishAttempt.Success);
}
}

View File

@@ -0,0 +1,258 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
[Test]
public async Task Can_Schedule_Publish_Invariant()
{
var doctype = await SetupInvariantDoctypeAsync();
var content = await CreateInvariantContentAsync(doctype);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = Constants.System.InvariantCulture,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsNull(content!.PublishDate);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(Constants.System.InvariantCulture, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Schedule_Publish_Single_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langEn.IsoCode, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Schedule_Publish_Some_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langEn.IsoCode, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langDa.IsoCode, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(2, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Schedule_Publish_All_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = langBe.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langEn.IsoCode, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langDa.IsoCode, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langBe.IsoCode, ContentScheduleAction.Release).Single().Date);
Assert.AreEqual(3, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Cannot_Schedule_Publish_Unknown_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = UnknownCulture,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate }
},
],
Constants.Security.SuperUserKey);
Assert.IsFalse(scheduleAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, scheduleAttempt.Status);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(0, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Publish_And_Schedule_Different_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(1, content!.PublishedCultures.Count());
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
}

View File

@@ -0,0 +1,221 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
[Test]
public async Task Can_Schedule_Unpublish_Invariant()
{
var doctype = await SetupInvariantDoctypeAsync();
var content = await CreateInvariantContentAsync(doctype);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = Constants.System.InvariantCulture,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsNull(content!.PublishDate);
Assert.AreEqual(
_scheduleUnPublishDate,
schedules.GetSchedule(Constants.System.InvariantCulture, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Schedule_Unpublish_Single_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langEn.IsoCode, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Schedule_Unpublish_Some_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langEn.IsoCode, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langDa.IsoCode, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(2, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Schedule_Unpublish_All_Cultures()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
new()
{
Culture = langBe.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langEn.IsoCode, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langDa.IsoCode, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(
_schedulePublishDate,
schedules.GetSchedule(langBe.IsoCode, ContentScheduleAction.Expire).Single().Date);
Assert.AreEqual(3, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Cannot_Schedule_Unpublish_Unknown_Culture()
{
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
langEn,
langDa,
langBe,
contentType);
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = langEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
new()
{
Culture = langDa.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate },
},
new()
{
Culture = UnknownCulture,
Schedule = new ContentScheduleModel { UnpublishDate = _schedulePublishDate }
},
],
Constants.Security.SuperUserKey);
Assert.IsFalse(scheduleAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, scheduleAttempt.Status);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(0, schedules.FullSchedule.Count);
});
}
}

View File

@@ -0,0 +1,248 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
[Test]
public async Task Can_UnSchedule_Publish_Invariant()
{
var doctype = await SetupInvariantDoctypeAsync();
var content = await CreateInvariantContentAsync(doctype);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishInvariantAsync(content);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var unscheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = Constants.System.InvariantCulture,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(unscheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsNull(content!.PublishDate);
Assert.IsFalse(schedules.GetSchedule(Constants.System.InvariantCulture, ContentScheduleAction.Release).Any());
Assert.IsTrue(schedules.GetSchedule(Constants.System.InvariantCulture, ContentScheduleAction.Expire).Any());
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Unschedule_Publish_Single_Culture()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Release).Any());
Assert.AreEqual(5, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Unschedule_Publish_Some_Cultures()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate }
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate }
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Release).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangDa.IsoCode, ContentScheduleAction.Release).Any());
Assert.AreEqual(4, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Unschedule_Publish_All_Cultures()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
new()
{
Culture = setupInfo.LangBe.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Release).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangDa.IsoCode, ContentScheduleAction.Release).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangBe.IsoCode, ContentScheduleAction.Release).Any());
Assert.AreEqual(3, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Cannot_Unschedule_Publish_Unknown_Culture()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
new()
{
Culture = UnknownCulture,
Schedule = new ContentScheduleModel { UnpublishDate = _scheduleUnPublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsFalse(scheduleAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, scheduleAttempt.Status);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(6, schedules.FullSchedule.Count);
});
}
}

View File

@@ -0,0 +1,247 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentPublishingServiceTests : UmbracoIntegrationTestWithContent
{
[Test]
public async Task Can_UnSchedule_Unpublish_Invariant()
{
var doctype = await SetupInvariantDoctypeAsync();
var content = await CreateInvariantContentAsync(doctype);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishInvariantAsync(content);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var unscheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = Constants.System.InvariantCulture,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(unscheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsNull(content!.PublishDate);
Assert.IsFalse(schedules.GetSchedule(Constants.System.InvariantCulture, ContentScheduleAction.Expire).Any());
Assert.IsTrue(schedules.GetSchedule(Constants.System.InvariantCulture, ContentScheduleAction.Release).Any());
Assert.AreEqual(1, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Unschedule_Unpublish_Single_Culture()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Expire).Any());
Assert.AreEqual(5, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Unschedule_Unpublish_Some_Cultures()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate }
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate }
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Expire).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangDa.IsoCode, ContentScheduleAction.Expire).Any());
Assert.AreEqual(4, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Can_Unschedule_Unpublish_All_Cultures()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = setupInfo.LangBe.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsTrue(scheduleAttempt.Success);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangEn.IsoCode, ContentScheduleAction.Expire).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangDa.IsoCode, ContentScheduleAction.Expire).Any());
Assert.IsFalse(schedules.GetSchedule(setupInfo.LangBe.IsoCode, ContentScheduleAction.Expire).Any());
Assert.AreEqual(3, schedules.FullSchedule.Count);
});
}
[Test]
public async Task Cannot_Unschedule_Unpublish_Unknown_Culture()
{
var setupInfo = await SetupVariantDoctypeAsync();
var content = await CreateVariantContentAsync(
setupInfo.LangEn,
setupInfo.LangDa,
setupInfo.LangBe,
setupInfo.contentType);
var scheduleSetupAttempt =
await SchedulePublishAndUnPublishForAllCulturesAsync(content, setupInfo);
if (scheduleSetupAttempt.Success is false)
{
throw new Exception("Setup failed");
}
var scheduleAttempt = await ContentPublishingService.PublishAsync(
content.Key,
[
new()
{
Culture = setupInfo.LangEn.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = setupInfo.LangDa.IsoCode,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
new()
{
Culture = UnknownCulture,
Schedule = new ContentScheduleModel { PublishDate = _schedulePublishDate },
},
],
Constants.Security.SuperUserKey);
Assert.IsFalse(scheduleAttempt.Success);
Assert.AreEqual(ContentPublishingOperationStatus.InvalidCulture, scheduleAttempt.Status);
var schedules = ContentService.GetContentScheduleByContentId(content.Id);
content = ContentService.GetById(content.Key);
Assert.Multiple(() =>
{
Assert.AreEqual(0, content!.PublishedCultures.Count());
Assert.AreEqual(6, schedules.FullSchedule.Count);
});
}
}

View File

@@ -35,7 +35,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
Database = UmbracoTestOptions.Database.NewSchemaPerTest,
PublishedRepositoryEvents = true,
WithApplication = true)]
public class ContentServiceTests : UmbracoIntegrationTestWithContent
internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
{
[SetUp]
public void Setup() => ContentRepositoryBase.ThrowOnWarning = true;
@@ -214,7 +214,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
ctVariant.Variations = ContentVariation.Culture;
ContentTypeService.Save(ctVariant);
var now = DateTime.Now;
var now = DateTime.UtcNow;
// 10x invariant content, half is scheduled to be published in 5 seconds, the other half is scheduled to be unpublished in 5 seconds
var invariant = new List<IContent>();
@@ -321,7 +321,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
// Act
var content = ContentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddHours(2));
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddHours(2));
ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
Assert.AreEqual(1, contentSchedule.FullSchedule.Count);
@@ -676,7 +676,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var root = ContentService.GetById(Textpage.Id);
ContentService.Publish(root!, root!.AvailableCultures.ToArray());
var content = ContentService.GetById(Subpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddSeconds(1));
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddSeconds(1));
ContentService.PersistContentSchedule(content!, contentSchedule);
ContentService.Publish(content, content.AvailableCultures.ToArray());
@@ -1386,7 +1386,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
{
// Arrange
var content = ContentService.GetById(Subpage.Id); // This Content expired 5min ago
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddMinutes(-5));
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddMinutes(-5));
ContentService.Save(content, contentSchedule: contentSchedule);
var parent = ContentService.GetById(Textpage.Id);
@@ -1416,7 +1416,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var content = ContentBuilder.CreateBasicContent(contentType);
content.SetCultureName("Hello", "en-US");
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", null, DateTime.Now.AddMinutes(-5));
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", null, DateTime.UtcNow.AddMinutes(-5));
ContentService.Save(content, contentSchedule: contentSchedule);
var published = ContentService.Publish(content, new[] { "en-US" });
@@ -1431,7 +1431,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
{
// Arrange
var content = ContentService.GetById(Subpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddHours(2), null);
ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
var parent = ContentService.GetById(Textpage.Id);
@@ -1488,7 +1488,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
content.Properties[0].SetValue("Foo", string.Empty);
contentService.Save(content);
contentService.PersistContentSchedule(content,
ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null));
ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddHours(2), null));
// Act
var result = contentService.Publish(content, Array.Empty<string>(), userId: Constants.Security.SuperUserId);
@@ -1540,7 +1540,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
contentService.Publish(content, Array.Empty<string>());
contentService.PersistContentSchedule(content,
ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null));
ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddHours(2), null));
contentService.Save(content);
// Act
@@ -1568,7 +1568,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var content = ContentBuilder.CreateBasicContent(contentType);
content.SetCultureName("Hello", "en-US");
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.Now.AddHours(2), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.UtcNow.AddHours(2), null);
ContentService.Save(content, contentSchedule: contentSchedule);
var published = ContentService.Publish(content, new[] { "en-US" });
@@ -1913,11 +1913,11 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
[Test]
[LongRunning]
public void Ensures_Permissions_Are_Retained_For_Copied_Descendants_With_Explicit_Permissions()
public async Task Ensures_Permissions_Are_Retained_For_Copied_Descendants_With_Explicit_Permissions()
{
// Arrange
var userGroup = UserGroupBuilder.CreateUserGroup("1");
UserService.Save(userGroup);
await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);
@@ -1954,11 +1954,11 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
[Test]
[LongRunning]
public void Ensures_Permissions_Are_Inherited_For_Copied_Descendants()
public async Task Ensures_Permissions_Are_Inherited_For_Copied_Descendants()
{
// Arrange
var userGroup = UserGroupBuilder.CreateUserGroup("1");
UserService.Save(userGroup);
await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);
@@ -1969,7 +1969,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
{
new(contentType.Key, 0, contentType.Alias)
};
ContentTypeService.Save(contentType);
await ContentTypeService.UpdateAsync(contentType, Constants.Security.SuperUserKey);
var parentPage = ContentBuilder.CreateSimpleContent(contentType);
ContentService.Save(parentPage);
@@ -2060,7 +2060,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey);
editorGroup.StartContentId = content1.Id;
UserService.Save(editorGroup);
await UserGroupService.UpdateAsync(editorGroup, Constants.Security.SuperUserKey);
var admin = await UserService.GetAsync(Constants.Security.SuperUserKey);
admin.StartContentIds = new[] { content1.Id };
@@ -2075,7 +2075,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
Assert.IsTrue(PublicAccessService.AddRule(content1, "test2", "test2").Success);
var user = await UserService.GetAsync(Constants.Security.SuperUserKey);
var userGroup = UserService.GetUserGroupByAlias(user.Groups.First().Alias);
var userGroup = await UserGroupService.GetAsync(user.Groups.First().Alias);
Assert.IsNotNull(NotificationService.CreateNotification(user, content1, "X"));
ContentService.SetPermission(content1, "A", new[] { userGroup.Id });

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public class
internal sealed class
ContentTypeEditingServiceModelsBuilderDisabledTests : ContentTypeEditingServiceModelsBuilderDisabledTestsBase
{
// test some properties from IPublishedContent

View File

@@ -6,6 +6,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
/// Unlike <see cref="ContentTypeEditingServiceModelsBuilderEnabledTestsBase"/> this testbase does not configure the modelsbuilder based <see cref="ConfigurePropertySettingsOptions"/>
/// which has the same effect as disabling it completely as <see cref="ContentTypeEditingServiceModelsBuilderEnabledTestsBase"/> only loads in that part anyway.
/// </summary>
public class ContentTypeEditingServiceModelsBuilderDisabledTestsBase : ContentTypeEditingServiceTestsBase
internal class ContentTypeEditingServiceModelsBuilderDisabledTestsBase : ContentTypeEditingServiceTestsBase
{
}

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public class ContentTypeEditingServiceModelsBuilderEnabledTests : ContentTypeEditingServiceModelsBuilderEnabledTestsBase
internal sealed class ContentTypeEditingServiceModelsBuilderEnabledTests : ContentTypeEditingServiceModelsBuilderEnabledTestsBase
{
// test some properties from IPublishedContent
[TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { nameof(IPublishedContent.Id) })]

View File

@@ -6,7 +6,7 @@ using Umbraco.Cms.Infrastructure.ModelsBuilder.Options;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public class ContentTypeEditingServiceModelsBuilderEnabledTestsBase : ContentTypeEditingServiceTestsBase
internal class ContentTypeEditingServiceModelsBuilderEnabledTestsBase : ContentTypeEditingServiceTestsBase
{
protected override void CustomTestSetup(IUmbracoBuilder builder)
{

View File

@@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentTypeEditingServiceTests
internal sealed partial class ContentTypeEditingServiceTests
{
[TestCase(false)]
[TestCase(true)]

View File

@@ -1,11 +1,11 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentTypeEditingServiceTests
internal sealed partial class ContentTypeEditingServiceTests
{
[TestCase(true)]
[TestCase(false)]

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
@@ -6,7 +6,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentTypeEditingServiceTests
internal sealed partial class ContentTypeEditingServiceTests
{
[TestCase(false)]
[TestCase(true)]

View File

@@ -1,5 +1,5 @@
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class ContentTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
internal sealed partial class ContentTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
{
}

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
using Umbraco.Cms.Core.Services;
@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
Database = UmbracoTestOptions.Database.NewSchemaPerTest,
PublishedRepositoryEvents = true,
WithApplication = true)]
public abstract class ContentTypeEditingServiceTestsBase : UmbracoIntegrationTest
internal abstract class ContentTypeEditingServiceTestsBase : UmbracoIntegrationTest
{
protected IContentTypeEditingService ContentTypeEditingService => GetRequiredService<IContentTypeEditingService>();

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB", "A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2 to itself

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Creating_Content_At_Root()

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Deleting_From_Recycle_Bin()

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", "60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Grandchild 1 to Child 2

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
public async Task Parent_And_Descendants_Are_Updated_When_Content_Is_Moved_To_Recycle_Bin()

View File

@@ -7,10 +7,11 @@ using Umbraco.Cms.Core.Services.Navigation;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
public async Task Structure_Can_Rebuild()
[TestCase(1, TestName = "Structure_Can_Rebuild")]
[TestCase(2, TestName = "Structure_Can_Rebuild_MultipleTimes")]
public async Task Structure_Can_Rebuild(int numberOfRebuilds)
{
// Arrange
Guid nodeKey = Root.Key;
@@ -21,6 +22,7 @@ public partial class DocumentNavigationServiceTests
DocumentNavigationQueryService.TryGetDescendantsKeys(nodeKey, out IEnumerable<Guid> originalDescendantsKeys);
DocumentNavigationQueryService.TryGetAncestorsKeys(nodeKey, out IEnumerable<Guid> originalAncestorsKeys);
DocumentNavigationQueryService.TryGetSiblingsKeys(nodeKey, out IEnumerable<Guid> originalSiblingsKeys);
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> originalRouteKeys);
// In-memory navigation structure is empty here
var newDocumentNavigationService = new DocumentNavigationService(
@@ -30,7 +32,10 @@ public partial class DocumentNavigationServiceTests
var initialNodeExists = newDocumentNavigationService.TryGetParentKey(nodeKey, out _);
// Act
await newDocumentNavigationService.RebuildAsync();
for (int i = 0; i < numberOfRebuilds; i++)
{
await newDocumentNavigationService.RebuildAsync();
}
// Capture rebuilt state
var nodeExists = newDocumentNavigationService.TryGetParentKey(nodeKey, out Guid? parentKeyFromRebuild);
@@ -38,6 +43,7 @@ public partial class DocumentNavigationServiceTests
newDocumentNavigationService.TryGetDescendantsKeys(nodeKey, out IEnumerable<Guid> descendantsKeysFromRebuild);
newDocumentNavigationService.TryGetAncestorsKeys(nodeKey, out IEnumerable<Guid> ancestorsKeysFromRebuild);
newDocumentNavigationService.TryGetSiblingsKeys(nodeKey, out IEnumerable<Guid> siblingsKeysFromRebuild);
newDocumentNavigationService.TryGetRootKeys(out IEnumerable<Guid> routeKeysFromRebuild);
// Assert
Assert.Multiple(() =>
@@ -53,11 +59,13 @@ public partial class DocumentNavigationServiceTests
CollectionAssert.AreEquivalent(originalDescendantsKeys, descendantsKeysFromRebuild);
CollectionAssert.AreEquivalent(originalAncestorsKeys, ancestorsKeysFromRebuild);
CollectionAssert.AreEquivalent(originalSiblingsKeys, siblingsKeysFromRebuild);
CollectionAssert.AreEquivalent(originalRouteKeys, routeKeysFromRebuild);
});
}
[Test]
public async Task Bin_Structure_Can_Rebuild()
[TestCase(1, TestName = "Bin_Structure_Can_Rebuild")]
[TestCase(2, TestName = "Bin_Structure_Can_Rebuild_MultipleTimes")]
public async Task Bin_Structure_Can_Rebuild(int numberOfRebuilds)
{
// Arrange
Guid nodeKey = Root.Key;
@@ -69,6 +77,7 @@ public partial class DocumentNavigationServiceTests
DocumentNavigationQueryService.TryGetDescendantsKeysInBin(nodeKey, out IEnumerable<Guid> originalDescendantsKeys);
DocumentNavigationQueryService.TryGetAncestorsKeysInBin(nodeKey, out IEnumerable<Guid> originalAncestorsKeys);
DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeKey, out IEnumerable<Guid> originalSiblingsKeys);
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> originalRouteKeys);
// In-memory navigation structure is empty here
var newDocumentNavigationService = new DocumentNavigationService(
@@ -78,7 +87,10 @@ public partial class DocumentNavigationServiceTests
var initialNodeExists = newDocumentNavigationService.TryGetParentKeyInBin(nodeKey, out _);
// Act
await newDocumentNavigationService.RebuildBinAsync();
for (int i = 0; i < numberOfRebuilds; i++)
{
await newDocumentNavigationService.RebuildBinAsync();
}
// Capture rebuilt state
var nodeExists = newDocumentNavigationService.TryGetParentKeyInBin(nodeKey, out Guid? parentKeyFromRebuild);
@@ -86,6 +98,7 @@ public partial class DocumentNavigationServiceTests
newDocumentNavigationService.TryGetDescendantsKeysInBin(nodeKey, out IEnumerable<Guid> descendantsKeysFromRebuild);
newDocumentNavigationService.TryGetAncestorsKeysInBin(nodeKey, out IEnumerable<Guid> ancestorsKeysFromRebuild);
newDocumentNavigationService.TryGetSiblingsKeysInBin(nodeKey, out IEnumerable<Guid> siblingsKeysFromRebuild);
newDocumentNavigationService.TryGetRootKeys(out IEnumerable<Guid> routeKeysFromRebuild);
// Assert
Assert.Multiple(() =>
@@ -101,6 +114,7 @@ public partial class DocumentNavigationServiceTests
CollectionAssert.AreEquivalent(originalDescendantsKeys, descendantsKeysFromRebuild);
CollectionAssert.AreEquivalent(originalAncestorsKeys, ancestorsKeysFromRebuild);
CollectionAssert.AreEquivalent(originalSiblingsKeys, siblingsKeysFromRebuild);
CollectionAssert.AreEquivalent(originalRouteKeys, routeKeysFromRebuild);
});
}
}

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", "C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Grandchild 1 to Child 1

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Models.ContentEditing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Reversing_Children_Sort_Order()

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Models.ContentEditing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests
internal sealed partial class DocumentNavigationServiceTests
{
[Test]
public async Task Structure_Does_Not_Update_When_Updating_Content()

View File

@@ -6,7 +6,7 @@ using Umbraco.Cms.Tests.Common.Builders;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class DocumentNavigationServiceTests : DocumentNavigationServiceTestsBase
internal sealed partial class DocumentNavigationServiceTests : DocumentNavigationServiceTestsBase
{
[SetUp]
public async Task Setup()

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public abstract class DocumentNavigationServiceTestsBase : UmbracoIntegrationTest
internal abstract class DocumentNavigationServiceTestsBase : UmbracoIntegrationTest
{
protected IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Mock)]
public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
internal sealed class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
{
private const string SubSubPage2Key = "48AE405E-5142-4EBE-929F-55EB616F51F2";
private const string SubSubPage3Key = "AACF2979-3F53-4184-B071-BA34D3338497";
@@ -230,7 +230,7 @@ public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
// Create a subpage
var subsubpage = ContentBuilder.CreateSimpleContent(ContentType, documentName, Subpage.Id);
subsubpage.Key = Guid.Parse(documentKey);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(subsubpage, -1, contentSchedule);
if (loadDraft is false)
@@ -248,7 +248,7 @@ public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
secondRoot.Key = new Guid("8E21BCD4-02CA-483D-84B0-1FC92702E198");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
if (loadDraft is false)
@@ -266,7 +266,7 @@ public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTestWithContent
internal sealed class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTestWithContent
{
protected IDocumentUrlService DocumentUrlService => GetRequiredService<IDocumentUrlService>();
protected ILanguageService LanguageService => GetRequiredService<ILanguageService>();
@@ -62,7 +62,7 @@ public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTest
// Create a subpage
var subsubpage = ContentBuilder.CreateSimpleContent(ContentType, "Sub Page 1", Subpage.Id);
subsubpage.Key = new Guid("DF49F477-12F2-4E33-8563-91A7CC1DCDBB");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(subsubpage, -1, contentSchedule);
if (loadDraft is false)
@@ -81,7 +81,7 @@ public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTest
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
secondRoot.Key = new Guid("8E21BCD4-02CA-483D-84B0-1FC92702E198");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
if (loadDraft is false)
@@ -100,7 +100,7 @@ public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTest
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
[SuppressMessage("ReSharper", "NotNullOrRequiredMemberIsNotInitialized")]
public class DynamicRootServiceTests : UmbracoIntegrationTest
internal sealed class DynamicRootServiceTests : UmbracoIntegrationTest
{
public enum DynamicRootOrigin
{

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class ElementSwitchValidatorTests : UmbracoIntegrationTest
internal sealed class ElementSwitchValidatorTests : UmbracoIntegrationTest
{
private IElementSwitchValidator ElementSwitchValidator => GetRequiredService<IElementSwitchValidator>();

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class LogViewerServiceTests : UmbracoIntegrationTest
internal sealed class LogViewerServiceTests : UmbracoIntegrationTest
{
private ILogViewerService LogViewerService => GetRequiredService<ILogViewerService>();

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class MediaEditingServiceTests : UmbracoIntegrationTest
internal sealed class MediaEditingServiceTests : UmbracoIntegrationTest
{
protected IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Creating_Media_At_Root()

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
[TestCase("1CD97C02-8534-4B72-AE9E-AE52EC94CF31")] // Album

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Deleting_From_Recycle_Bin()

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
[TestCase("62BCE72F-8C18-420E-BCAC-112B5ECC95FD", "139DC977-E50F-4382-9728-B278C4B7AC6A")] // Image 4 to Sub-album 1

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Moving_Media_To_Recycle_Bin()

View File

@@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Services.Navigation;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
public async Task Structure_Can_Rebuild()

View File

@@ -3,7 +3,7 @@ using Umbraco.Cms.Core;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
[TestCase("62BCE72F-8C18-420E-BCAC-112B5ECC95FD", "139DC977-E50F-4382-9728-B278C4B7AC6A")] // Image 4 to Sub-album 1

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Models.ContentEditing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
public async Task Structure_Updates_When_Reversing_Children_Sort_Order()

View File

@@ -4,9 +4,9 @@ using Umbraco.Cms.Core.Models.ContentEditing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests
internal sealed partial class MediaNavigationServiceTests
{
[Test]
[Test]
public async Task Structure_Does_Not_Update_When_Updating_Media()
{
// Arrange

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaNavigationServiceTests : MediaNavigationServiceTestsBase
internal sealed partial class MediaNavigationServiceTests : MediaNavigationServiceTestsBase
{
[SetUp]
public async Task Setup()

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public abstract class MediaNavigationServiceTestsBase : UmbracoIntegrationTest
internal abstract class MediaNavigationServiceTestsBase : UmbracoIntegrationTest
{
protected IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaTypeEditingServiceTests
internal sealed partial class MediaTypeEditingServiceTests
{
[Test]
public async Task Can_Create_With_All_Basic_Settings()

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
/// even though those share a base implementation.
/// For a more detailed test suite on compositions, check <see cref="ContentTypeEditingServiceTests" />.
/// </summary>
public partial class MediaTypeEditingServiceTests
internal sealed partial class MediaTypeEditingServiceTests
{
[Test]
public async Task Can_Get_Available_Compositions_Only_From_Media_Type_Key()

View File

@@ -1,10 +1,10 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaTypeEditingServiceTests
internal sealed partial class MediaTypeEditingServiceTests
{
[Test]
public async Task Can_Get_Default_Folder_Media_Type()

View File

@@ -1,10 +1,10 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaTypeEditingServiceTests
internal sealed partial class MediaTypeEditingServiceTests
{
[TestCase("jpg", Constants.Conventions.MediaTypes.Image)]
[TestCase("png", Constants.Conventions.MediaTypes.Image)]

View File

@@ -1,11 +1,11 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public partial class MediaTypeEditingServiceTests
internal sealed partial class MediaTypeEditingServiceTests
{
[Test]
public async Task Can_Update_All_Basic_Settings()

View File

@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
/// Tests for the media type editing service. Please notice that a lot of functional test is covered by the content type
/// editing service tests, since these services share the same base implementation.
/// </summary>
public partial class MediaTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
internal sealed partial class MediaTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
{
protected override void ConfigureTestServices(IServiceCollection services)
{

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
@@ -12,7 +12,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
/// </summary>
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class MemberGroupServiceTests : UmbracoIntegrationTest
internal sealed class MemberGroupServiceTests : UmbracoIntegrationTest
{
private IMemberGroupService MemberGroupService => GetRequiredService<IMemberGroupService>();

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
/// Tests for the member type editing service. Please notice that a lot of functional test is covered by the content type
/// editing service tests, since these services share the same base implementation.
/// </summary>
public class MemberTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
internal sealed class MemberTypeEditingServiceTests : ContentTypeEditingServiceTestsBase
{
private IMemberTypeEditingService MemberTypeEditingService => GetRequiredService<IMemberTypeEditingService>();

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class MetricsConsentServiceTest : UmbracoIntegrationTest
internal sealed class MetricsConsentServiceTest : UmbracoIntegrationTest
{
private IMetricsConsentService MetricsConsentService => GetRequiredService<IMetricsConsentService>();

View File

@@ -1,33 +1,23 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Handlers;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.DependencyInjection;
using Umbraco.Cms.Infrastructure.Examine;
using Umbraco.Cms.Infrastructure.Examine.DependencyInjection;
using Umbraco.Cms.Infrastructure.HostedServices;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Cms.Tests.Common.Attributes;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping;
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Mock)]
public class PublishStatusServiceTest : UmbracoIntegrationTestWithContent
internal sealed class PublishStatusServiceTest : UmbracoIntegrationTestWithContent
{
protected IPublishStatusQueryService PublishStatusQueryService => GetRequiredService<IPublishStatusQueryService>();
@@ -164,7 +154,7 @@ public class PublishStatusServiceTest : UmbracoIntegrationTestWithContent
{
var grandchild = ContentBuilder.CreateSimpleContent(ContentType, "Grandchild", Subpage2.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(grandchild, -1, contentSchedule);
var publishResults = ContentService.PublishBranch(Textpage, PublishBranchFilter.IncludeUnpublished, ["*"]);

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Tests.Common.Builders;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public class PublishedUrlInfoProviderTests : PublishedUrlInfoProviderTestsBase
internal sealed class PublishedUrlInfoProviderTests : PublishedUrlInfoProviderTestsBase
{
[Test]
@@ -12,7 +12,7 @@ public class PublishedUrlInfoProviderTests : PublishedUrlInfoProviderTestsBase
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -18,7 +18,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Mock)]
public abstract class PublishedUrlInfoProviderTestsBase : UmbracoIntegrationTestWithContent
internal abstract class PublishedUrlInfoProviderTestsBase : UmbracoIntegrationTestWithContent
{
protected IDocumentUrlService DocumentUrlService => GetRequiredService<IDocumentUrlService>();

View File

@@ -6,7 +6,7 @@ using Umbraco.Cms.Tests.Common.Builders;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
public class PublishedUrlInfoProvider_hidetoplevel_false : PublishedUrlInfoProviderTestsBase
internal sealed class PublishedUrlInfoProvider_hidetoplevel_false : PublishedUrlInfoProviderTestsBase
{
protected override void CustomTestSetup(IUmbracoBuilder builder)
{
@@ -19,7 +19,7 @@ public class PublishedUrlInfoProvider_hidetoplevel_false : PublishedUrlInfoProvi
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -1,4 +1,4 @@
using Castle.Components.DictionaryAdapter.Xml;
using Castle.Components.DictionaryAdapter.Xml;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
@@ -13,7 +13,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class RelationServiceTests : UmbracoIntegrationTest
internal sealed class RelationServiceTests : UmbracoIntegrationTest
{
private IRelationService RelationService => GetRequiredService<IRelationService>();

View File

@@ -21,7 +21,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
/// </summary>
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class TelemetryProviderTests : UmbracoIntegrationTest
internal sealed class TelemetryProviderTests : UmbracoIntegrationTest
{
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
@@ -48,6 +48,8 @@ public class TelemetryProviderTests : UmbracoIntegrationTest
private IUserService UserService => GetRequiredService<IUserService>();
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();
private IMediaService MediaService => GetRequiredService<IMediaService>();
private IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();
@@ -266,11 +268,11 @@ public class TelemetryProviderTests : UmbracoIntegrationTest
}
[Test]
public void UserTelemetry_Can_Get_With_Saved_UserGroups()
public async Task UserTelemetry_Can_Get_With_Saved_UserGroups()
{
var userGroup = BuildUserGroup("testGroup");
UserService.Save(userGroup);
await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
var result = UserTelemetryProvider.GetInformation()
.FirstOrDefault(x => x.Name == Constants.Telemetry.UserGroupCount);
@@ -278,14 +280,13 @@ public class TelemetryProviderTests : UmbracoIntegrationTest
}
[Test]
public void UserTelemetry_Can_Get_More_UserGroups()
public async Task UserTelemetry_Can_Get_More_UserGroups()
{
var userGroups = BuildUserGroups(100);
foreach (var userGroup in userGroups)
{
UserService.Save(userGroup);
await UserGroupService.CreateAsync(userGroup, Constants.Security.SuperUserKey);
}
var result = UserTelemetryProvider.GetInformation()

View File

@@ -0,0 +1,91 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.TemporaryFile;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Attributes;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public class TemporaryFileServiceTests : UmbracoIntegrationTest
{
private ITemporaryFileService TemporaryFileService => GetRequiredService<ITemporaryFileService>();
public static void ConfigureAllowedUploadedFileExtensions(IUmbracoBuilder builder)
{
builder.Services.Configure<ContentSettings>(config =>
config.AllowedUploadedFileExtensions = new HashSet<string> { "txt" });
}
[Test]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowedUploadedFileExtensions))]
public async Task Can_Create_Get_And_Delete_Temporary_File()
{
var key = Guid.NewGuid();
const string FileName = "test.txt";
const string FileContents = "test";
var model = new CreateTemporaryFileModel
{
FileName = FileName,
Key = key,
OpenReadStream = () =>
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(FileContents);
writer.Flush();
stream.Position = 0;
return stream;
}
};
var createAttempt = await TemporaryFileService.CreateAsync(model);
Assert.IsTrue(createAttempt.Success);
TemporaryFileModel? fileModel = await TemporaryFileService.GetAsync(key);
Assert.IsNotNull(fileModel);
Assert.AreEqual(key, fileModel.Key);
Assert.AreEqual(FileName, fileModel.FileName);
using (var reader = new StreamReader(fileModel.OpenReadStream()))
{
string fileContents = reader.ReadToEnd();
Assert.AreEqual(FileContents, fileContents);
}
var deleteAttempt = await TemporaryFileService.DeleteAsync(key);
Assert.IsTrue(createAttempt.Success);
fileModel = await TemporaryFileService.GetAsync(key);
Assert.IsNull(fileModel);
}
[Test]
[ConfigureBuilder(ActionName = nameof(ConfigureAllowedUploadedFileExtensions))]
public async Task Cannot_Create_File_Outside_Of_Temporary_Files_Root()
{
var key = Guid.NewGuid();
const string FileName = "../test.txt";
var model = new CreateTemporaryFileModel
{
FileName = FileName,
Key = key,
OpenReadStream = () =>
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(string.Empty);
writer.Flush();
stream.Position = 0;
return stream;
}
};
var createAttempt = await TemporaryFileService.CreateAsync(model);
Assert.IsFalse(createAttempt.Success);
Assert.AreEqual(TemporaryFileOperationStatus.InvalidFileName, createAttempt.Status);
}
}

View File

@@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class UserGroupServiceValidationTests : UmbracoIntegrationTest
internal sealed class UserGroupServiceValidationTests : UmbracoIntegrationTest
{
private IUserGroupService UserGroupService => GetRequiredService<IUserGroupService>();

Some files were not shown because too many files have changed in this diff Show More