Files
Umbraco-CMS/tests/Umbraco.Tests/Scoping/ScopedXmlTests.cs
Paul Johnson 00133e880d Move test projects from src/ to tests/ (#11357)
* Update gitignore

* Move csproj

* Update project references

* Update solutions

* Update build scripts

* Tests used to share editorconfig with projects in src

* Fix broken tests.

* Stop copying around .editorconfig

merged root one with linting

* csharp_style_expression_bodied -> suggestion

* Move StyleCop rulesets to matching directories and update shared build properties

* Remove legacy build files, update NuGet.cofig and solution files

* Restore myget source

* Clean up .gitignore

* Update .gitignore

* Move new test classes to tests after merge

* Gitignore + nuget config

* Move new test

Co-authored-by: Ronald Barendse <ronald@barend.se>
2021-10-18 08:14:04 +01:00

304 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Xml;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.Sync;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Extensions;
using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.Composing;
namespace Umbraco.Tests.Scoping
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)]
public class ScopedXmlTests : TestWithDatabaseBase
{
private DistributedCacheBinder _distributedCacheBinder;
protected override void Compose()
{
base.Compose();
// the cache refresher component needs to trigger to refresh caches
// but then, it requires a lot of plumbing ;(
// FIXME: and we cannot inject a DistributedCache yet
// so doing all this mess
Builder.Services.AddUnique<IServerMessenger, LocalServerMessenger>();
Builder.Services.AddUnique(f => Mock.Of<IServerRoleAccessor>());
Builder.WithCollectionBuilder<CacheRefresherCollectionBuilder>()
.Add(() => Builder.TypeLoader.GetCacheRefreshers());
Builder.AddNotificationHandler<ContentPublishedNotification, NotificationHandler>();
}
protected override void ComposeSettings()
{
var contentSettings = new ContentSettings();
var globalSettings = new GlobalSettings();
var userPasswordConfigurationSettings = new UserPasswordConfigurationSettings();
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(contentSettings));
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(globalSettings));
Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings));
}
public class NotificationHandler : INotificationHandler<ContentPublishedNotification>
{
public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification);
public static Action<ContentPublishedNotification> PublishedContent { get; set; }
}
[TearDown]
public void Teardown()
{
NotificationHandler.PublishedContent = null;
SafeXmlReaderWriter.Cloning = null;
}
// in 7.6, content.Instance
// .XmlContent - comes from .XmlContentInternal and is cached in context items for current request
// .XmlContentInternal - the actual main xml document
// become in 8
// xmlStore.Xml - the actual main xml document
// publishedContentCache.GetXml() - the captured xml
private static XmlStore XmlStore => (Current.Factory.GetRequiredService<IPublishedSnapshotService>() as XmlPublishedSnapshotService).XmlStore;
private static XmlDocument XmlMaster => XmlStore.Xml;
private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.Content).GetXml(false);
[TestCase(true)]
[TestCase(false)]
public void TestScope(bool complete)
{
var umbracoContext = GetUmbracoContext("http://example.com/", setSingleton: true);
// sanity checks
Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext);
Assert.AreSame(XmlStore, ((PublishedContentCache) umbracoContext.Content).XmlStore);
// create document type, document
var contentType = new ContentType(ShortStringHelper, -1) { Alias = "CustomDocument", Name = "Custom Document" };
ServiceContext.ContentTypeService.Save(contentType);
var item = new Content("name", -1, contentType);
// wire cache refresher
_distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(Current.ServerMessenger, Current.CacheRefreshers));
// check xml in context = "before"
var xml = XmlInContext;
var beforeXml = xml;
var beforeOuterXml = beforeXml.OuterXml;
Console.WriteLine("Xml Before:");
Console.WriteLine(xml.OuterXml);
// event handler
var evented = 0;
NotificationHandler.PublishedContent = notification =>
{
evented++;
// should see the changes in context, not in master
xml = XmlInContext;
Assert.AreNotSame(beforeXml, xml);
Console.WriteLine("Xml Event:");
Console.WriteLine(xml.OuterXml);
var node = xml.GetElementById(item.Id.ToString());
Assert.IsNotNull(node);
xml = XmlMaster;
Assert.AreSame(beforeXml, xml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
};
using (var scope = ScopeProvider.CreateScope())
{
ServiceContext.ContentService.SaveAndPublish(item); // should create an xml clone
item.Name = "changed";
ServiceContext.ContentService.SaveAndPublish(item); // should re-use the xml clone
// this should never change
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
// this does not change, other thread don't see the changes
xml = XmlMaster;
Assert.AreSame(beforeXml, xml);
Console.WriteLine("XmlInternal During:");
Console.WriteLine(xml.OuterXml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
// this does not change during the scope (only in events)
// because it is the events that trigger the changes
xml = XmlInContext;
Assert.IsNotNull(xml);
Assert.AreSame(beforeXml, xml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
// note
// this means that, as long as ppl don't create scopes, they'll see their
// changes right after SaveAndPublish, but if they create scopes,
// they will have to wail until the scope is completed, ie wait until the
// events trigger, to use eg GetUrl etc
if (complete)
scope.Complete();
}
//The reason why there is only 1 event occuring is because we are publishing twice for the same event for the same
//object and the scope deduplicates the events(uses the latest)
Assert.AreEqual(complete? 1 : 0, evented);
// this should never change
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
xml = XmlInContext;
Console.WriteLine("Xml After:");
Console.WriteLine(xml.OuterXml);
if (complete)
{
var node = xml.GetElementById(item.Id.ToString());
Assert.IsNotNull(node);
}
else
{
Assert.AreSame(beforeXml, xml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
}
xml = XmlMaster;
if (complete)
{
var node = xml.GetElementById(item.Id.ToString());
Assert.IsNotNull(node);
}
else
{
Assert.AreSame(beforeXml, xml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
}
}
[TestCase(true)]
[TestCase(false)]
public void TestScopeMany(bool complete)
{
var umbracoContext = GetUmbracoContext("http://example.com/", setSingleton: true);
// sanity checks
Assert.AreSame(umbracoContext, Umbraco.Web.Composing.Current.UmbracoContext);
Assert.AreSame(XmlStore, ((PublishedContentCache)umbracoContext.Content).XmlStore);
// create document type
var contentType = new ContentType(ShortStringHelper,-1) { Alias = "CustomDocument", Name = "Custom Document" };
ServiceContext.ContentTypeService.Save(contentType);
// wire cache refresher
_distributedCacheBinder = new DistributedCacheBinder(new DistributedCache(Current.ServerMessenger, Current.CacheRefreshers));
// check xml in context = "before"
var xml = XmlInContext;
var beforeXml = xml;
var beforeOuterXml = beforeXml.OuterXml;
var item = new Content("name", -1, contentType);
const int count = 10;
var ids = new int[count];
var clones = 0;
SafeXmlReaderWriter.Cloning = () => { clones++; };
Console.WriteLine("Xml Before:");
Console.WriteLine(xml.OuterXml);
using (var scope = ScopeProvider.CreateScope())
{
ServiceContext.ContentService.SaveAndPublish(item);
for (var i = 0; i < count; i++)
{
var temp = new Content("content_" + i, -1, contentType);
ServiceContext.ContentService.SaveAndPublish(temp);
ids[i] = temp.Id;
}
// this should never change
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
xml = XmlMaster;
Assert.IsNotNull(xml);
Console.WriteLine("Xml InScope (before complete):");
Console.WriteLine(xml.OuterXml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
if (complete)
scope.Complete();
xml = XmlMaster;
Assert.IsNotNull(xml);
Console.WriteLine("Xml InScope (after complete):");
Console.WriteLine(xml.OuterXml);
Assert.AreEqual(beforeOuterXml, xml.OuterXml);
}
var scopeProvider = ScopeProvider;
Assert.IsNotNull(scopeProvider);
// ambient scope may be null, or maybe not, depending on whether the code that
// was called did proper scoped work, or some direct (NoScope) use of the database
Assert.IsNull(scopeProvider.AmbientContext);
// limited number of clones!
Assert.AreEqual(complete ? 1 : 0, clones);
// this should never change
Assert.AreEqual(beforeOuterXml, beforeXml.OuterXml);
xml = XmlMaster;
Assert.IsNotNull(xml);
Console.WriteLine("Xml After:");
Console.WriteLine(xml.OuterXml);
if (complete)
{
var node = xml.GetElementById(item.Id.ToString());
Assert.IsNotNull(node);
for (var i = 0; i < 10; i++)
{
node = xml.GetElementById(ids[i].ToString());
Assert.IsNotNull(node);
}
}
else
{
Assert.AreEqual(beforeOuterXml, xml.OuterXml); // nothing has changed!
}
}
public class LocalServerMessenger : ServerMessengerBase
{
public LocalServerMessenger()
: base(false)
{ }
public override void SendMessages() { }
public override void Sync() { }
protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null)
{
throw new NotImplementedException();
}
}
}
}