Files
Umbraco-CMS/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs
Andy Butland 825f791d01 Remove the non-controversial, straightforward obsoleted constructs for Umbraco 16 (#18661)
* Removed obsoletes from IConfigManipulator.

* Removed obsolete models builder extensions.

* Removed the obsolete ContentDashboardSettings.

* Removed the obsolete InstallMissingDatabase setting on GlobalSettings.

* Removed obsolete NuCache settings.

* Removed obsolete RuntimeMinificationSettings.

* Removed obsolete health check constant.

* Removed obsolete icon constant.

* Removed obsolete telemetry constant.

* Removed obsolete property and constructor on UmbracoBuilder.

* Removed obsolete constructor on AuditNotificationsHandler.

* Removed obsolete constructor on HTTP header health checks.

* Removed obsolete constructor on MediaFileManager.

* Removed obsolete GetDefaultFileContent on ViewHelper.

* Remove obsoleted methods on embed providers.

* Fix tests.

* Removed obsolete constructors on BlockEditorDataConverter.

* Removed obsolete SeedCacheDuration property on CacheSettings.

* Removed obsolete PublishCulture on ContentRepositoryExtensions.

* Removed obsolete MonitorLock.

* Removed obsolete synchronous HasSavedValues from IDataTypeUsageService and IDataTypeUsageRepository.

* Removed obsolete HasSavedPropertyValues from IPropertyTypeUsageService and IPropertyTypeUsageRepository.

* Removed obsolete methods in ITrackedReferencesService and ITrackedReferencesRepository.

* Removed obsolete DateValueEditor constructors.

* Removed obsolete GetAutomaticRelationTypesAliases.

* Removed obsolete constructor on TextOnlyValueEditor.

* Removed obsolete constructors on RegexValidator and RequiredValidator.

* Removed obsolete constructs on SliderValueConverter and TagsValueConverter.

* Removed obsolete GetContentType methods from IPublishedCache.

* Removed ContentFinderByIdPath.

* Removed obsolete constructor on DefaultMediaUrlProvider.

* Removed obsolete constructor on Domain.

* Removed obsolete constructor on PublishedRequest.

* Removed obsolete methods on CheckPermissions.

* Removed obsolete GetUserId from IBackOfficeSecurity.

* Removed obsolete methods on LegacyPasswordSecurity.

* Removed obsolete constructors on AuditService.

* Removed obsolete methods on IContentEditingService.

* Remove obsolete constructors and methods on ContentService/IContentService.

* Removed obsolete constructor in ContentTypeEditingService.

* Removed obsolete constructor in MediaTypeEditingService.

* Removed obsolete constructor in MemberTypeEditingService.

* Removed obsolete constructor in ContentTypeService.

* Removed obsolete constructors in ContentTypeServiceBase.

* Removed obsolete constructors and methods in ContentVersionService.

* Removed obsolete constructor in DataTypeUsageService.

* Removed obsolete constructor in DomainService.

* Removed obsolete constructor in FileService.

* Removes obsolete AttemptMove from IContentService.

* Removes obsolete SetPreventCleanup from IContentVersionService.

* Removes obsolete GetReferences from IDataTypeService.

* Removed obsolete SetConsentLevel from IMetricsConsentService.

* Removed obsolete methods from IPackageDataInstallation.

* Removed obsolete methods from IPackagingService.

* Removed obsolete methods on ITwoFactorLoginService.
Removed obsolete ITemporaryMediaService.

* Removed obsolete constructor from MediaService, MemberTypeService and MediaTypeService.

* More obsolete constructors.

* Removed obsoleted overloads on IPropertyValidationService.

* Fixed build for tests.

* Removed obsolete constructor for PublicAccessService, UserService and RelationService.

* Removed GetDefaultMemberType.

* Removed obsolete user group functionality from IUserService.

* Removed obsolete extension methods on IUserService.

* Removed obsolete method from ITelemetryService.

* Removed obsolete UdiParserServiceConnectors.

* Removed obsolete method on ICookieManager.

* Removed obsolete DynamicContext.

* Removed obsolete XmlHelper.

* Fixed failing integration tests.

* Removed obsoletes in Umbraco.Cms.Api.Common

* Removed obsoletes in Umbraco.Cms.Api.Delivery

* Removed obsoletes in Umbraco.Cms.Api.Management

* Removed obsoletes in Umbraco.Examine.Lucene

* Removed obsoletes in Umbraco.Infrastructure

* Fix failing delivery API contract integration test.

* Made integration tests internal.

* Removed obsoletes from web projects.

* Fix build.

* Removed Twitter OEmbed provider

* Removed obsolete constructor on PublishedDataType.

* Removed obsolete constructors on PublishedCacheBase.

* Removed the obsolete PropertyEditorTagsExtensions.

* Removed obsoletion properties on configuration response  models (#18697)

* Removed obsolete methods from server-side models.

* Update client-side types and sdk.

* Update client-side files.

* Removed obsoletion of Utf8ToAsciiConverter.ToAsciiString overload. (#18694)

* Removed obsolete method in UserService. (#18710)

* Removed obsoleted group alias keys from being publicly available. (#18682)

* Removed unneceessary ApiVersion attribute.

* Clean-up obsoletions on MemberService (#18703)

* Removed obsoleted method on MemberService, added future obsoletion to interface and updated all callers.

* Removed obsoletion on member service method that's not obsolete on the interface.
2025-03-21 17:02:31 +00:00

755 lines
30 KiB
C#

// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Tests.Common;
using Umbraco.Cms.Tests.Common.Attributes;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Extensions;
using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewEmptyPerFixture)]
internal sealed class ScopeTests : UmbracoIntegrationTest
{
private new ScopeProvider ScopeProvider => (ScopeProvider)base.ScopeProvider;
[SetUp]
public void SetUp() => Assert.IsNull(ScopeProvider.AmbientScope); // gone
protected override void ConfigureTestServices(IServiceCollection services)
{
// Need to have a mockable request cache for tests
var appCaches = new AppCaches(
NoAppCache.Instance,
Mock.Of<IRequestCache>(x => x.IsAvailable == false),
new IsolatedCaches(_ => NoAppCache.Instance));
services.AddUnique(appCaches);
}
[Test]
[LongRunning]
public void GivenUncompletedScopeOnChildThread_WhenTheParentCompletes_TheTransactionIsRolledBack()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(ScopeProvider.AmbientScope);
IScope mainScope = scopeProvider.CreateScope();
var t = Task.Run(() =>
{
IScope nested = scopeProvider.CreateScope();
Thread.Sleep(2000);
nested.Dispose();
});
Thread.Sleep(1000); // mimic some long running operation that is shorter than the other thread
mainScope.Complete();
Assert.Throws<InvalidOperationException>(() => mainScope.Dispose());
Task.WaitAll(t);
}
[Test]
public void GivenNonDisposedChildScope_WhenTheParentDisposes_ThenInvalidOperationExceptionThrows()
{
// this all runs in the same execution context so the AmbientScope reference isn't a copy
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(ScopeProvider.AmbientScope);
IScope mainScope = scopeProvider.CreateScope();
IScope nested = scopeProvider.CreateScope(); // not disposing
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => mainScope.Dispose());
Console.WriteLine(ex);
}
[Test]
[LongRunning]
public void GivenChildThread_WhenParentDisposedBeforeChild_ParentScopeThrows()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(ScopeProvider.AmbientScope);
IScope mainScope = scopeProvider.CreateScope();
var t = Task.Run(() =>
{
Console.WriteLine("Child Task start: " + scopeProvider.AmbientScope.InstanceId);
// This will push the child scope to the top of the Stack
IScope nested = scopeProvider.CreateScope();
Console.WriteLine("Child Task scope created: " + scopeProvider.AmbientScope.InstanceId);
Thread.Sleep(5000); // block for a bit to ensure the parent task is disposed first
Console.WriteLine("Child Task before dispose: " + scopeProvider.AmbientScope.InstanceId);
nested.Dispose();
Console.WriteLine("Child Task after dispose: " + scopeProvider.AmbientScope.InstanceId);
});
// provide some time for the child thread to start so the ambient context is copied in AsyncLocal
Thread.Sleep(2000);
// now dispose the main without waiting for the child thread to join
Console.WriteLine("Parent Task disposing: " + scopeProvider.AmbientScope.InstanceId);
// This will throw because at this stage a child scope has been created which means
// it is the Ambient (top) scope but here we're trying to dispose the non top scope.
Assert.Throws<InvalidOperationException>(() => mainScope.Dispose());
Task.WaitAll(t); // wait for the child to dispose
mainScope.Dispose(); // now it's ok
Console.WriteLine("Parent Task disposed: " + scopeProvider.AmbientScope?.InstanceId);
}
[Test]
public void GivenChildThread_WhenChildDisposedBeforeParent_OK()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(ScopeProvider.AmbientScope);
IScope mainScope = scopeProvider.CreateScope();
// Task.Run will flow the execution context unless ExecutionContext.SuppressFlow() is explicitly called.
// This is what occurs in normal async behavior since it is expected to await (and join) the main thread,
// but if Task.Run is used as a fire and forget thread without being done correctly then the Scope will
// flow to that thread.
var t = Task.Run(() =>
{
Console.WriteLine("Child Task start: " + scopeProvider.AmbientScope.InstanceId);
IScope nested = scopeProvider.CreateScope();
Console.WriteLine("Child Task before dispose: " + scopeProvider.AmbientScope.InstanceId);
nested.Dispose();
Console.WriteLine("Child Task after disposed: " + scopeProvider.AmbientScope.InstanceId);
});
Console.WriteLine("Parent Task waiting: " + scopeProvider.AmbientScope?.InstanceId);
Task.WaitAll(t);
Console.WriteLine("Parent Task disposing: " + scopeProvider.AmbientScope.InstanceId);
mainScope.Dispose();
Console.WriteLine("Parent Task disposed: " + scopeProvider.AmbientScope?.InstanceId);
Assert.Pass();
}
[Test]
public void SimpleCreateScope()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(ScopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
}
Assert.IsNull(scopeProvider.AmbientScope);
}
[Test]
public void SimpleCreateScopeContext()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
Assert.IsNotNull(scopeProvider.Context);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
[Test]
public void SimpleCreateScopeDatabase()
{
ScopeProvider scopeProvider = ScopeProvider;
IUmbracoDatabase database;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
database = ScopeAccessor.AmbientScope.Database; // populates scope's database
Assert.IsNotNull(database);
Assert.IsNotNull(database.Connection); // in a transaction
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(database.Connection); // poof gone
}
[Test]
public void NestedCreateScope()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
using (IScope nested = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(nested);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(nested, scopeProvider.AmbientScope);
Assert.AreSame(scope, ((Scope)nested).ParentScope);
}
}
Assert.IsNull(scopeProvider.AmbientScope);
}
[Test]
public void NestedCreateScopeContext()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
IScopeContext context;
using (IScope nested = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(nested);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(nested, scopeProvider.AmbientScope);
Assert.AreSame(scope, ((Scope)nested).ParentScope);
Assert.IsNotNull(scopeProvider.Context);
Assert.IsNotNull(scopeProvider.AmbientContext);
context = scopeProvider.Context;
}
Assert.IsNotNull(scopeProvider.AmbientContext);
Assert.AreSame(context, scopeProvider.AmbientContext);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
[Test]
public void NestedCreateScopeInnerException()
{
ScopeProvider scopeProvider = ScopeProvider;
bool? scopeCompleted = null;
Assert.IsNull(scopeProvider.AmbientScope);
try
{
using (IScope scope = scopeProvider.CreateScope())
{
scopeProvider.Context.Enlist("test", completed => scopeCompleted = completed);
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
using (IScope nested = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(nested);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(nested, scopeProvider.AmbientScope);
Assert.AreSame(scope, ((Scope)nested).ParentScope);
nested.Complete();
throw new Exception("bang!");
}
scope.Complete();
}
Assert.Fail("Expected exception.");
}
catch (Exception e)
{
if (e.Message != "bang!")
{
Assert.Fail("Wrong exception.");
}
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNotNull(scopeCompleted);
Assert.IsFalse(scopeCompleted.Value);
}
[Test]
public void NestedCreateScopeDatabase()
{
ScopeProvider scopeProvider = ScopeProvider;
IUmbracoDatabase database;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
database = ScopeAccessor.AmbientScope.Database; // populates scope's database
Assert.IsNotNull(database);
Assert.IsNotNull(database.Connection); // in a transaction
using (IScope nested = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(nested);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(nested, scopeProvider.AmbientScope);
Assert.AreSame(scope, ((Scope)nested).ParentScope);
Assert.AreSame(database, ScopeAccessor.AmbientScope.Database);
}
Assert.IsNotNull(database.Connection); // still
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(database.Connection); // poof gone
}
[Test]
public void Transaction()
{
ScopeProvider scopeProvider = ScopeProvider;
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("CREATE TABLE tmp3 (id INT, name NVARCHAR(64))");
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp3 (id, name) VALUES (1, 'a')");
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp3 WHERE id=1");
Assert.AreEqual("a", n);
}
using (IScope scope = scopeProvider.CreateScope())
{
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp3 WHERE id=1");
Assert.IsNull(n);
}
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp3 (id, name) VALUES (1, 'a')");
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp3 WHERE id=1");
Assert.AreEqual("a", n);
}
}
[Test]
public void NestedTransactionInnerFail()
{
ScopeProvider scopeProvider = ScopeProvider;
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute($"CREATE TABLE tmp1 (id INT, name NVARCHAR(64))");
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp1 (id, name) VALUES (1, 'a')");
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp1 WHERE id=1");
Assert.AreEqual("a", n);
using (IScope nested = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp1 (id, name) VALUES (2, 'b')");
string nn = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp1 WHERE id=2");
Assert.AreEqual("b", nn);
}
n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp1 WHERE id=2");
Assert.AreEqual("b", n);
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp1 WHERE id=1");
Assert.IsNull(n);
n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp1 WHERE id=2");
Assert.IsNull(n);
}
}
[Test]
public void NestedTransactionOuterFail()
{
ScopeProvider scopeProvider = ScopeProvider;
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("CREATE TABLE tmp2 (id INT, name NVARCHAR(64))");
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp2 (id, name) VALUES (1, 'a')");
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp2 WHERE id=1");
Assert.AreEqual("a", n);
using (IScope nested = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp2 (id, name) VALUES (2, 'b')");
string nn = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp2 WHERE id=2");
Assert.AreEqual("b", nn);
nested.Complete();
}
n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp2 WHERE id=2");
Assert.AreEqual("b", n);
}
using (IScope scope = scopeProvider.CreateScope())
{
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp2 WHERE id=1");
Assert.IsNull(n);
n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp2 WHERE id=2");
Assert.IsNull(n);
}
}
[Test]
public void NestedTransactionComplete()
{
ScopeProvider scopeProvider = ScopeProvider;
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))");
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')");
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp WHERE id=1");
Assert.AreEqual("a", n);
using (IScope nested = scopeProvider.CreateScope())
{
ScopeAccessor.AmbientScope.Database.Execute("INSERT INTO tmp (id, name) VALUES (2, 'b')");
string nn = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp WHERE id=2");
Assert.AreEqual("b", nn);
nested.Complete();
}
n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp WHERE id=2");
Assert.AreEqual("b", n);
scope.Complete();
}
using (IScope scope = scopeProvider.CreateScope())
{
string n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp WHERE id=1");
Assert.AreEqual("a", n);
n = ScopeAccessor.AmbientScope.Database.ExecuteScalar<string>("SELECT name FROM tmp WHERE id=2");
Assert.AreEqual("b", n);
}
}
[Test]
public void CallContextScope1()
{
var taskHelper = new TaskHelper(Mock.Of<ILogger<TaskHelper>>());
ScopeProvider scopeProvider = ScopeProvider;
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
// Run on another thread without a flowed context
Task t = taskHelper.ExecuteBackgroundTask(() =>
{
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
using (IScope newScope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientScope.ParentScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
return Task.CompletedTask;
});
Task.WaitAll(t);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
[Test]
public void CallContextScope2()
{
var taskHelper = new TaskHelper(Mock.Of<ILogger<TaskHelper>>());
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
// Run on another thread without a flowed context
Task t = taskHelper.ExecuteBackgroundTask(() =>
{
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
using (IScope newScope = scopeProvider.CreateScope())
{
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientScope.ParentScope);
Assert.IsNotNull(scopeProvider.AmbientContext);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
return Task.CompletedTask;
});
Task.WaitAll(t);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
[Test]
public void ScopeReference()
{
ScopeProvider scopeProvider = ScopeProvider;
Scope scope = (Scope) scopeProvider.CreateScope();
Scope nested = (Scope) scopeProvider.CreateScope();
Assert.IsNotNull(scopeProvider.AmbientScope);
var scopeRef = new HttpScopeReference(scopeProvider);
scopeRef.Register();
scopeRef.Dispose();
Assert.IsNull(scopeProvider.AmbientScope);
Assert.Throws<ObjectDisposedException>(() =>
{
IUmbracoDatabase db = scope.Database;
});
Assert.Throws<ObjectDisposedException>(() =>
{
IUmbracoDatabase db = nested.Database;
});
}
[TestCase(true)]
[TestCase(false)]
public void ScopeContextEnlist(bool complete)
{
ScopeProvider scopeProvider = ScopeProvider;
bool? completed = null;
IScope ambientScope = null;
IScopeContext ambientContext = null;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
scopeProvider.Context.Enlist("name", c =>
{
completed = c;
ambientScope = scopeProvider.AmbientScope;
ambientContext = scopeProvider.AmbientContext;
});
if (complete)
{
scope.Complete();
}
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
Assert.IsNotNull(completed);
Assert.AreEqual(complete, completed.Value);
Assert.IsNull(ambientScope); // the scope is gone
Assert.IsNotNull(ambientContext); // the context is still there
}
[TestCase(true)]
[TestCase(false)]
public void ScopeContextEnlistAgain(bool complete)
{
ScopeProvider scopeProvider = ScopeProvider;
bool? completed = null;
bool? completed2 = null;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
scopeProvider.Context.Enlist("name", c =>
{
completed = c;
// at that point the scope is gone, but the context is still there
IScopeContext ambientContext = scopeProvider.AmbientContext;
ambientContext.Enlist("another", c2 => completed2 = c2);
});
if (complete)
{
scope.Complete();
}
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
Assert.IsNotNull(completed);
Assert.AreEqual(complete, completed.Value);
Assert.AreEqual(complete, completed2.Value);
}
[Test]
public void ScopeContextException()
{
ScopeProvider scopeProvider = ScopeProvider;
bool? completed = null;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
IScope detached = scopeProvider.CreateDetachedScope();
scopeProvider.AttachScope(detached);
// the exception does not prevent other enlisted items to run
// *and* it does not prevent the scope from properly going down
scopeProvider.Context.Enlist("name", c => throw new Exception("bang"));
scopeProvider.Context.Enlist("other", c => completed = c);
detached.Complete();
Assert.Throws<AggregateException>(() => detached.Dispose());
// even though disposing of the scope has thrown, it has exited
// properly ie it has removed itself, and the app remains clean
Assert.AreSame(scope, scopeProvider.AmbientScope);
scope.Complete();
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
Assert.IsNotNull(completed);
Assert.AreEqual(true, completed);
}
[Test]
public void DetachableScope()
{
ScopeProvider scopeProvider = ScopeProvider;
Assert.IsNull(scopeProvider.AmbientScope);
using (IScope scope = scopeProvider.CreateScope())
{
Assert.IsInstanceOf<Scope>(scope);
Assert.IsNotNull(scopeProvider.AmbientScope);
Assert.AreSame(scope, scopeProvider.AmbientScope);
Assert.IsNotNull(scopeProvider.AmbientContext); // the ambient context
Assert.IsNotNull(scopeProvider.Context); // the ambient context too (getter only)
IScopeContext context = scopeProvider.Context;
IScope detached = scopeProvider.CreateDetachedScope();
scopeProvider.AttachScope(detached);
Assert.AreEqual(detached, scopeProvider.AmbientScope);
Assert.AreNotSame(context, scopeProvider.Context);
// nesting under detached!
using (IScope nested = scopeProvider.CreateScope())
{
Assert.Throws<InvalidOperationException>(() =>
// cannot detach a non-detachable scope
scopeProvider.DetachScope());
nested.Complete();
}
Assert.AreEqual(detached, scopeProvider.AmbientScope);
Assert.AreNotSame(context, scopeProvider.Context);
// can detach
Assert.AreSame(detached, scopeProvider.DetachScope());
Assert.AreSame(scope, scopeProvider.AmbientScope);
Assert.AreSame(context, scopeProvider.AmbientContext);
Assert.Throws<InvalidOperationException>(() =>
// cannot disposed a non-attached scope
// in fact, only the ambient scope can be disposed
detached.Dispose());
scopeProvider.AttachScope(detached);
detached.Complete();
detached.Dispose();
// has self-detached, and is gone!
Assert.AreSame(scope, scopeProvider.AmbientScope);
Assert.AreSame(context, scopeProvider.AmbientContext);
}
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
}
}
}