309 lines
12 KiB
C#
309 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using LightInject;
|
|
using Umbraco.Core.Cache;
|
|
using Umbraco.Core.Composing;
|
|
using Umbraco.Core.Events;
|
|
using Umbraco.Core.Models;
|
|
using Umbraco.Core.Services;
|
|
using Umbraco.Core.Services.Implement;
|
|
using Umbraco.Core.Sync;
|
|
using Umbraco.Tests.TestHelpers;
|
|
using Umbraco.Tests.Testing;
|
|
using Umbraco.Web.Cache;
|
|
using Umbraco.Web.PublishedCache;
|
|
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
|
|
|
namespace Umbraco.Tests.Scoping
|
|
{
|
|
[TestFixture]
|
|
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)]
|
|
public class ScopedXmlTests : TestWithDatabaseBase
|
|
{
|
|
private CacheRefresherComponent _cacheRefresher;
|
|
|
|
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
|
|
Container.RegisterSingleton<IServerMessenger, LocalServerMessenger>();
|
|
Container.RegisterSingleton(f => Mock.Of<IServerRegistrar>());
|
|
Container.RegisterCollectionBuilder<CacheRefresherCollectionBuilder>()
|
|
.Add(f => f.TryGetInstance<TypeLoader>().GetCacheRefreshers());
|
|
}
|
|
|
|
[TearDown]
|
|
public void Teardown()
|
|
{
|
|
_cacheRefresher?.Unbind();
|
|
_cacheRefresher = null;
|
|
|
|
_onPublishedAssertAction = null;
|
|
ContentService.Published -= OnPublishedAssert;
|
|
SafeXmlReaderWriter.Cloning = null;
|
|
}
|
|
|
|
private void OnPublishedAssert(IContentService sender, PublishEventArgs<IContent> args)
|
|
{
|
|
_onPublishedAssertAction?.Invoke();
|
|
}
|
|
|
|
private Action _onPublishedAssertAction;
|
|
|
|
// 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.Container.GetInstance<IPublishedSnapshotService>() as PublishedSnapshotService).XmlStore;
|
|
private static XmlDocument XmlMaster => XmlStore.Xml;
|
|
private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.ContentCache).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.ContentCache).XmlStore);
|
|
|
|
// settings
|
|
var settings = SettingsForTests.GenerateMockUmbracoSettings();
|
|
var contentMock = Mock.Get(settings.Content);
|
|
contentMock.Setup(x => x.XmlCacheEnabled).Returns(false);
|
|
SettingsForTests.ConfigureSettings(settings);
|
|
|
|
// create document type, document
|
|
var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" };
|
|
Current.Services.ContentTypeService.Save(contentType);
|
|
var item = new Content("name", -1, contentType);
|
|
|
|
// wire cache refresher
|
|
_cacheRefresher = new CacheRefresherComponent(true);
|
|
_cacheRefresher.Initialize(new DistributedCache());
|
|
|
|
// 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;
|
|
_onPublishedAssertAction = () =>
|
|
{
|
|
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);
|
|
};
|
|
|
|
ContentService.Published += OnPublishedAssert;
|
|
|
|
using (var scope = ScopeProvider.CreateScope())
|
|
{
|
|
item.TryPublishValues();
|
|
Current.Services.ContentService.SaveAndPublish(item); // should create an xml clone
|
|
item.Name = "changed";
|
|
item.TryPublishValues();
|
|
Current.Services.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.ContentCache).XmlStore);
|
|
|
|
// settings
|
|
var settings = SettingsForTests.GenerateMockUmbracoSettings();
|
|
var contentMock = Mock.Get(settings.Content);
|
|
contentMock.Setup(x => x.XmlCacheEnabled).Returns(false);
|
|
SettingsForTests.ConfigureSettings(settings);
|
|
|
|
// create document type
|
|
var contentType = new ContentType(-1) { Alias = "CustomDocument", Name = "Custom Document" };
|
|
Current.Services.ContentTypeService.Save(contentType);
|
|
|
|
// wire cache refresher
|
|
_cacheRefresher = new CacheRefresherComponent(true);
|
|
_cacheRefresher.Initialize(new DistributedCache());
|
|
|
|
// 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())
|
|
{
|
|
item.TryPublishValues();
|
|
Current.Services.ContentService.SaveAndPublish(item);
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var temp = new Content("content_" + i, -1, contentType);
|
|
temp.TryPublishValues();
|
|
Current.Services.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)
|
|
{ }
|
|
|
|
protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|
|
}
|