using System; using System.Collections.Generic; using System.Linq; using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core.Collections; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Services; namespace Umbraco.Tests.Scoping { [TestFixture] public class ScopeEventDispatcherTests //: BaseUmbracoConfigurationTest { private TestObjects _testObjects; [SetUp] public void Setup() { // remove all handlers first DoThing1 = null; DoThing2 = null; DoThing3 = null; Current.Container = new ServiceContainer(); _testObjects = new TestObjects(Current.Container); Current.Container.RegisterSingleton(f => Current.Container); Current.Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance())); Current.Container.RegisterCollectionBuilder(); SettingsForTests.Reset(); // ensure we have configuration } [TearDown] public void TearDown() { Current.Reset(); SettingsForTests.Reset(); } [TestCase(false, true, true)] [TestCase(false, true, false)] [TestCase(false, false, true)] [TestCase(false, false, false)] [TestCase(true, true, true)] [TestCase(true, true, false)] [TestCase(true, false, true)] [TestCase(true, false, false)] public void EventsHandling(bool passive, bool cancel, bool complete) { var counter1 = 0; var counter2 = 0; DoThing1 += (sender, args) => { counter1++; if (cancel) args.Cancel = true; }; DoThing2 += (sender, args) => { counter2++; }; var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: passive ? new PassiveEventDispatcher() : null)) { var cancelled = scope.Events.DispatchCancelable(DoThing1, this, new SaveEventArgs("test")); if (cancelled == false) scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); if (complete) scope.Complete(); } var expected1 = passive ? 0 : 1; Assert.AreEqual(expected1, counter1); int expected2; if (passive) expected2 = 0; else expected2 = cancel ? 0 : (complete ? 1 : 0); Assert.AreEqual(expected2, counter2); } [Test] public void QueueEvents() { DoThing1 += OnDoThingFail; DoThing2 += OnDoThingFail; DoThing3 += OnDoThingFail; var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have been queued Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); var knownNames = new[] { "DoThing1", "DoThing2", "DoThing3" }; var knownArgTypes = new[] { typeof(SaveEventArgs), typeof(SaveEventArgs), typeof(SaveEventArgs) }; for (var i = 0; i < events.Length; i++) { Assert.AreEqual(knownNames[i], events[i].EventName); Assert.AreEqual(knownArgTypes[i], events[i].Args.GetType()); } } } [Test] public void SupersededEvents() { DoSaveForContent += OnDoThingFail; DoDeleteForContent += OnDoThingFail; DoForTestArgs += OnDoThingFail; DoForTestArgs2 += OnDoThingFail; var contentType = MockedContentTypes.CreateBasicContentType(); var content1 = MockedContent.CreateBasicContent(contentType); content1.Id = 123; var content2 = MockedContent.CreateBasicContent(contentType); content2.Id = 456; var content3 = MockedContent.CreateBasicContent(contentType); content3.Id = 789; var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { //content1 will be filtered from the args scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[]{ content1 , content3})); scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs(content1), "DoDeleteForContent"); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); //this entire event will be filtered scope.Events.Dispatch(DoForTestArgs, this, new TestEventArgs(content1)); scope.Events.Dispatch(DoForTestArgs2, this, new TestEventArgs2(content1)); // events have been queued var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); Assert.AreEqual(4, events.Length); Assert.AreEqual(typeof(SaveEventArgs), events[0].Args.GetType()); Assert.AreEqual(1, ((SaveEventArgs)events[0].Args).SavedEntities.Count()); Assert.AreEqual(content3.Id, ((SaveEventArgs)events[0].Args).SavedEntities.First().Id); Assert.AreEqual(typeof(DeleteEventArgs), events[1].Args.GetType()); Assert.AreEqual(content1.Id, ((DeleteEventArgs) events[1].Args).DeletedEntities.First().Id); Assert.AreEqual(typeof(SaveEventArgs), events[2].Args.GetType()); Assert.AreEqual(content2.Id, ((SaveEventArgs)events[2].Args).SavedEntities.First().Id); Assert.AreEqual(typeof(TestEventArgs2), events[3].Args.GetType()); } } [Test] public void SupersededEvents2() { Test_UnPublished += OnDoThingFail; Test_Deleted += OnDoThingFail; var contentService = Mock.Of(); var content = Mock.Of(); var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(Test_UnPublished, contentService, new PublishEventArgs(new [] { content }), "UnPublished"); scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new [] { content }), "Deleted"); // see U4-10764 var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); Assert.AreEqual(2, events.Length); } } /// /// This will test that when we track events that before we Get the events we normalize all of the /// event entities to be the latest one (most current) found amongst the event so that there is /// no 'stale' entities in any of the args /// [Test] public void LatestEntities() { DoSaveForContent += OnDoThingFail; var now = DateTime.Now; var contentType = MockedContentTypes.CreateBasicContentType(); var content1 = MockedContent.CreateBasicContent(contentType); content1.Id = 123; content1.UpdateDate = now.AddMinutes(1); var content2 = MockedContent.CreateBasicContent(contentType); content2.Id = 123; content2.UpdateDate = now.AddMinutes(2); var content3 = MockedContent.CreateBasicContent(contentType); content3.Id = 123; content3.UpdateDate = now.AddMinutes(3); var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); // events have been queued var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); Assert.AreEqual(3, events.Length); foreach (var t in events) { var args = (SaveEventArgs)t.Args; foreach (var entity in args.SavedEntities) { Assert.AreEqual(content3, entity); Assert.IsTrue(object.ReferenceEquals(content3, entity)); } } } } [Test] public void FirstIn() { DoSaveForContent += OnDoThingFail; var now = DateTime.Now; var contentType = MockedContentTypes.CreateBasicContentType(); var content1 = MockedContent.CreateBasicContent(contentType); content1.Id = 123; content1.UpdateDate = now.AddMinutes(1); var content2 = MockedContent.CreateBasicContent(contentType); content2.Id = 123; content1.UpdateDate = now.AddMinutes(2); var content3 = MockedContent.CreateBasicContent(contentType); content3.Id = 123; content1.UpdateDate = now.AddMinutes(3); var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); // events have been queued var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray(); Assert.AreEqual(1, events.Length); Assert.AreEqual(content1, ((SaveEventArgs) events[0].Args).SavedEntities.First()); Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First())); Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs) events[0].Args).SavedEntities.First().UpdateDate); } } [Test] public void LastIn() { DoSaveForContent += OnDoThingFail; var now = DateTime.Now; var contentType = MockedContentTypes.CreateBasicContentType(); var content1 = MockedContent.CreateBasicContent(contentType); content1.Id = 123; content1.UpdateDate = now.AddMinutes(1); var content2 = MockedContent.CreateBasicContent(contentType); content2.Id = 123; content2.UpdateDate = now.AddMinutes(2); var content3 = MockedContent.CreateBasicContent(contentType); content3.Id = 123; content3.UpdateDate = now.AddMinutes(3); var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content3)); // events have been queued var events = scope.Events.GetEvents(EventDefinitionFilter.LastIn).ToArray(); Assert.AreEqual(1, events.Length); Assert.AreEqual(content3, ((SaveEventArgs)events[0].Args).SavedEntities.First()); Assert.IsTrue(object.ReferenceEquals(content3, ((SaveEventArgs)events[0].Args).SavedEntities.First())); Assert.AreEqual(content3.UpdateDate, ((SaveEventArgs)events[0].Args).SavedEntities.First().UpdateDate); } } [TestCase(true)] [TestCase(false)] public void EventsDispatching_Passive(bool complete) { DoThing1 += OnDoThingFail; DoThing2 += OnDoThingFail; DoThing3 += OnDoThingFail; var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have been queued Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); if (complete) scope.Complete(); } // no event has been raised (else OnDoThingFail would have failed) } [TestCase(true)] [TestCase(false)] public void EventsDispatching_Scope(bool complete) { var counter = 0; IScope ambientScope = null; ScopeContext ambientContext = null; Guid value = Guid.Empty; var scopeProvider = _testObjects.GetScopeProvider(Mock.Of()) as ScopeProvider; DoThing1 += (sender, args) => { counter++; }; DoThing2 += (sender, args) => { counter++; }; DoThing3 += (sender, args) => { ambientScope = scopeProvider.AmbientScope; ambientContext = scopeProvider.AmbientContext; value = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { }); counter++; }; Guid guid; using (var scope = scopeProvider.CreateScope()) { Assert.IsNotNull(scopeProvider.AmbientContext); guid = scopeProvider.Context.Enlist("value", Guid.NewGuid, (c, o) => { }); scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); scope.Events.Dispatch(DoThing2, this, new SaveEventArgs(0)); scope.Events.Dispatch(DoThing3, this, new SaveEventArgs(0)); // events have been queued Assert.AreEqual(3, scope.Events.GetEvents(EventDefinitionFilter.All).Count()); Assert.AreEqual(0, counter); if (complete) scope.Complete(); } if (complete) { // events have been raised Assert.AreEqual(3, counter); Assert.IsNull(ambientScope); // scope was gone Assert.IsNotNull(ambientContext); // but not context Assert.AreEqual(guid, value); // so we got the same value! } else { // else, no event has been raised Assert.AreEqual(0, counter); } // everything's gone Assert.IsNull(scopeProvider.AmbientScope); Assert.IsNull(scopeProvider.AmbientContext); } private static void OnDoThingFail(object sender, EventArgs eventArgs) { Assert.Fail(); } public static event EventHandler> DoSaveForContent; public static event EventHandler> DoDeleteForContent; public static event EventHandler DoForTestArgs; public static event EventHandler DoForTestArgs2; public static event EventHandler> DoThing1; public static event EventHandler> DoThing2; public static event TypedEventHandler> DoThing3; public static event TypedEventHandler> Test_UnPublished; public static event TypedEventHandler> Test_Deleted; public class TestEventArgs : CancellableObjectEventArgs { public TestEventArgs(object eventObject) : base(eventObject) { } public object MyEventObject { get { return EventObject; } } } [SupersedeEvent(typeof(TestEventArgs))] public class TestEventArgs2 : CancellableObjectEventArgs { public TestEventArgs2(object eventObject) : base(eventObject) { } public object MyEventObject { get { return EventObject; } } } public class PassiveEventDispatcher : QueuingEventDispatcherBase { public PassiveEventDispatcher() : base(false) { } protected override void ScopeExitCompleted() { // do nothing } } } }