Merge remote-tracking branch 'origin/v13/dev' into v14/dev

# Conflicts:
#	tests/Umbraco.Tests.AcceptanceTest/package-lock.json
#	tests/Umbraco.Tests.AcceptanceTest/package.json
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Datatype/BlockGridEditorDataTypeBlocks.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorDocument.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/content.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/routing.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/partialViews.spec.ts
This commit is contained in:
Bjarke Berg
2023-10-10 13:47:18 +02:00
24 changed files with 1326 additions and 96 deletions

View File

@@ -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
/// <inheritdoc />
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<string>(); // empty means 'already published'
}
if (isRoot || edited)
if (edited)
{
cultures.Add(c); // <culture> 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<string, object?>();
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<string, object?> 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)
/// <param name="rootPublishingNotificationState">Only set this when processing a the root of the branch
/// Published notification will not be send when this property is set</param>
/// <returns></returns>
private PublishResult? SaveAndPublishBranchItem(
ICoreScope scope,
IContent document,
@@ -2199,8 +2191,9 @@ public class ContentService : RepositoryService, IContentService
EventMessages evtMsgs,
int userId,
IReadOnlyCollection<ILanguage> allLangs,
IDictionary<string, object?>? rootPublishingNotificationState)
out IDictionary<string, object?> notificationState)
{
notificationState = new Dictionary<string, object?>();
HashSet<string>? 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<string, object?>();
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;
}
}

View File

@@ -354,7 +354,6 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
scope.ReadLock(ReadLockIds);
return Repository.GetMany(ids.ToArray());

View File

@@ -68,7 +68,7 @@ internal class ContentVersionService : IContentVersionService
/// <inheritdoc />
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<ContentVersionMeta>? 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<ContentVersionMeta> 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;

View File

@@ -680,7 +680,7 @@ namespace Umbraco.Cms.Core.Services.Implement
[Obsolete("Please use GetReferencesAsync. Will be deleted in V15.")]
public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true);
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return _dataTypeRepository.FindUsages(id);
}

View File

@@ -33,7 +33,7 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
var theRest = new List<ContentVersionMeta>();
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();
}
}

View File

@@ -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;
}
}

View File

@@ -42,8 +42,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService
/// <inheritdoc />
public async Task DeleteUserLoginsAsync(Guid userOrMemberKey)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey);
scope.Complete();
}
/// <inheritdoc />
@@ -138,8 +140,12 @@ public class TwoFactorLoginService : ITwoFactorLoginService
/// <inheritdoc />
public async Task<bool> 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;
}
/// <inheritdoc />
@@ -156,9 +162,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService
/// <inheritdoc />
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;
}

View File

@@ -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
{

View File

@@ -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<IScopeProvider>())
{
}
//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<SavedLogSearch> 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<SavedLogSearch> 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<SavedLogSearch> result = GetSavedSearches();
scope.Complete();
return result;
}
}

View File

@@ -96,7 +96,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
throw new ArgumentNullException(nameof(user));
}
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
// create member
IMember memberEntity = _memberService.CreateMember(
@@ -150,6 +150,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
x.Value)));
}
scope.Complete();
return Task.FromResult(IdentityResult.Success);
}
catch (Exception ex)
@@ -179,7 +180,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
}
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
using ICoreScope scope = _scopeProvider.CreateCoreScope();
IMember? found = _memberService.GetById(asInt);
if (found != null)
@@ -220,6 +221,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
}
}
scope.Complete();
return Task.FromResult(IdentityResult.Success);
}
catch (Exception ex)

View File

@@ -86,7 +86,9 @@ internal class Property : PublishedPropertyBase
_isPreviewing = content.IsPreviewing;
_isMember = content.ContentType.ItemType == PublishedItemType.Member;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_variations = propertyType.Variations;
// this variable is used for contextualizing the variation level when calculating property values.
// it must be set to the union of variance (the combination of content type and property type variance).
_variations = propertyType.Variations | content.ContentType.Variations;
}
// clone for previewing as draft a published content that is published and has no draft

View File

@@ -573,6 +573,11 @@ public class BackOfficeController : UmbracoController
if (errors.Count > 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,

View File

@@ -188,15 +188,41 @@ public class ExamineManagementController : UmbracoAuthorizedJsonController
private ExamineIndexModel CreateModel(IIndex index)
{
var indexName = index.Name;
IIndexDiagnostics indexDiag = _indexDiagnosticsFactory.Create(index);
Attempt<string?> 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<string, object?>
{
["DocumentCount"] = indexDiag.GetDocumentCount(),
["FieldCount"] = indexDiag.GetFieldNames().Count()
["DocumentCount"] = documentCount,
["FieldCount"] = fieldCount
};
foreach (KeyValuePair<string, object?> 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)
};

View File

@@ -102,7 +102,7 @@ public class UmbProfileController : SurfaceController
private async Task<IdentityResult> 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;
}
}

View File

@@ -118,7 +118,7 @@ public class UmbRegisterController : SurfaceController
/// <returns>Result of registration operation.</returns>
private async Task<IdentityResult> 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;
}
}

View File

@@ -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=="
}
}
}

View File

@@ -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",

View File

@@ -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<IIOHelper>(),
Mock.Of<IOptions<GlobalSettings>>(),
Mock.Of<IHostingEnvironment>());
var mediaFileManager = new MediaFileManager(
Mock.Of<IFileSystem>(),
Mock.Of<IMediaPathScheme>(),
loggerFactory.CreateLogger<MediaFileManager>(),
Mock.Of<IShortStringHelper>(),
Mock.Of<IServiceProvider>(),
Options.Create(new ContentSettings()));
var databaseFactory = new Mock<IUmbracoDatabaseFactory>();
var database = new Mock<IUmbracoDatabase>();
var sqlContext = new Mock<ISqlContext>();
var lockingMechanism = new Mock<IDistributedLockingMechanism>();
lockingMechanism.Setup(x => x.ReadLock(It.IsAny<int>(), It.IsAny<TimeSpan?>()))
.Returns(Mock.Of<IDistributedLock>());
lockingMechanism.Setup(x => x.WriteLock(It.IsAny<int>(), It.IsAny<TimeSpan?>()))
.Returns(Mock.Of<IDistributedLock>());
var lockingMechanismFactory = new Mock<IDistributedLockingMechanismFactory>();
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<ISqlSyntaxProvider>();
// 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<CoreDebugSettings>(new CoreDebugSettings()),
mediaFileManager,
loggerFactory,
Mock.Of<IEventAggregator>());
}
}
public IJsonSerializer JsonSerializer { get; } = new JsonNetSerializer();

View File

@@ -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);

View File

@@ -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
/// <value>The assembly directory.</value>
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;

View File

@@ -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());
}
}
/// <summary>
/// Creates a new property with a mocked publishedSnapshotAccessor that uses a VariationContext that reads culture and segment information from the passed in functions.
/// </summary>
private Property CreateProperty(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation, PropertyCacheLevel propertyTypeCacheLevel, Func<string> getCulture, Func<string> getSegment)
{
var contentType = new Mock<IPublishedContentType>();
contentType.SetupGet(c => c.PropertyTypes).Returns(Array.Empty<IPublishedPropertyType>());
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<string, PropertyData[]>(), null);
var elementCache = new FastDictionaryAppCache();
var snapshotCache = new FastDictionaryAppCache();
var publishedSnapshotMock = new Mock<IPublishedSnapshot>();
publishedSnapshotMock.SetupGet(p => p.ElementsCache).Returns(elementCache);
publishedSnapshotMock.SetupGet(p => p.SnapshotCache).Returns(snapshotCache);
var publishedSnapshot = publishedSnapshotMock.Object;
var publishedSnapshotAccessor = new Mock<IPublishedSnapshotAccessor>();
publishedSnapshotAccessor.Setup(p => p.TryGetPublishedSnapshot(out publishedSnapshot)).Returns(true);
var variationContextAccessorMock = new Mock<IVariationContextAccessor>();
variationContextAccessorMock
.SetupGet(mock => mock.VariationContext)
.Returns(() => new VariationContext(getCulture(), getSegment()));
var content = new PublishedContent(
contentNode,
contentData,
publishedSnapshotAccessor.Object,
variationContextAccessorMock.Object,
Mock.Of<IPublishedModelFactory>());
var propertyType = new Mock<IPublishedPropertyType>();
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<IPublishedElement>(), It.IsAny<object?>(), It.IsAny<bool>()))
.Returns(() => $"{getCulture()}:{getSegment()}");
propertyType
.Setup(p => p.ConvertInterToObject(It.IsAny<IPublishedElement>(), It.IsAny<PropertyCacheLevel>(), It.IsAny<object?>(), It.IsAny<bool>()))
.Returns((IPublishedElement _, PropertyCacheLevel _, object? inter, bool _) => $"{getCulture()}:{getSegment()} ({inter})" );
return new Property(propertyType.Object, content, publishedSnapshotAccessor.Object);
}
}

View File

@@ -48,7 +48,7 @@ public class LogviewerTests
File.Copy(exampleLogfilePath, _newLogfilePath, true);
var logger = Mock.Of<ILogger<SerilogJsonLogViewer>>();
var logViewerConfig = new LogViewerConfig(LogViewerQueryRepository, Mock.Of<IScopeProvider>());
var logViewerConfig = new LogViewerConfig(LogViewerQueryRepository, TestHelper.ScopeProvider);
var logLevelLoader = Mock.Of<ILogLevelLoader>();
_logViewer =
new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, logLevelLoader, Log.Logger);

View File

@@ -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<IScopeProvider>().Object;
var scopeProvider = TestHelper.ScopeProvider;
_mockMemberService = new Mock<IMemberService>();
var mapDefinitions = new List<IMapDefinition>

View File

@@ -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<IMemberService>();
var mockScope = new Mock<IScope>();
var mockScopeProvider = new Mock<IScopeProvider>();
mockScopeProvider
.Setup(x => x.CreateScope(
It.IsAny<IsolationLevel>(),
It.IsAny<RepositoryCacheMode>(),
It.IsAny<IEventDispatcher>(),
It.IsAny<IScopedNotificationPublisher>(),
It.IsAny<bool?>(),
It.IsAny<bool>(),
It.IsAny<bool>()))
.Returns(mockScope.Object);
var mockScopeProvider = TestHelper.ScopeProvider;
return new MemberUserStore(
_mockMemberService.Object,
new UmbracoMapper(new MapDefinitionCollection(() => new List<IMapDefinition>()), mockScopeProvider.Object, NullLogger<UmbracoMapper>.Instance),
mockScopeProvider.Object,
new UmbracoMapper(new MapDefinitionCollection(() => new List<IMapDefinition>()), mockScopeProvider, NullLogger<UmbracoMapper>.Instance),
mockScopeProvider,
new IdentityErrorDescriber(),
Mock.Of<IPublishedSnapshotAccessor>(),
Mock.Of<IExternalLoginWithKeyService>(),