diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index e70f323f1c..42d3319661 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -372,7 +372,7 @@ public class ContentService : RepositoryService, IContentService public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { // locking the content tree secures content types too scope.WriteLock(Constants.Locks.ContentTree); @@ -395,6 +395,8 @@ public class ContentService : RepositoryService, IContentService Save(content, userId); + scope.Complete(); + return content; } } @@ -416,7 +418,7 @@ public class ContentService : RepositoryService, IContentService throw new ArgumentNullException(nameof(parent)); } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { // locking the content tree secures content types too scope.WriteLock(Constants.Locks.ContentTree); @@ -431,6 +433,7 @@ public class ContentService : RepositoryService, IContentService Save(content, userId); + scope.Complete(); return content; } } @@ -508,10 +511,11 @@ public class ContentService : RepositoryService, IContentService /// public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.ContentTree); _documentRepository.PersistContentSchedule(content, contentSchedule); + scope.Complete(); } } @@ -1951,7 +1955,7 @@ public class ContentService : RepositoryService, IContentService cultures = new HashSet(); // empty means 'already published' } - if (isRoot || edited) + if (edited) { cultures.Add(c); // means 'republish this culture' } @@ -2106,13 +2110,11 @@ public class ContentService : RepositoryService, IContentService } // deal with the branch root - if it fails, abort - var rootPublishNotificationState = new Dictionary(); - PublishResult? rootResult = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, - publishedDocuments, eventMessages, userId, allLangs, rootPublishNotificationState); - if (rootResult != null) + PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary notificationState); + if (result != null) { - results.Add(rootResult); - if (!rootResult.Success) + results.Add(result); + if (!result.Success) { return results; } @@ -2125,7 +2127,6 @@ public class ContentService : RepositoryService, IContentService int count; var page = 0; const int pageSize = 100; - PublishResult? result = null; do { count = 0; @@ -2144,8 +2145,7 @@ public class ContentService : RepositoryService, IContentService } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, - publishedDocuments, eventMessages, userId, allLangs,null); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _); if (result != null) { results.Add(result); @@ -2169,12 +2169,7 @@ public class ContentService : RepositoryService, IContentService // (SaveAndPublishBranchOne does *not* do it) scope.Notifications.Publish( new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages)); - if (rootResult?.Success is true) - { - scope.Notifications.Publish( - new ContentPublishedNotification(rootResult!.Content!.Yield(), eventMessages, true) - .WithState(rootPublishNotificationState)); - } + scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages).WithState(notificationState)); scope.Complete(); } @@ -2185,9 +2180,6 @@ public class ContentService : RepositoryService, IContentService // shouldPublish: a function determining whether the document has changes that need to be published // note - 'force' is handled by 'editing' // publishValues: a function publishing values (using the appropriate PublishCulture calls) - /// Only set this when processing a the root of the branch - /// Published notification will not be send when this property is set - /// private PublishResult? SaveAndPublishBranchItem( ICoreScope scope, IContent document, @@ -2199,8 +2191,9 @@ public class ContentService : RepositoryService, IContentService EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs, - IDictionary? rootPublishingNotificationState) + out IDictionary notificationState) { + notificationState = new Dictionary(); HashSet? culturesToPublish = shouldPublish(document); // null = do not include @@ -2228,17 +2221,11 @@ public class ContentService : RepositoryService, IContentService return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - var notificationState = rootPublishingNotificationState ?? new Dictionary(); - PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, notificationState, userId, true, isRoot); - if (!result.Success) + PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, true, isRoot); + if (result.Success) { - return result; - } - - publishedDocuments.Add(document); - if (rootPublishingNotificationState == null) - { - scope.Notifications.Publish(new ContentPublishedNotification(result.Content!, evtMsgs).WithState(notificationState)); + publishedDocuments.Add(document); + notificationState = savingNotification.State; } return result; @@ -2982,7 +2969,7 @@ public class ContentService : RepositoryService, IContentService public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.ContentTree); @@ -2995,6 +2982,8 @@ public class ContentService : RepositoryService, IContentService scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); } + scope.Complete(); + return report; } } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 6f2b2ddef5..a645e6222e 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -354,7 +354,6 @@ public abstract class ContentTypeServiceBase : ContentTypeSe } using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { scope.ReadLock(ReadLockIds); return Repository.GetMany(ids.ToArray()); diff --git a/src/Umbraco.Core/Services/ContentVersionService.cs b/src/Umbraco.Core/Services/ContentVersionService.cs index 6f1c28deb8..1c190311da 100644 --- a/src/Umbraco.Core/Services/ContentVersionService.cs +++ b/src/Umbraco.Core/Services/ContentVersionService.cs @@ -68,7 +68,7 @@ internal class ContentVersionService : IContentVersionService /// public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = Constants.Security.SuperUserId) { - using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.ContentTree); _documentVersionRepository.SetPreventCleanup(versionId, preventCleanup); @@ -87,6 +87,7 @@ internal class ContentVersionService : IContentVersionService var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'"; Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}"); + scope.Complete(); } } @@ -120,7 +121,7 @@ internal class ContentVersionService : IContentVersionService * * tl;dr lots of scopes to enable other connections to use the DB whilst we work. */ - using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { IReadOnlyCollection? allHistoricVersions = _documentVersionRepository.GetDocumentVersionsEligibleForCleanup(); @@ -154,6 +155,8 @@ internal class ContentVersionService : IContentVersionService versionsToDelete.Add(version); } + + scope.Complete(); } if (!versionsToDelete.Any()) @@ -169,7 +172,7 @@ internal class ContentVersionService : IContentVersionService foreach (IEnumerable group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount)) { - using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.ContentTree); var groupEnumerated = group.ToList(); @@ -182,12 +185,16 @@ internal class ContentVersionService : IContentVersionService scope.Notifications.Publish( new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId)); } + + scope.Complete(); } } - using (_scopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy"); + + scope.Complete(); } return versionsToDelete; diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 99d8c225c3..c27c72a6d8 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -680,7 +680,7 @@ namespace Umbraco.Cms.Core.Services.Implement [Obsolete("Please use GetReferencesAsync. Will be deleted in V15.")] public IReadOnlyDictionary> GetReferences(int id) { - using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true); + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); return _dataTypeRepository.FindUsages(id); } diff --git a/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs index f51858fa5b..234dae683f 100644 --- a/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs +++ b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs @@ -33,7 +33,7 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy var theRest = new List(); - using (_scopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = _scopeProvider.CreateCoreScope()) { var policyOverrides = _documentVersionRepository.GetCleanupPolicies()? .ToDictionary(x => x.ContentTypeId); @@ -77,6 +77,8 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy yield return version; } } + + scope.Complete(); } } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 26731b9393..7aba44d16b 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1199,7 +1199,7 @@ namespace Umbraco.Cms.Core.Services public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.MediaTree); @@ -1212,6 +1212,7 @@ namespace Umbraco.Cms.Core.Services scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); } + scope.Complete(); return report; } } diff --git a/src/Umbraco.Core/Services/TwoFactorLoginService.cs b/src/Umbraco.Core/Services/TwoFactorLoginService.cs index 64da30b991..bcfa102d1f 100644 --- a/src/Umbraco.Core/Services/TwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/TwoFactorLoginService.cs @@ -42,8 +42,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService /// public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + using ICoreScope scope = _scopeProvider.CreateCoreScope(); await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey); + + scope.Complete(); } /// @@ -138,8 +140,12 @@ public class TwoFactorLoginService : ITwoFactorLoginService /// public async Task DisableAsync(Guid userOrMemberKey, string providerName) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName); + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + var result = await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName); + + scope.Complete(); + + return result; } /// @@ -156,9 +162,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService /// public Task SaveAsync(TwoFactorLogin twoFactorLogin) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + using ICoreScope scope = _scopeProvider.CreateCoreScope(); _twoFactorLoginRepository.Save(twoFactorLogin); + scope.Complete(); return Task.CompletedTask; } diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index 328cf5ee5c..da1fbaf157 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -106,7 +106,7 @@ public class ScheduledPublishing : RecurringHostedServiceBase // but then what should be its "scope"? could we attach it to scopes? // - and we should definitively *not* have to flush it here (should be auto) using UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext(); - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + using ICoreScope scope = _scopeProvider.CreateCoreScope(); /* We used to assume that there will never be two instances running concurrently where (IsMainDom && ServerRole == SchedulingPublisher) * However this is possible during an azure deployment slot swap for the SchedulingPublisher instance when trying to achieve zero downtime deployments. @@ -125,6 +125,8 @@ public class ScheduledPublishing : RecurringHostedServiceBase grouped.Count(), grouped.Key); } + + scope.Complete(); } finally { diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs index 96be41d6fe..53ecef211a 100644 --- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs +++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerConfig.cs @@ -1,6 +1,8 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; namespace Umbraco.Cms.Core.Logging.Viewer; @@ -10,6 +12,21 @@ public class LogViewerConfig : ILogViewerConfig private readonly ILogViewerQueryRepository _logViewerQueryRepository; private readonly IScopeProvider _scopeProvider; + [Obsolete("Use non-obsolete ctor. This will be removed in Umbraco 14.")] + public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, Umbraco.Cms.Core.Scoping.IScopeProvider scopeProvider) + : this(logViewerQueryRepository, StaticServiceProvider.Instance.GetRequiredService()) + { + + } + + //Temp ctor used by MSDI (Greedy) + [Obsolete("Use non-obsolete ctor. This will be removed in Umbraco 14.")] + public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, Umbraco.Cms.Core.Scoping.IScopeProvider coreScopeProvider, IScopeProvider scopeProvider) + : this(logViewerQueryRepository, scopeProvider) + { + + } + public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider) { _logViewerQueryRepository = logViewerQueryRepository; @@ -28,9 +45,10 @@ public class LogViewerConfig : ILogViewerConfig [Obsolete("Use ILogViewerService.AddSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")] public IReadOnlyList AddSavedSearch(string name, string query) { - using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + using IScope scope = _scopeProvider.CreateScope(); _logViewerQueryRepository.Save(new LogViewerQuery(name, query)); + scope.Complete(); return GetSavedSearches(); } @@ -40,7 +58,7 @@ public class LogViewerConfig : ILogViewerConfig [Obsolete("Use ILogViewerService.DeleteSavedLogQueryAsync instead. Scheduled for removal in Umbraco 15.")] public IReadOnlyList DeleteSavedSearch(string name) { - using IScope scope = _scopeProvider.CreateScope(autoComplete: true); + using IScope scope = _scopeProvider.CreateScope(); ILogViewerQuery? item = _logViewerQueryRepository.GetByName(name); if (item is not null) @@ -49,6 +67,8 @@ public class LogViewerConfig : ILogViewerConfig } // Return the updated object - so we can instantly reset the entire array from the API response - return GetSavedSearches(); + IReadOnlyList result = GetSavedSearches(); + scope.Complete(); + return result; } } diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 6e1931997a..fcc87dacf3 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -96,7 +96,7 @@ public class MemberUserStore : UmbracoUserStore 0) { + // the external user might actually be signed in at this point, but certain errors (i.e. missing claims) + // prevents us from applying said user to a back-office session. make sure the sign-in manager does not + // report the user as being signed in for subsequent requests. + await _signInManager.SignOutAsync(); + ViewData.SetExternalSignInProviderErrors( new BackOfficeExternalLoginProviderErrors( loginInfo.LoginProvider, diff --git a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index e59d3166bb..2e48fbf25d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -188,15 +188,41 @@ public class ExamineManagementController : UmbracoAuthorizedJsonController private ExamineIndexModel CreateModel(IIndex index) { var indexName = index.Name; - IIndexDiagnostics indexDiag = _indexDiagnosticsFactory.Create(index); - Attempt isHealth = indexDiag.IsHealthy(); + var healthResult = isHealth.Result; + + long documentCount; + int fieldCount; + + try + { + // This will throw if the index is corrupted - i.e. a file in the index folder cannot be found + // Which will break the UI and not give the possibility to rebuild the index + documentCount = indexDiag.GetDocumentCount(); + fieldCount = indexDiag.GetFieldNames().Count(); + } + catch (FileNotFoundException ex) + { + // Safe catch that will allow to rebuild a corrupted index + documentCount = 0; + fieldCount = 0; + + _logger.LogWarning(ex, "{name} is corrupted.", indexName); + + if (!string.IsNullOrWhiteSpace(healthResult)) + { + healthResult += " "; + } + + // Provide a useful message in the Examine dashboard + healthResult += $"It may not be possible to rebuild the index. Please try deleting the entire {indexName} folder and then attempt to rebuild it again."; + } var properties = new Dictionary { - ["DocumentCount"] = indexDiag.GetDocumentCount(), - ["FieldCount"] = indexDiag.GetFieldNames().Count() + ["DocumentCount"] = documentCount, + ["FieldCount"] = fieldCount }; foreach (KeyValuePair p in indexDiag.Metadata) @@ -207,7 +233,7 @@ public class ExamineManagementController : UmbracoAuthorizedJsonController var indexerModel = new ExamineIndexModel { Name = indexName, - HealthStatus = isHealth.Success ? isHealth.Result ?? "Healthy" : isHealth.Result ?? "Unhealthy", + HealthStatus = isHealth.Success ? healthResult ?? "Healthy" : healthResult ?? "Unhealthy", ProviderProperties = properties, CanRebuild = _indexRebuilder.CanRebuild(index.Name) }; diff --git a/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs b/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs index 41286f7dba..22d5f7e3e2 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbProfileController.cs @@ -102,7 +102,7 @@ public class UmbProfileController : SurfaceController private async Task UpdateMemberAsync(ProfileModel model, MemberIdentityUser currentMember) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + using ICoreScope scope = _scopeProvider.CreateCoreScope(); currentMember.Email = model.Email; currentMember.Name = model.Name; @@ -140,6 +140,7 @@ public class UmbProfileController : SurfaceController _memberService.Save(member); + scope.Complete(); return saveResult; } } diff --git a/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs b/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs index 493dd624d1..3fb2a966c4 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbRegisterController.cs @@ -118,7 +118,7 @@ public class UmbRegisterController : SurfaceController /// Result of registration operation. private async Task RegisterMemberAsync(RegisterModel model) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + using ICoreScope scope = _scopeProvider.CreateCoreScope(); // U4-10762 Server error with "Register Member" snippet (Cannot save member with empty name) // If name field is empty, add the email address instead. @@ -160,6 +160,8 @@ public class UmbRegisterController : SurfaceController } } + scope.Complete(); + return identityResult; } } diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 4608c7278d..a735ba8f7c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -17,7 +17,7 @@ "xhr2": "^0.2.1" }, "devDependencies": { - "@playwright/test": "^1.35", + "@playwright/test": "^1.38", "del": "^6.0.0", "ncp": "^2.0.0", "prompt": "^1.2.0", @@ -86,22 +86,18 @@ } }, "node_modules/@playwright/test": { - "version": "1.35.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz", - "integrity": "sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", + "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.35.1" + "playwright": "1.38.0" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" } }, "node_modules/@sideway/address": { @@ -744,10 +740,28 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", + "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.0" + }, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/playwright-core": { - "version": "1.35.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz", - "integrity": "sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", + "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -988,5 +1002,718 @@ "node": ">= 6" } } + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@playwright/test": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", + "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "dev": true, + "requires": { + "playwright": "1.38.0" + } + }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "@types/node": { + "version": "14.17.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.33.tgz", + "integrity": "sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g==", + "dev": true + }, + "@umbraco/json-models-builders": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", + "integrity": "sha512-bXwfXcpuqG1Ye714L9KJEGXuSzJfckysE/6CuPjdG8FqHWTE1brv28teR2oMw+ih8ca2u2zUboRgdzLEU/1D3Q==", + "requires": { + "camelize": "^1.0.0", + "faker": "^4.1.0" + } + }, + "@umbraco/playwright-testhelpers": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.25.tgz", + "integrity": "sha512-6H452J6LhP0EHjF4jR7V7i0U8WPTiAbSyhN1J459BbbYEJ4QX1A2ZlCdA6VSBAsK1xYdMXD+yxsVJq7AAwiy9A==", + "requires": { + "@umbraco/json-models-builders": "^1.0.6", + "camelize": "^1.0.0", + "faker": "^4.1.0", + "form-data": "^4.0.0", + "node-fetch": "^2.6.7", + "xhr2": "^0.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.7" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true + }, + "del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dotenv": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", + "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true + }, + "faker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", + "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "joi": { + "version": "17.6.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.3.tgz", + "integrity": "sha512-YlQsIaS9MHYekzf1Qe11LjTkNzx9qhYluK3172z38RxYoAUf82XMX1p1DG1H4Wtk2ED/vPdSn9OggqtDu+aTow==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "requires": { + "mime-db": "1.51.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "playwright": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", + "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.38.0" + } + }, + "playwright-core": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", + "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", + "dev": true + }, + "prompt": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", + "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "async": "3.2.3", + "read": "1.0.x", + "revalidator": "0.1.x", + "winston": "2.x" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true + }, + "wait-on": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", + "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "dev": true, + "requires": { + "axios": "^0.25.0", + "joi": "^17.6.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.5.4" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "winston": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", + "integrity": "sha512-J5Zu4p0tojLde8mIOyDSsmLmcP8I3Z6wtwpTDHx1+hGcdhxcJaAmG4CFtagkb+NiN1M9Ek4b42pzMWqfc9jm8w==", + "dev": true, + "requires": { + "async": "^3.2.3", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "xhr2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==" + } } } diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index e13dc2591d..5d861a0ae6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -10,7 +10,9 @@ "createTest": "node createTest.js" }, "devDependencies": { - "@playwright/test": "^1.35", + "@playwright/test": "^1.38", + "typescript": "^4.8.3", + "tslib": "^2.4.0", "del": "^6.0.0", "ncp": "^2.0.0", "prompt": "^1.2.0", diff --git a/tests/Umbraco.Tests.Common/TestHelperBase.cs b/tests/Umbraco.Tests.Common/TestHelperBase.cs index aa896bb7f0..844a25e8c6 100644 --- a/tests/Umbraco.Tests.Common/TestHelperBase.cs +++ b/tests/Umbraco.Tests.Common/TestHelperBase.cs @@ -7,6 +7,7 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; @@ -14,6 +15,8 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Diagnostics; +using Umbraco.Cms.Core.DistributedLocking; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; @@ -24,6 +27,8 @@ using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Infrastructure.Serialization; using Umbraco.Cms.Tests.Common.TestHelpers; using Umbraco.Extensions; @@ -76,6 +81,61 @@ public abstract class TestHelperBase public IShortStringHelper ShortStringHelper { get; } = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + public IScopeProvider ScopeProvider + { + get + { + var loggerFactory = NullLoggerFactory.Instance; + var fileSystems = new FileSystems( + loggerFactory, + Mock.Of(), + Mock.Of>(), + Mock.Of()); + var mediaFileManager = new MediaFileManager( + Mock.Of(), + Mock.Of(), + loggerFactory.CreateLogger(), + Mock.Of(), + Mock.Of(), + Options.Create(new ContentSettings())); + var databaseFactory = new Mock(); + var database = new Mock(); + var sqlContext = new Mock(); + + var lockingMechanism = new Mock(); + lockingMechanism.Setup(x => x.ReadLock(It.IsAny(), It.IsAny())) + .Returns(Mock.Of()); + lockingMechanism.Setup(x => x.WriteLock(It.IsAny(), It.IsAny())) + .Returns(Mock.Of()); + + var lockingMechanismFactory = new Mock(); + lockingMechanismFactory.Setup(x => x.DistributedLockingMechanism) + .Returns(lockingMechanism.Object); + + // Setup mock of database factory to return mock of database. + databaseFactory.Setup(x => x.CreateDatabase()).Returns(database.Object); + databaseFactory.Setup(x => x.SqlContext).Returns(sqlContext.Object); + + // Setup mock of database to return mock of sql SqlContext + database.Setup(x => x.SqlContext).Returns(sqlContext.Object); + + var syntaxProviderMock = new Mock(); + + // Setup mock of ISqlContext to return syntaxProviderMock + sqlContext.Setup(x => x.SqlSyntax).Returns(syntaxProviderMock.Object); + + return new ScopeProvider( + new AmbientScopeStack(), + new AmbientScopeContextStack(), + lockingMechanismFactory.Object, + databaseFactory.Object, + fileSystems, + new TestOptionsMonitor(new CoreDebugSettings()), + mediaFileManager, + loggerFactory, + Mock.Of()); + } + } public IJsonSerializer JsonSerializer { get; } = new JsonNetSerializer(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs index 023cde3625..2f74c6c979 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs @@ -92,7 +92,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. + PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready); @@ -139,7 +139,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. + PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublish, @@ -184,7 +184,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest var r = ContentService.SaveAndPublishBranch(vRoot, false) .ToArray(); // no culture specified so "*" is used, so all cultures - Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. + Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result); } @@ -220,7 +220,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest var saveResult = ContentService.Save(iv1); var r = ContentService.SaveAndPublishBranch(vRoot, false, "de").ToArray(); - Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. + Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result); } @@ -380,7 +380,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. + PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublish, PublishResultType.SuccessPublishCulture); @@ -406,7 +406,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest AssertPublishResults( r, x => x.Result, - PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it. + PublishResultType.SuccessPublishAlready, PublishResultType.SuccessPublish, PublishResultType.SuccessPublishCulture); diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index 81e027b7c1..4468587866 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -31,6 +31,7 @@ using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Runtime; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Mail; @@ -42,6 +43,7 @@ using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Extensions; using File = System.IO.File; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; +using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; namespace Umbraco.Cms.Tests.UnitTests.TestHelpers; @@ -58,6 +60,8 @@ public static class TestHelper /// The assembly directory. public static string WorkingDirectory => s_testHelperInternal.WorkingDirectory; + public static IScopeProvider ScopeProvider => s_testHelperInternal.ScopeProvider; + public static ICoreScopeProvider CoreScopeProvider => s_testHelperInternal.ScopeProvider; public static IShortStringHelper ShortStringHelper => s_testHelperInternal.ShortStringHelper; public static IJsonSerializer JsonSerializer => s_testHelperInternal.JsonSerializer; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheVarianceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheVarianceTests.cs new file mode 100644 index 0000000000..72a68b443c --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheVarianceTests.cs @@ -0,0 +1,381 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Property = Umbraco.Cms.Infrastructure.PublishedCache.Property; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Published; + +[TestFixture] +public class PropertyCacheVarianceTests +{ + // This class tests various permutations of property value calculation across variance types and cache levels. + // + // Properties contain different "value levels", all of which are cached: + // 1. The source value => the "raw" value from the client side editor (it can be different, but it's easiest to think of it like that). + // 2. The intermediate value => a "temporary" value that is used to calculate the various "final" values. + // 3. The object value => the "final" object value that is exposed in an IPublishedElement output. + // 4. The XPath value => a legacy "final" value, don't think too hard on it. + // 3. The delivery API object value => the "final" object value that is exposed in the Delivery API. + // + // Property values are cached based on a few rules: + // 1. The property type variation and the parent content type variation determines how the intermediate value is cached. + // The effective property variation is a product of both variations, meaning the property type and the content type + // variations are combined in an OR. + // The rules are as follows: + // - ContentVariation.Nothing => the intermediate value is calculated once and reused across all variants (cultures and segments). + // - ContentVariation.Culture => the intermediate value is calculated per culture and reused across all segments. + // - ContentVariation.Segment => the intermediate value is calculated per segment and reused across all cultures. + // - ContentVariation.CultureAndSegment => the intermediate value is calculated for all invoked culture and segment combinations. + // 2. The property type cache level (which is usually derived from the property value converter). + // - PropertyCacheLevel.Element => the final values are cached until the parent content item is updated. + // - PropertyCacheLevel.Elements => the final values are cached until the _any_ content item is updated. + // - PropertyCacheLevel.Snapshot => the final values are cached for the duration of the active cache snapshot (i.e. until the end of the current request). + // - PropertyCacheLevel.None => the final values are never cached and will be re-calculated each time they're requested. + + // ### Invariant content type + invariant property type ### + [TestCase( + ContentVariation.Nothing, + ContentVariation.Nothing, + PropertyCacheLevel.Element, + // no variation => the intermediate value is calculated only once + // cache level => the final value is calculated only once + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Nothing, + ContentVariation.Nothing, + PropertyCacheLevel.Elements, + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Nothing, + ContentVariation.Nothing, + PropertyCacheLevel.Snapshot, + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Nothing, + ContentVariation.Nothing, + PropertyCacheLevel.None, + // no variation => the intermediate value is calculated once + // no cache => the final value is calculated for each request (reflects both changes in culture and segments) + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (da-DK:segment1)", + "en-US:segment2 (da-DK:segment1)", + "da-DK:segment2 (da-DK:segment1)")] + // ### Culture variant content type + invariant property type ### + [TestCase( + ContentVariation.Culture, + ContentVariation.Nothing, + PropertyCacheLevel.Element, + // culture variation => the intermediate value is calculated per culture (ignores segment changes until a culture changes) + // cache level => the final value is calculated only once per culture (ignores segment changes until a culture changes) + // NOTE: in this test, culture changes before segment, so the updated segment is never reflected here + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment1 (en-US:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Culture, + ContentVariation.Nothing, + PropertyCacheLevel.Elements, + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment1 (en-US:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Culture, + ContentVariation.Nothing, + PropertyCacheLevel.Snapshot, + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment1 (en-US:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Culture, + ContentVariation.Nothing, + PropertyCacheLevel.None, + // culture variation => the intermediate value is calculated per culture (ignores segment changes until a culture changes) + // no cache => the final value is calculated for each request (reflects both changes in culture and segments) + // NOTE: in this test, culture changes before segment, so the updated segment is never reflected in the intermediate value here + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment1)", + "da-DK:segment2 (da-DK:segment1)")] + // NOTE: As the tests above show, cache levels Element, Elements and Snapshot all yield the same values in this + // test, because we are efficiently executing the test in a snapshot. From here on out we're only building + // test cases for Element and None. + // ### Segment variant content type + invariant property type ### + [TestCase( + ContentVariation.Segment, + ContentVariation.Nothing, + PropertyCacheLevel.Element, + // segment variation => the intermediate value is calculated per segment (ignores culture changes until a segment changes) + // cache level => the final value is calculated only once per segment (ignores culture changes until a segment changes) + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment2 (en-US:segment2)", + "en-US:segment2 (en-US:segment2)")] + [TestCase( + ContentVariation.Segment, + ContentVariation.Nothing, + PropertyCacheLevel.None, + // segment variation => the intermediate value is calculated per segment (ignores culture changes until a segment changes) + // no cache => the final value is calculated for each request (reflects both changes in culture and segments) + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (da-DK:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (en-US:segment2)")] + // ### Culture and segment variant content type + invariant property type ### + [TestCase( + ContentVariation.CultureAndSegment, + ContentVariation.Nothing, + PropertyCacheLevel.Element, + // culture and segment variation => the intermediate value is calculated per culture and segment + // cache level => the final value is calculated only once per culture and segment (efficiently on every request in this test) + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + [TestCase( + ContentVariation.CultureAndSegment, + ContentVariation.Nothing, + PropertyCacheLevel.None, + // culture and segment variation => the intermediate value is calculated per culture and segment + // no cache => the final value is calculated for each request + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + // ### Invariant content type + culture variant property type ### + [TestCase( + ContentVariation.Nothing, + ContentVariation.Culture, + PropertyCacheLevel.Element, + // same behaviour as culture variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment1 (en-US:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Nothing, + ContentVariation.Culture, + PropertyCacheLevel.None, + // same behaviour as culture variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment1)", + "da-DK:segment2 (da-DK:segment1)")] + // ### Invariant content type + segment variant property type ### + [TestCase( + ContentVariation.Nothing, + ContentVariation.Segment, + PropertyCacheLevel.Element, + // same behaviour as segment variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment2 (en-US:segment2)", + "en-US:segment2 (en-US:segment2)")] + [TestCase( + ContentVariation.Nothing, + ContentVariation.Segment, + PropertyCacheLevel.None, + // same behaviour as segment variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (da-DK:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (en-US:segment2)")] + // ### Invariant content type + culture and segment variant property type ### + [TestCase( + ContentVariation.Nothing, + ContentVariation.CultureAndSegment, + PropertyCacheLevel.Element, + // same behaviour as culture and segment variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + [TestCase( + ContentVariation.Nothing, + ContentVariation.CultureAndSegment, + PropertyCacheLevel.None, + // same behaviour as culture and segment variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + // ### Culture variant content type + segment variant property type ### + [TestCase( + ContentVariation.Culture, + ContentVariation.Segment, + PropertyCacheLevel.Element, + // same behaviour as culture and segment variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + [TestCase( + ContentVariation.Culture, + ContentVariation.Segment, + PropertyCacheLevel.None, + // same behaviour as culture and segment variation on content type + no variation on property type, see comments above + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + public void ContentType_PropertyType_Variation_Cache_Values( + ContentVariation contentTypeVariation, + ContentVariation propertyTypeVariation, + PropertyCacheLevel propertyCacheLevel, + string expectedValue1DaDkSegment1, + string expectedValue2EnUsSegment1, + string expectedValue3EnUsSegment2, + string expectedValue4DaDkSegment2) + { + var variationContextCulture = "da-DK"; + var variationContextSegment = "segment1"; + var property = CreateProperty( + contentTypeVariation, + propertyTypeVariation, + propertyCacheLevel, + () => variationContextCulture, + () => variationContextSegment); + + Assert.AreEqual(expectedValue1DaDkSegment1, property.GetValue()); + + variationContextCulture = "en-US"; + Assert.AreEqual(expectedValue2EnUsSegment1, property.GetValue()); + + variationContextSegment = "segment2"; + Assert.AreEqual(expectedValue3EnUsSegment2, property.GetValue()); + + variationContextCulture = "da-DK"; + Assert.AreEqual(expectedValue4DaDkSegment2, property.GetValue()); + } + + [TestCase( + ContentVariation.Culture, + ContentVariation.Nothing, + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment1 (en-US:segment1)", + "da-DK:segment1 (da-DK:segment1)")] + [TestCase( + ContentVariation.Segment, + ContentVariation.Nothing, + "da-DK:segment1 (da-DK:segment1)", + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment2 (en-US:segment2)", + "en-US:segment2 (en-US:segment2)")] + [TestCase( + ContentVariation.Culture, + ContentVariation.Segment, + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + [TestCase( + ContentVariation.CultureAndSegment, + ContentVariation.Nothing, + "da-DK:segment1 (da-DK:segment1)", + "en-US:segment1 (en-US:segment1)", + "en-US:segment2 (en-US:segment2)", + "da-DK:segment2 (da-DK:segment2)")] + public void ContentType_PropertyType_Variation_Are_Interchangeable( + ContentVariation variation1, + ContentVariation variation2, + string expectedValue1DaDkSegment1, + string expectedValue2EnUsSegment1, + string expectedValue3EnUsSegment2, + string expectedValue4DaDkSegment2) + { + var scenarios = new[] + { + new { ContentTypeVariation = variation1, PropertyTypeVariation = variation2 }, + new { ContentTypeVariation = variation2, PropertyTypeVariation = variation1 } + }; + + foreach (var scenario in scenarios) + { + var variationContextCulture = "da-DK"; + var variationContextSegment = "segment1"; + var property = CreateProperty( + scenario.ContentTypeVariation, + scenario.PropertyTypeVariation, + PropertyCacheLevel.Element, + () => variationContextCulture, + () => variationContextSegment); + + Assert.AreEqual(expectedValue1DaDkSegment1, property.GetValue()); + + variationContextCulture = "en-US"; + Assert.AreEqual(expectedValue2EnUsSegment1, property.GetValue()); + + variationContextSegment = "segment2"; + Assert.AreEqual(expectedValue3EnUsSegment2, property.GetValue()); + + variationContextCulture = "da-DK"; + Assert.AreEqual(expectedValue4DaDkSegment2, property.GetValue()); + } + } + + /// + /// Creates a new property with a mocked publishedSnapshotAccessor that uses a VariationContext that reads culture and segment information from the passed in functions. + /// + private Property CreateProperty(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation, PropertyCacheLevel propertyTypeCacheLevel, Func getCulture, Func getSegment) + { + var contentType = new Mock(); + contentType.SetupGet(c => c.PropertyTypes).Returns(Array.Empty()); + contentType.SetupGet(c => c.Variations).Returns(contentTypeVariation); + + var contentNode = new ContentNode(123, Guid.NewGuid(), contentType.Object, 1, string.Empty, 1, 1, DateTime.Now, 1); + var contentData = new ContentData("bla", "bla", 1, DateTime.Now, 1, 1, true, new Dictionary(), null); + + var elementCache = new FastDictionaryAppCache(); + var snapshotCache = new FastDictionaryAppCache(); + var publishedSnapshotMock = new Mock(); + publishedSnapshotMock.SetupGet(p => p.ElementsCache).Returns(elementCache); + publishedSnapshotMock.SetupGet(p => p.SnapshotCache).Returns(snapshotCache); + + var publishedSnapshot = publishedSnapshotMock.Object; + var publishedSnapshotAccessor = new Mock(); + publishedSnapshotAccessor.Setup(p => p.TryGetPublishedSnapshot(out publishedSnapshot)).Returns(true); + + var variationContextAccessorMock = new Mock(); + variationContextAccessorMock + .SetupGet(mock => mock.VariationContext) + .Returns(() => new VariationContext(getCulture(), getSegment())); + + var content = new PublishedContent( + contentNode, + contentData, + publishedSnapshotAccessor.Object, + variationContextAccessorMock.Object, + Mock.Of()); + + var propertyType = new Mock(); + propertyType.SetupGet(p => p.CacheLevel).Returns(propertyTypeCacheLevel); + propertyType.SetupGet(p => p.DeliveryApiCacheLevel).Returns(propertyTypeCacheLevel); + propertyType.SetupGet(p => p.Variations).Returns(propertyTypeVariation); + propertyType + .Setup(p => p.ConvertSourceToInter(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(() => $"{getCulture()}:{getSegment()}"); + propertyType + .Setup(p => p.ConvertInterToObject(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((IPublishedElement _, PropertyCacheLevel _, object? inter, bool _) => $"{getCulture()}:{getSegment()} ({inter})" ); + + return new Property(propertyType.Object, content, publishedSnapshotAccessor.Object); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs index 65307cd143..a27ca71058 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs @@ -48,7 +48,7 @@ public class LogviewerTests File.Copy(exampleLogfilePath, _newLogfilePath, true); var logger = Mock.Of>(); - var logViewerConfig = new LogViewerConfig(LogViewerQueryRepository, Mock.Of()); + var logViewerConfig = new LogViewerConfig(LogViewerQueryRepository, TestHelper.ScopeProvider); var logLevelLoader = Mock.Of(); _logViewer = new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, logLevelLoader, Log.Logger); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs index ef74846db6..20f908a359 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberManagerTests.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; using Umbraco.Cms.Web.Common.Security; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security; @@ -33,7 +34,7 @@ public class MemberManagerTests public MemberManager CreateSut() { - var scopeProvider = new Mock().Object; + var scopeProvider = TestHelper.ScopeProvider; _mockMemberService = new Mock(); var mapDefinitions = new List diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs index 7032876a66..87e0f17dff 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Tests.UnitTests.TestHelpers; using Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper; using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; @@ -27,23 +28,12 @@ public class MemberUserStoreTests public MemberUserStore CreateSut() { _mockMemberService = new Mock(); - var mockScope = new Mock(); - var mockScopeProvider = new Mock(); - mockScopeProvider - .Setup(x => x.CreateScope( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(mockScope.Object); + var mockScopeProvider = TestHelper.ScopeProvider; return new MemberUserStore( _mockMemberService.Object, - new UmbracoMapper(new MapDefinitionCollection(() => new List()), mockScopeProvider.Object, NullLogger.Instance), - mockScopeProvider.Object, + new UmbracoMapper(new MapDefinitionCollection(() => new List()), mockScopeProvider, NullLogger.Instance), + mockScopeProvider, new IdentityErrorDescriber(), Mock.Of(), Mock.Of(),