diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj
index e918718925..1dce223a34 100644
--- a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj
+++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj
@@ -12,6 +12,7 @@
+
diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs
index 3cb53c8be2..ed8f682641 100644
--- a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs
+++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs
@@ -1,7 +1,11 @@
using Volo.Abp.Modularity;
+using Volo.Abp.EventBus.Abstractions;
namespace Volo.Abp.Gdpr;
+[DependsOn(
+ typeof(AbpEventBusAbstractionsModule)
+)]
public class AbpGdprAbstractionsModule : AbpModule
{
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeletionRequestedEto.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeletionRequestedEto.cs
index aa7b721117..e9520d9580 100644
--- a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeletionRequestedEto.cs
+++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeletionRequestedEto.cs
@@ -1,9 +1,18 @@
using System;
+using Volo.Abp.EventBus;
namespace Volo.Abp.Gdpr;
[Serializable]
-public class GdprUserDataDeletionRequestedEto
+public class GdprUserDataDeletionRequestedEto : IEventDataMayHaveTenantId
{
+ public Guid? TenantId { get; set; }
+
public Guid UserId { get; set; }
-}
\ No newline at end of file
+
+ public bool IsMultiTenant(out Guid? tenantId)
+ {
+ tenantId = TenantId;
+ return TenantId.HasValue;
+ }
+}
diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs
index d52e12783c..26f075d894 100644
--- a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs
+++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs
@@ -1,13 +1,22 @@
using System;
+using Volo.Abp.EventBus;
namespace Volo.Abp.Gdpr;
[Serializable]
-public class GdprUserDataPreparedEto
+public class GdprUserDataPreparedEto : IEventDataMayHaveTenantId
{
+ public Guid? TenantId { get; set; }
+
public Guid RequestId { get; set; }
public string Provider { get; set; } = default!;
public GdprDataInfo Data { get; set; } = default!;
-}
\ No newline at end of file
+
+ public bool IsMultiTenant(out Guid? tenantId)
+ {
+ tenantId = TenantId;
+ return TenantId.HasValue;
+ }
+}
diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs
index b341ab2659..3403b0d514 100644
--- a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs
+++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs
@@ -1,11 +1,20 @@
using System;
+using Volo.Abp.EventBus;
namespace Volo.Abp.Gdpr;
[Serializable]
-public class GdprUserDataRequestedEto
+public class GdprUserDataRequestedEto : IEventDataMayHaveTenantId
{
+ public Guid? TenantId { get; set; }
+
public Guid UserId { get; set; }
public Guid RequestId { get; set; }
-}
\ No newline at end of file
+
+ public bool IsMultiTenant(out Guid? tenantId)
+ {
+ tenantId = TenantId;
+ return TenantId.HasValue;
+ }
+}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
index f1906087b7..8e327439af 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
@@ -115,15 +115,16 @@ public class IdentityUserManager : UserManager, IDomainService
user.PasswordHistories.Clear();
user.Passkeys.Clear();
+ //Soft deleting reloads the original values, the store saves the changes without validating the user.
+ //Nothing else is deleted before this succeeds, it is where the user is checked for concurrency.
+ (await Store.UpdateAsync(user, CancellationToken)).CheckErrors();
+
//They are in the host database and deleting them here covers the current unit of work.
using (CurrentTenant.Change(null))
{
await IdentityLinkUserRepository.DeleteAsync(new IdentityLinkUserInfo(user.Id, user.TenantId), CancellationToken);
}
- //Soft deleting reloads the original values, the store saves the changes without validating the user.
- (await Store.UpdateAsync(user, CancellationToken)).CheckErrors();
-
return await base.DeleteAsync(user);
}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs
index efc78184a9..f6272f6878 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs
@@ -50,38 +50,47 @@ public class OrganizationUnitManager : DomainService
///
/// Creates the given organization units by querying the siblings of a parent once instead of once
- /// per organization unit. The parents must already exist. Custom validation should be added by
- /// overriding .
+ /// per organization unit. They all have to belong to the current tenant and their parents must
+ /// already exist. is used for the first organization unit of a
+ /// parent, the codes of the rest follow it. Custom validation should be added by overriding
+ /// .
///
[UnitOfWork]
public virtual async Task CreateManyAsync(List organizationUnits)
{
Check.NotNull(organizationUnits, nameof(organizationUnits));
- foreach (var group in organizationUnits.GroupBy(x => new { x.TenantId, x.ParentId }))
+ if (organizationUnits.Any(x => x.TenantId != CurrentTenant.Id))
{
- //Siblings, codes and the database of a group belong to its own tenant.
- using (CurrentTenant.Change(group.Key.TenantId))
- {
- await ValidateParentTenantAsync(group.Key.ParentId, group.Key.TenantId);
+ throw new AbpException("Organization units of another tenant can not be created, change the current tenant instead!");
+ }
- var siblings = await FindChildrenAsync(group.Key.ParentId);
- var lastCode = siblings.OrderBy(x => x.Code).LastOrDefault()?.Code;
+ var groups = organizationUnits.GroupBy(x => x.ParentId).ToList();
+
+ foreach (var group in groups)
+ {
+ await ValidateParentTenantAsync(group.Key, CurrentTenant.Id);
- foreach (var organizationUnit in group)
- {
- await ValidateOrganizationUnitAsync(organizationUnit, siblings);
+ var siblings = await FindChildrenAsync(group.Key);
+ string lastCode = null;
- organizationUnit.Code = lastCode = lastCode == null
- ? await GetNextChildCodeAsync(group.Key.ParentId)
- : OrganizationUnit.CalculateNextCode(lastCode);
+ foreach (var organizationUnit in group)
+ {
+ organizationUnit.Code = lastCode = lastCode == null
+ ? await GetNextChildCodeAsync(group.Key)
+ : OrganizationUnit.CalculateNextCode(lastCode);
- siblings.Add(organizationUnit);
- }
+ await ValidateOrganizationUnitAsync(organizationUnit, siblings);
- await OrganizationUnitRepository.InsertManyAsync(group.ToList());
+ siblings.Add(organizationUnit);
}
}
+
+ //Nothing is inserted before every group is validated.
+ foreach (var group in groups)
+ {
+ await OrganizationUnitRepository.InsertManyAsync(group.ToList());
+ }
}
public virtual async Task UpdateAsync(OrganizationUnit organizationUnit)
diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_CreateMany_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_CreateMany_Tests.cs
new file mode 100644
index 0000000000..94f6dce0eb
--- /dev/null
+++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_CreateMany_Tests.cs
@@ -0,0 +1,5 @@
+namespace Volo.Abp.Identity;
+
+public class OrganizationUnitManager_CreateMany_Tests : OrganizationUnitManager_CreateMany_Tests
+{
+}
diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs
index 0c0083c88d..a9c51678c2 100644
--- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs
+++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs
@@ -5,6 +5,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Threading;
+using Volo.Abp.Security.Claims;
+using Volo.Abp.Identity.Localization;
+using Volo.Abp.Caching;
+using Microsoft.Extensions.Localization;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
@@ -232,4 +238,84 @@ public class OrganizationUnitManager_Tests : AbpIdentityDomainTestBase
await uow.CompleteAsync();
}
}
+
+ [Fact]
+ public async Task CreateManyAsync_Should_Not_Create_Organization_Units_Of_Another_Tenant()
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ await Should.ThrowAsync(async () =>
+ await _organizationUnitManager.CreateManyAsync([
+ new OrganizationUnit(_guidGenerator.Create(), "another-tenant", null, Guid.NewGuid())
+ ]));
+
+ await uow.CompleteAsync();
+ }
+ }
+
+ [Fact]
+ public async Task CreateManyAsync_Should_Use_The_Overridden_Extension_Points()
+ {
+ var manager = new TestOrganizationUnitManager(
+ _organizationUnitRepository,
+ GetRequiredService>(),
+ _identityRoleRepository,
+ GetRequiredService>(),
+ GetRequiredService())
+ {
+ LazyServiceProvider = GetRequiredService()
+ };
+
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ await Should.ThrowAsync(async () =>
+ await manager.CreateManyAsync([new OrganizationUnit(_guidGenerator.Create(), "rejected-by-the-override")]));
+
+ await manager.CreateManyAsync([
+ new OrganizationUnit(_guidGenerator.Create(), $"extension-point-1-{Guid.NewGuid():N}"),
+ new OrganizationUnit(_guidGenerator.Create(), $"extension-point-2-{Guid.NewGuid():N}")
+ ]);
+
+ await uow.CompleteAsync();
+ }
+
+ //Every organization unit is validated, the code generator is only used for the first one of a parent.
+ //Both calls above created a root organization unit, so the code generator was used twice.
+ manager.ValidateCallCount.ShouldBe(3);
+ manager.GetNextChildCodeCallCount.ShouldBe(2);
+ }
+
+ public class TestOrganizationUnitManager : OrganizationUnitManager
+ {
+ public int ValidateCallCount { get; private set; }
+ public int GetNextChildCodeCallCount { get; private set; }
+
+ public TestOrganizationUnitManager(
+ IOrganizationUnitRepository organizationUnitRepository,
+ IStringLocalizer localizer,
+ IIdentityRoleRepository identityRoleRepository,
+ IDistributedCache dynamicClaimCache,
+ ICancellationTokenProvider cancellationTokenProvider)
+ : base(organizationUnitRepository, localizer, identityRoleRepository, dynamicClaimCache, cancellationTokenProvider)
+ {
+ }
+
+ public override async Task GetNextChildCodeAsync(Guid? parentId)
+ {
+ GetNextChildCodeCallCount++;
+ return await base.GetNextChildCodeAsync(parentId);
+ }
+
+ protected override async Task ValidateOrganizationUnitAsync(OrganizationUnit organizationUnit, List siblings)
+ {
+ ValidateCallCount++;
+
+ if (organizationUnit.DisplayName == "rejected-by-the-override")
+ {
+ throw new BusinessException(IdentityErrorCodes.DuplicateOrganizationUnitDisplayName);
+ }
+
+ await base.ValidateOrganizationUnitAsync(organizationUnit, siblings);
+ }
+ }
}
diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitManager_CreateMany_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitManager_CreateMany_Tests.cs
new file mode 100644
index 0000000000..35f8004c5b
--- /dev/null
+++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitManager_CreateMany_Tests.cs
@@ -0,0 +1,8 @@
+using Xunit;
+
+namespace Volo.Abp.Identity.MongoDB;
+
+[Collection(MongoTestCollection.Name)]
+public class OrganizationUnitManager_CreateMany_Tests : OrganizationUnitManager_CreateMany_Tests
+{
+}
diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_Delete_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_Delete_Tests.cs
index 0f874c836a..c6e6b179c6 100644
--- a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_Delete_Tests.cs
+++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_Delete_Tests.cs
@@ -148,7 +148,18 @@ public abstract class IdentityUserManager_Delete_Tests : AbpIden
using (var uow = UnitOfWorkManager.Begin())
{
var user = await IdentityUserManager.GetByIdAsync(userId);
- await IdentityUserManager.AddToRoleAsync(user, "moderator");
+
+ (await IdentityUserManager.AddClaimAsync(user, new Claim("test", "test"))).CheckErrors();
+ (await IdentityUserManager.AddLoginAsync(user, new UserLoginInfo("test", "test", "test"))).CheckErrors();
+ (await IdentityUserManager.AddToRoleAsync(user, "moderator")).CheckErrors();
+ user.SetToken("test", "test", "test");
+ user.AddPasswordHistory("test");
+ user.AddPasskey([1, 2, 3], new IdentityPasskeyData());
+ await IdentityUserManager.AddToOrganizationUnitAsync(
+ user,
+ await OrganizationUnitRepository.GetAsync(LookupNormalizer.NormalizeName("OU11")));
+ await IdentityUserRepository.UpdateAsync(user);
+
await uow.CompleteAsync();
}
@@ -163,12 +174,20 @@ public abstract class IdentityUserManager_Delete_Tests : AbpIden
await uow.CompleteAsync();
}
+ //Every collection of the user has to be loaded before it is cleared.
using (var uow = UnitOfWorkManager.Begin())
using (DataFilter.Disable())
{
var deletedUser = await IdentityUserRepository.FindAsync(userId);
deletedUser.ShouldNotBeNull();
+
+ deletedUser.Claims.Count.ShouldBe(0);
+ deletedUser.Logins.Count.ShouldBe(0);
deletedUser.Roles.Count.ShouldBe(0);
+ deletedUser.Tokens.Count.ShouldBe(0);
+ deletedUser.OrganizationUnits.Count.ShouldBe(0);
+ deletedUser.PasswordHistories.Count.ShouldBe(0);
+ deletedUser.Passkeys.Count.ShouldBe(0);
await uow.CompleteAsync();
}
@@ -358,4 +377,101 @@ public abstract class IdentityUserManager_Delete_Tests : AbpIden
async () => await IdentityUserManager.DeleteAsync(staleUser));
}
}
+
+ [Fact]
+ public virtual async Task Deleting_A_User_Through_The_Repository_Should_Not_Clear_Its_Own_Collections()
+ {
+ var userId = Guid.NewGuid();
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ (await IdentityUserManager.CreateAsync(
+ new IdentityUser(userId, $"repo-collections-{userId:N}", $"repo-collections-{userId:N}@abp.io"))).CheckErrors();
+
+ await uow.CompleteAsync();
+ }
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ var user = await IdentityUserManager.GetByIdAsync(userId);
+ (await IdentityUserManager.AddToRoleAsync(user, "moderator")).CheckErrors();
+ user.AddPasswordHistory("test");
+ await IdentityUserRepository.UpdateAsync(user);
+
+ await uow.CompleteAsync();
+ }
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ await IdentityUserRepository.DeleteAsync(await IdentityUserRepository.GetAsync(userId));
+
+ await uow.CompleteAsync();
+ }
+
+ //UserDeletedEventHandler only covers the aggregates that have no navigation from the user,
+ //the collections of the user itself are cleared by IdentityUserManager.DeleteAsync.
+ using (var uow = UnitOfWorkManager.Begin())
+ using (DataFilter.Disable())
+ {
+ var deletedUser = await IdentityUserRepository.FindAsync(userId);
+ deletedUser.Roles.Count.ShouldBe(1);
+ deletedUser.PasswordHistories.Count.ShouldBe(1);
+
+ await uow.CompleteAsync();
+ }
+ }
+
+ [Fact]
+ public virtual async Task Deleting_A_Stale_User_Should_Not_Delete_Its_Link_Users()
+ {
+ var userId = Guid.NewGuid();
+ var linkedUserId = Guid.NewGuid();
+ IdentityUser staleUser;
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ (await IdentityUserManager.CreateAsync(
+ new IdentityUser(userId, $"stale-link-{userId:N}", $"stale-link-{userId:N}@abp.io"))).CheckErrors();
+ (await IdentityUserManager.CreateAsync(
+ new IdentityUser(linkedUserId, $"stale-linked-{linkedUserId:N}", $"stale-linked-{linkedUserId:N}@abp.io"))).CheckErrors();
+
+ await uow.CompleteAsync();
+ }
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ await IdentityLinkUserManager.LinkAsync(
+ new IdentityLinkUserInfo(userId),
+ new IdentityLinkUserInfo(linkedUserId));
+
+ staleUser = await IdentityUserRepository.GetAsync(userId);
+
+ await uow.CompleteAsync();
+ }
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ var user = await IdentityUserRepository.GetAsync(userId);
+ user.Name = "Changed";
+ await IdentityUserRepository.UpdateAsync(user);
+
+ await uow.CompleteAsync();
+ }
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ await Should.ThrowAsync(
+ async () => await IdentityUserManager.DeleteAsync(staleUser));
+ }
+
+ //The user is still there, so its link users must be there as well.
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ (await IdentityLinkUserManager.IsLinkedAsync(
+ new IdentityLinkUserInfo(userId),
+ new IdentityLinkUserInfo(linkedUserId))).ShouldBeTrue();
+
+ await uow.CompleteAsync();
+ }
+ }
}
diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitManager_CreateMany_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitManager_CreateMany_Tests.cs
new file mode 100644
index 0000000000..347d91b9c9
--- /dev/null
+++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitManager_CreateMany_Tests.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Shouldly;
+using Volo.Abp.Guids;
+using Volo.Abp.Modularity;
+using Volo.Abp.Uow;
+using Xunit;
+
+namespace Volo.Abp.Identity;
+
+public abstract class OrganizationUnitManager_CreateMany_Tests : AbpIdentityTestBase
+ where TStartupModule : IAbpModule
+{
+ protected OrganizationUnitManager OrganizationUnitManager { get; }
+ protected IOrganizationUnitRepository OrganizationUnitRepository { get; }
+ protected ILookupNormalizer LookupNormalizer { get; }
+ protected IUnitOfWorkManager UnitOfWorkManager { get; }
+ protected IGuidGenerator GuidGenerator { get; }
+
+ protected OrganizationUnitManager_CreateMany_Tests()
+ {
+ OrganizationUnitManager = GetRequiredService();
+ OrganizationUnitRepository = GetRequiredService();
+ LookupNormalizer = GetRequiredService();
+ UnitOfWorkManager = GetRequiredService();
+ GuidGenerator = GetRequiredService();
+ }
+
+ [Fact]
+ public virtual async Task Should_Not_Insert_Anything_When_A_Group_Is_Not_Valid()
+ {
+ var validDisplayName = $"valid-{Guid.NewGuid():N}";
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ var parent = await OrganizationUnitRepository.GetAsync("OU1");
+
+ //The second group is not valid, OU11 is already a child of OU1.
+ await Should.ThrowAsync(async () =>
+ await OrganizationUnitManager.CreateManyAsync([
+ new OrganizationUnit(GuidGenerator.Create(), validDisplayName),
+ new OrganizationUnit(GuidGenerator.Create(), "OU11", parent.Id)
+ ]));
+ }
+
+ using (var uow = UnitOfWorkManager.Begin())
+ {
+ (await OrganizationUnitRepository.GetAsync(validDisplayName)).ShouldBeNull();
+
+ await uow.CompleteAsync();
+ }
+ }
+}