* Add key to UserGroupDto * Fix renaming table in sqlite The SqliteSyntaxProvider needed an overload to use the correct query * Start work on user group GUID migration * Add key index to UserGroupDto * Copy over data when migrating sqlite * Make sqlite column migration work * Remove PostMigrations These should be replaced with Notification usage * Remove outer scope from Upgrader * Remove unececary null check * Add marker base class for migrations * Enable scopeless migrations * Remove unnecessary state check The final state of the migration is no longer necessarily the final state of the plan. * Extend ExecutedMigrationPlan * Ensure that MigrationPlanExecutor.Execute always returns a result. * Always save final state, regardless of errors * Remove obsolete Execute * Add Umbraco specific migration notification * Publish notification after umbraco migration * Throw the exception that failed a migration after publishing notification * Handle notification publishing in DatabaseBuilder * Fix tests * Remember to complete scope * Clean up MigrationPlanExecutor * Run each package migration in a separate scope * Add PartialMigrationsTests * Add unhappy path test * Fix bug shown by test * Move PartialMigrationsTests into the correct folder * Comment out refresh cache in data type migration Need to add this back again as a notification handler or something. * Start working on a notification test * Allow migrations to request a cache rebuild * Set RebuildCache from MigrateDataTypeConfigurations * Clean MigrationPlanExecutor * Add comment explaining the need to partial migration success * Fix tests * Allow overriding DefinePlan of UmbracoPlan This is needed to test the DatabaseBuilder * Fix notification test * Don't throw exception to be immediately re-caught * Assert that scopes notification are always published * Ensure that scopes are created when requested * Make test classes internal. It doesn't really matter, but this way it doesn't show up in intellisense * Add notification handler for clearing cookies * Add CompatibilitySuppressions * Use unscoped migration for adding GUID to user group * Make sqlite migration work It's really not pretty, square peg, round hole. * Don't re-enable foreign keys This will happen automatically next time a connection is started. * Scope database when using SQLServer * Don't call complete transaction * Tidy up a couple of comment * Only allow scoping the database from UnscopedMigrationBase * Fix comment * Remove remark in UnscopedMigrationBase as it's no longer true * Add keys when creating default user groups * Map database value from DTO to entity * Fix migration Rename also renamed the foreign keys, making it not work * Make migration idempotent * Fix unit test * Update CompatibilitySuppressions.xml * Add GetUserGroupByKey to UserService * Add ByKey endpoint * Add UniqueId to AppendGroupBy Otherwise MSSQL grenades * Ensure that languages are returned by PerformGetByQuery * add POC displaying model * Clean up by key controller * Add GetAllEndpoint * Add delete endpoint * Use GetKey to get GUID from id Instead of pulling up the entire entity. * Add UserGroup2Permission table * Fetch the new permissions when getting user groups * Dont ToString int to parse it to a short I'm pretty sure this is some way old migration type code that doesn't make any sense anymore * Add new relation to GetDeleteClauses * Persist the permissions * Split UserGroupViewModel into multiple models This is to make it possible to make endpoints more rest-ish * Bootstrap create and update endpoints * Make GetAllUserGroupController paged * Add method to create IUserGroup from UserGroupSaveModel * Add sanity check version of endpoint * Fix persisting permissions * Map section aliases to the name the frontend expects This is a temporary fix till we find out how we really want to handle this * Fix up post merge * Make naming more consistent * Implement initial update endpoint * Fix media start node * Clean name for XSS when mapping to IUserGroup * Use a set instead of a list for permission names We don't want dupes * Make permission column nvarchar max * Add UserGroupOperationStatuses * Add IUserGroupAuthorizationService * Add specific user group creation method to user service * Move validating and authorizing into its own methods * Add operation result to action result mapping * Update create controller to use the create method * Fix create end point * Comment out getting current user untill we have auth * Add usergroup service * Obsolete usergroup things from IUserService * Add update to UserGroupService interface * User IUserGroupService in controllers * User async notifications overloads * Move authorize user group creation into its own service * Add AuthorizeUserGroupUpdate method * Make new service implementations internal and sealed * Add update user * Add GetAll to usergroup service * Remove or obsolete usages of GetAllUserGroups * Add usergroup service to DI * Remove usage of GetGroupsByAlias * Remove usages of GetUserGroupByAlias * Remove usage of GetUserGroupById * Add new table when creating a new database * Implement Delete * Add skip and take to getall * Move skip take into the service * Fixup suggestions in user group service * Fixup unit tests * Allow admins to change user groups they're not a part of * Add CompatibilitySuppressions * Update openapi * Uppdate OpenApi.json again * Add missing compatibility suppression * Added missing type info in ProducesResponseTypeAttribute * Added INamedEntityViewModel and added on the relevant view models * Fixed bug, resulting in serialization not being the same as swagger reported. Now all types objects implementing an interface, is serialized with the $type property * updated OpenApi.json * Added missing title in notfound response * Typo * .Result to .GetAwaiter().GetResult() * Update comment to mention it should be implemented on CurrentUserController * Validate that start nodes actually exists * Handle not found consistently * Use iso codes instead of ids * Update OpenAPI * Automatically infer statuscode in problemdetails * Ensure that the language exists * Fix usergroup 2 permission index * Validate that group name and alias is not too long * Only return status from validation We're just returning the same usergroups, and this is less boilerplate code * Handle empty and null group names * Remove group prefix from statuses * Add some basic validation tests * Don't allow updating a usergroup to having a duplicate alias --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk>
278 lines
10 KiB
C#
278 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using Umbraco.Cms.Core.Actions;
|
|
using Umbraco.Cms.Core.Dictionary;
|
|
using Umbraco.Cms.Core.Events;
|
|
using Umbraco.Cms.Core.Mapping;
|
|
using Umbraco.Cms.Core.Models;
|
|
using Umbraco.Cms.Core.Models.ContentEditing;
|
|
using Umbraco.Cms.Core.PropertyEditors;
|
|
using Umbraco.Cms.Core.Routing;
|
|
using Umbraco.Cms.Core.Scoping;
|
|
using Umbraco.Cms.Core.Security;
|
|
using Umbraco.Cms.Core.Serialization;
|
|
using Umbraco.Cms.Core.Services;
|
|
using Umbraco.Cms.Core.Strings;
|
|
using Umbraco.Cms.Infrastructure.Persistence;
|
|
using Umbraco.Cms.Tests.Common.Builders;
|
|
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
|
using Umbraco.Cms.Web.BackOffice.Controllers;
|
|
|
|
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers;
|
|
|
|
[TestFixture]
|
|
public class ContentControllerTests
|
|
{
|
|
[Test]
|
|
public void Root_Node_With_Domains_Causes_No_Warning()
|
|
{
|
|
// Setup domain service
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(1060, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/", "da-dk"), new UmbracoDomain("/en", "en-us") });
|
|
|
|
// Create content, we need to specify and ID in order to be able to configure domain service
|
|
var rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new[] { "en-us", "da-dk" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
var contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
|
|
Assert.IsEmpty(notifications.Notifications);
|
|
}
|
|
|
|
[Test]
|
|
public void Node_With_Single_Published_Culture_Causes_No_Warning()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(Enumerable.Empty<IDomain>());
|
|
|
|
var rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new[] { "da-dk" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
var contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
|
|
Assert.IsEmpty(notifications.Notifications);
|
|
}
|
|
|
|
[Test]
|
|
public void Root_Node_Without_Domains_Causes_SingleWarning()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(Enumerable.Empty<IDomain>());
|
|
|
|
var rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new[] { "en-us", "da-dk" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
var contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
[Test]
|
|
public void One_Warning_Per_Culture_Being_Published()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/", "da-dk") });
|
|
|
|
var rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new[] { "en-us", "da-dk", "nl-bk", "se-sv" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
var contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
Assert.AreEqual(3, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
[Test]
|
|
public void Ancestor_Domains_Counts()
|
|
{
|
|
var rootId = 1060;
|
|
var level1Id = 1061;
|
|
var level2Id = 1062;
|
|
var level3Id = 1063;
|
|
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(rootId, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/", "da-dk") });
|
|
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(level1Id, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/en", "en-us") });
|
|
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(level2Id, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/se", "se-sv"), new UmbracoDomain("/nl", "nl-bk") });
|
|
|
|
var level3Node = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(level3Id)
|
|
.WithPath($"-1,{rootId},{level1Id},{level2Id},{level3Id}")
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("se-sv")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("nl-bk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("de-de")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new[] { "en-us", "da-dk", "nl-bk", "se-sv", "de-de" };
|
|
|
|
var contentController = CreateContentController(domainServiceMock.Object);
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
contentController.AddDomainWarnings(level3Node, culturesPublished, notifications);
|
|
|
|
// We expect one error because all domains except "de-de" is registered somewhere in the ancestor path
|
|
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
[Test]
|
|
public void Only_Warns_About_Cultures_Being_Published()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/", "da-dk") });
|
|
|
|
var rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("se-sv")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("de-de")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new[] { "en-us", "se-sv" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
var contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
|
|
// We only get two errors, one for each culture being published, so no errors from previously published cultures.
|
|
Assert.AreEqual(2, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
private ContentController CreateContentController(IDomainService domainService)
|
|
{
|
|
// We have to configure ILocalizedTextService to return a new string every time Localize is called
|
|
// Otherwise it won't add the notification because it skips dupes
|
|
var localizedTextServiceMock = new Mock<ILocalizedTextService>();
|
|
localizedTextServiceMock.Setup(x => x.Localize(
|
|
It.IsAny<string>(),
|
|
It.IsAny<string>(),
|
|
It.IsAny<CultureInfo>(),
|
|
It.IsAny<IDictionary<string, string>>()))
|
|
.Returns(() => Guid.NewGuid().ToString());
|
|
|
|
var controller = new ContentController(
|
|
Mock.Of<ICultureDictionary>(),
|
|
NullLoggerFactory.Instance,
|
|
Mock.Of<IShortStringHelper>(),
|
|
Mock.Of<IEventMessagesFactory>(),
|
|
localizedTextServiceMock.Object,
|
|
new PropertyEditorCollection(new DataEditorCollection(() => null)),
|
|
Mock.Of<IContentService>(),
|
|
Mock.Of<IUserService>(),
|
|
Mock.Of<IBackOfficeSecurityAccessor>(),
|
|
Mock.Of<IContentTypeService>(),
|
|
Mock.Of<IUmbracoMapper>(),
|
|
Mock.Of<IPublishedUrlProvider>(),
|
|
domainService,
|
|
Mock.Of<IDataTypeService>(),
|
|
Mock.Of<ILocalizationService>(),
|
|
Mock.Of<IFileService>(),
|
|
Mock.Of<INotificationService>(),
|
|
new ActionCollection(() => null),
|
|
Mock.Of<ISqlContext>(),
|
|
Mock.Of<IJsonSerializer>(),
|
|
Mock.Of<ICoreScopeProvider>(),
|
|
Mock.Of<IAuthorizationService>(),
|
|
Mock.Of<IContentVersionService>(),
|
|
Mock.Of<ICultureImpactFactory>(),
|
|
Mock.Of<IUserGroupService>());
|
|
|
|
return controller;
|
|
}
|
|
|
|
private IContentType CreateContentType() =>
|
|
new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build();
|
|
}
|