1
0
mirror of https://github.com/bitwarden/server synced 2025-12-14 15:23:42 +00:00

[PM-12492] Create ResendOrganizationInviteCommand (#6182)

* Add IResendOrganizationInviteCommand and ResendOrganizationInviteCommand implementation

* Add unit tests for ResendOrganizationInviteCommand to validate invite resend functionality

* Refactor Organizations, OrganizationUsers, and Members controllers to use IResendInviteCommand for invite resending functionality

* Fix Organizations, OrganizationUsers, and Members controllers to replace IResendInviteCommand with IResendOrganizationInviteCommand

* Remove ResendInviteAsync method from IOrganizationService and its implementation in OrganizationService to streamline invite management functionality.

* Add IResendOrganizationInviteCommand registration in OrganizationServiceCollectionExtensions
This commit is contained in:
Rui Tomé
2025-08-14 15:02:00 +01:00
committed by GitHub
parent 4e6a036f22
commit c30c0c1d2a
9 changed files with 226 additions and 25 deletions

View File

@@ -9,6 +9,7 @@ using Bit.Admin.Utilities;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.Providers.Interfaces; using Bit.Core.AdminConsole.Providers.Interfaces;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums; using Bit.Core.Billing.Enums;
@@ -32,7 +33,6 @@ namespace Bit.Admin.AdminConsole.Controllers;
[Authorize] [Authorize]
public class OrganizationsController : Controller public class OrganizationsController : Controller
{ {
private readonly IOrganizationService _organizationService;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository; private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
@@ -55,9 +55,9 @@ public class OrganizationsController : Controller
private readonly IProviderBillingService _providerBillingService; private readonly IProviderBillingService _providerBillingService;
private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand; private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand;
private readonly IPricingClient _pricingClient; private readonly IPricingClient _pricingClient;
private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand;
public OrganizationsController( public OrganizationsController(
IOrganizationService organizationService,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IOrganizationConnectionRepository organizationConnectionRepository, IOrganizationConnectionRepository organizationConnectionRepository,
@@ -79,9 +79,9 @@ public class OrganizationsController : Controller
IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand, IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand,
IProviderBillingService providerBillingService, IProviderBillingService providerBillingService,
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand, IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand,
IPricingClient pricingClient) IPricingClient pricingClient,
IResendOrganizationInviteCommand resendOrganizationInviteCommand)
{ {
_organizationService = organizationService;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_organizationConnectionRepository = organizationConnectionRepository; _organizationConnectionRepository = organizationConnectionRepository;
@@ -104,6 +104,7 @@ public class OrganizationsController : Controller
_providerBillingService = providerBillingService; _providerBillingService = providerBillingService;
_organizationInitiateDeleteCommand = organizationInitiateDeleteCommand; _organizationInitiateDeleteCommand = organizationInitiateDeleteCommand;
_pricingClient = pricingClient; _pricingClient = pricingClient;
_resendOrganizationInviteCommand = resendOrganizationInviteCommand;
} }
[RequirePermission(Permission.Org_List_View)] [RequirePermission(Permission.Org_List_View)]
@@ -395,7 +396,7 @@ public class OrganizationsController : Controller
var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, OrganizationUserType.Owner); var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, OrganizationUserType.Owner);
foreach (var organizationUser in organizationUsers) foreach (var organizationUser in organizationUsers)
{ {
await _organizationService.ResendInviteAsync(id, null, organizationUser.Id, true); await _resendOrganizationInviteCommand.ResendInviteAsync(id, null, organizationUser.Id, true);
} }
return Json(null); return Json(null);

View File

@@ -12,6 +12,7 @@ using Bit.Core;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v1; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v1;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
@@ -62,6 +63,7 @@ public class OrganizationUsersController : Controller
private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly IPricingClient _pricingClient; private readonly IPricingClient _pricingClient;
private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand;
private readonly IConfirmOrganizationUserCommand _confirmOrganizationUserCommand; private readonly IConfirmOrganizationUserCommand _confirmOrganizationUserCommand;
private readonly IRestoreOrganizationUserCommand _restoreOrganizationUserCommand; private readonly IRestoreOrganizationUserCommand _restoreOrganizationUserCommand;
private readonly IInitPendingOrganizationCommand _initPendingOrganizationCommand; private readonly IInitPendingOrganizationCommand _initPendingOrganizationCommand;
@@ -92,7 +94,8 @@ public class OrganizationUsersController : Controller
IConfirmOrganizationUserCommand confirmOrganizationUserCommand, IConfirmOrganizationUserCommand confirmOrganizationUserCommand,
IRestoreOrganizationUserCommand restoreOrganizationUserCommand, IRestoreOrganizationUserCommand restoreOrganizationUserCommand,
IInitPendingOrganizationCommand initPendingOrganizationCommand, IInitPendingOrganizationCommand initPendingOrganizationCommand,
IRevokeOrganizationUserCommand revokeOrganizationUserCommand) IRevokeOrganizationUserCommand revokeOrganizationUserCommand,
IResendOrganizationInviteCommand resendOrganizationInviteCommand)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@@ -116,6 +119,7 @@ public class OrganizationUsersController : Controller
_policyRequirementQuery = policyRequirementQuery; _policyRequirementQuery = policyRequirementQuery;
_featureService = featureService; _featureService = featureService;
_pricingClient = pricingClient; _pricingClient = pricingClient;
_resendOrganizationInviteCommand = resendOrganizationInviteCommand;
_confirmOrganizationUserCommand = confirmOrganizationUserCommand; _confirmOrganizationUserCommand = confirmOrganizationUserCommand;
_restoreOrganizationUserCommand = restoreOrganizationUserCommand; _restoreOrganizationUserCommand = restoreOrganizationUserCommand;
_initPendingOrganizationCommand = initPendingOrganizationCommand; _initPendingOrganizationCommand = initPendingOrganizationCommand;
@@ -266,7 +270,7 @@ public class OrganizationUsersController : Controller
public async Task Reinvite(Guid orgId, Guid id) public async Task Reinvite(Guid orgId, Guid id)
{ {
var userId = _userService.GetProperUserId(User); var userId = _userService.GetProperUserId(User);
await _organizationService.ResendInviteAsync(orgId, userId.Value, id); await _resendOrganizationInviteCommand.ResendInviteAsync(orgId, userId.Value, id);
} }
[HttpPost("{organizationUserId}/accept-init")] [HttpPost("{organizationUserId}/accept-init")]

View File

@@ -6,6 +6,7 @@ using Bit.Api.AdminConsole.Public.Models.Request;
using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.AdminConsole.Public.Models.Response;
using Bit.Api.Models.Public.Response; using Bit.Api.Models.Public.Response;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Context; using Bit.Core.Context;
@@ -32,6 +33,7 @@ public class MembersController : Controller
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand;
public MembersController( public MembersController(
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
@@ -45,7 +47,8 @@ public class MembersController : Controller
IPaymentService paymentService, IPaymentService paymentService,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IRemoveOrganizationUserCommand removeOrganizationUserCommand) IRemoveOrganizationUserCommand removeOrganizationUserCommand,
IResendOrganizationInviteCommand resendOrganizationInviteCommand)
{ {
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_groupRepository = groupRepository; _groupRepository = groupRepository;
@@ -59,6 +62,7 @@ public class MembersController : Controller
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_removeOrganizationUserCommand = removeOrganizationUserCommand; _removeOrganizationUserCommand = removeOrganizationUserCommand;
_resendOrganizationInviteCommand = resendOrganizationInviteCommand;
} }
/// <summary> /// <summary>
@@ -260,7 +264,7 @@ public class MembersController : Controller
{ {
return new NotFoundResult(); return new NotFoundResult();
} }
await _organizationService.ResendInviteAsync(_currentContext.OrganizationId.Value, null, id); await _resendOrganizationInviteCommand.ResendInviteAsync(_currentContext.OrganizationId.Value, null, id);
return new OkResult(); return new OkResult();
} }
} }

View File

@@ -0,0 +1,14 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
public interface IResendOrganizationInviteCommand
{
/// <summary>
/// Resend an invite to an organization user.
/// </summary>
/// <param name="organizationId">The ID of the organization.</param>
/// <param name="invitingUserId">The ID of the user who is inviting the organization user.</param>
/// <param name="organizationUserId">The ID of the organization user to resend the invite to.</param>
/// <param name="initOrganization">Whether to initialize the organization.
/// This is should only be true when inviting the owner of a new organization.</param>
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
}

View File

@@ -0,0 +1,56 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.Utilities.DebuggingInstruments;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Microsoft.Extensions.Logging;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
public class ResendOrganizationInviteCommand : IResendOrganizationInviteCommand
{
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand;
private readonly ILogger<ResendOrganizationInviteCommand> _logger;
public ResendOrganizationInviteCommand(
IOrganizationUserRepository organizationUserRepository,
IOrganizationRepository organizationRepository,
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand,
ILogger<ResendOrganizationInviteCommand> logger)
{
_organizationUserRepository = organizationUserRepository;
_organizationRepository = organizationRepository;
_sendOrganizationInvitesCommand = sendOrganizationInvitesCommand;
_logger = logger;
}
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId,
bool initOrganization = false)
{
var organizationUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if (organizationUser == null || organizationUser.OrganizationId != organizationId ||
organizationUser.Status != OrganizationUserStatusType.Invited)
{
throw new BadRequestException("User invalid.");
}
_logger.LogUserInviteStateDiagnostics(organizationUser);
var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId);
if (organization == null)
{
throw new BadRequestException("Organization invalid.");
}
await SendInviteAsync(organizationUser, organization, initOrganization);
}
private async Task SendInviteAsync(OrganizationUser organizationUser, Organization organization, bool initOrganization) =>
await _sendOrganizationInvitesCommand.SendInvitesAsync(new SendInvitesRequest(
users: [organizationUser],
organization: organization,
initOrganization: initOrganization));
}

View File

@@ -29,7 +29,6 @@ public interface IOrganizationService
Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, Task<List<OrganizationUser>> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser,
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites); IEnumerable<(OrganizationUserInvite invite, string externalId)> invites);
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId); Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId); Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
Task ImportAsync(Guid organizationId, IEnumerable<ImportedGroup> groups, Task ImportAsync(Guid organizationId, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds, IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,

View File

@@ -766,21 +766,6 @@ public class OrganizationService : IOrganizationService
return result; return result;
} }
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId,
bool initOrganization = false)
{
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if (orgUser == null || orgUser.OrganizationId != organizationId ||
orgUser.Status != OrganizationUserStatusType.Invited)
{
throw new BadRequestException("User invalid.");
}
_logger.LogUserInviteStateDiagnostics(orgUser);
var org = await GetOrgById(orgUser.OrganizationId);
await SendInviteAsync(orgUser, org, initOrganization);
}
private async Task SendInvitesAsync(IEnumerable<OrganizationUser> orgUsers, Organization organization) => private async Task SendInvitesAsync(IEnumerable<OrganizationUser> orgUsers, Organization organization) =>
await _sendOrganizationInvitesCommand.SendInvitesAsync(new SendInvitesRequest(orgUsers, organization)); await _sendOrganizationInvitesCommand.SendInvitesAsync(new SendInvitesRequest(orgUsers, organization));

View File

@@ -186,6 +186,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IInviteOrganizationUsersCommand, InviteOrganizationUsersCommand>(); services.AddScoped<IInviteOrganizationUsersCommand, InviteOrganizationUsersCommand>();
services.AddScoped<ISendOrganizationInvitesCommand, SendOrganizationInvitesCommand>(); services.AddScoped<ISendOrganizationInvitesCommand, SendOrganizationInvitesCommand>();
services.AddScoped<IResendOrganizationInviteCommand, ResendOrganizationInviteCommand>();
services.AddScoped<IInviteUsersValidator, InviteOrganizationUsersValidator>(); services.AddScoped<IInviteUsersValidator, InviteOrganizationUsersValidator>();
services.AddScoped<IInviteUsersOrganizationValidator, InviteUsersOrganizationValidator>(); services.AddScoped<IInviteUsersOrganizationValidator, InviteUsersOrganizationValidator>();

View File

@@ -0,0 +1,137 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
[SutProviderCustomize]
public class ResendOrganizationInviteCommandTests
{
[Theory]
[BitAutoData]
public async Task ResendInviteAsync_WhenValidUserAndOrganization_SendsInvite(
Organization organization,
OrganizationUser organizationUser,
SutProvider<ResendOrganizationInviteCommand> sutProvider)
{
// Arrange
organizationUser.OrganizationId = organization.Id;
organizationUser.Status = OrganizationUserStatusType.Invited;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
// Act
await sutProvider.Sut.ResendInviteAsync(organization.Id, invitingUserId: null, organizationUser.Id);
// Assert
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.Received(1)
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(req =>
req.Organization == organization &&
req.Users.Length == 1 &&
req.Users[0] == organizationUser &&
req.InitOrganization == false));
}
[Theory]
[BitAutoData]
public async Task ResendInviteAsync_WhenInitOrganizationTrue_SendsInviteWithInitFlag(
Organization organization,
OrganizationUser organizationUser,
SutProvider<ResendOrganizationInviteCommand> sutProvider)
{
// Arrange
organizationUser.OrganizationId = organization.Id;
organizationUser.Status = OrganizationUserStatusType.Invited;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
// Act
await sutProvider.Sut.ResendInviteAsync(organization.Id, invitingUserId: null, organizationUser.Id, initOrganization: true);
// Assert
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.Received(1)
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(req =>
req.Organization == organization &&
req.Users.Length == 1 &&
req.Users[0] == organizationUser &&
req.InitOrganization == true));
}
[Theory]
[BitAutoData]
public async Task ResendInviteAsync_WhenOrganizationUserInvalid_ThrowsBadRequest(
Organization organization,
OrganizationUser organizationUser,
SutProvider<ResendOrganizationInviteCommand> sutProvider)
{
// Arrange
organizationUser.OrganizationId = organization.Id;
organizationUser.Status = OrganizationUserStatusType.Accepted;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
// Act + Assert
var ex = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.ResendInviteAsync(organization.Id, invitingUserId: null, organizationUser.Id));
Assert.Equal("User invalid.", ex.Message);
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.DidNotReceive()
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
}
[Theory]
[BitAutoData]
public async Task ResendInviteAsync_WhenOrganizationNotFound_ThrowsBadRequest(
Organization organization,
OrganizationUser organizationUser,
SutProvider<ResendOrganizationInviteCommand> sutProvider)
{
// Arrange
organizationUser.OrganizationId = organization.Id;
organizationUser.Status = OrganizationUserStatusType.Invited;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns((Organization?)null);
// Act + Assert
var ex = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.ResendInviteAsync(organization.Id, invitingUserId: null, organizationUser.Id));
Assert.Equal("Organization invalid.", ex.Message);
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.DidNotReceive()
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
}
}