mirror of
https://github.com/bitwarden/server
synced 2025-12-10 13:23:27 +00:00
* Add new event types for collection management settings in EventType enum * Refactor collection management settings update process in OrganizationsController and IOrganizationService. Introduced UpdateCollectionManagementSettingsAsync method to streamline updates and logging for collection management settings. * Add unit tests for collection management settings updates in OrganizationsController and OrganizationService. Implemented tests to verify the successful update of collection management settings and the logging of specific events when settings are changed. Added error handling for cases where the organization is not found. * Refactor collection management settings handling in OrganizationsController and IOrganizationService. Updated the UpdateCollectionManagementSettingsAsync method to accept a single settings object, simplifying the parameter list and improving code readability. Introduced a new OrganizationCollectionManagementSettings model to encapsulate collection management settings. Adjusted related tests to reflect these changes. * Add Obsolete attribute to Organization_CollectionManagement_Updated event in EventType enum
1373 lines
67 KiB
C#
1373 lines
67 KiB
C#
using System.Text.Json;
|
|
using Bit.Core.AdminConsole.Entities.Provider;
|
|
using Bit.Core.AdminConsole.Enums.Provider;
|
|
using Bit.Core.AdminConsole.Models.Business;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
|
using Bit.Core.AdminConsole.Repositories;
|
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
|
using Bit.Core.Billing.Enums;
|
|
using Bit.Core.Billing.Pricing;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Business;
|
|
using Bit.Core.Models.Data;
|
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
|
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Settings;
|
|
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
|
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
|
using Bit.Core.Tokens;
|
|
using Bit.Core.Utilities;
|
|
using Bit.Test.Common.AutoFixture;
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
using Bit.Test.Common.Fakes;
|
|
using NSubstitute;
|
|
using NSubstitute.ExceptionExtensions;
|
|
using NSubstitute.ReturnsExtensions;
|
|
using Stripe;
|
|
using Xunit;
|
|
using Organization = Bit.Core.AdminConsole.Entities.Organization;
|
|
using OrganizationUser = Bit.Core.Entities.OrganizationUser;
|
|
using OrganizationUserInvite = Bit.Core.Models.Business.OrganizationUserInvite;
|
|
|
|
namespace Bit.Core.Test.Services;
|
|
|
|
[SutProviderCustomize]
|
|
public class OrganizationServiceTests
|
|
{
|
|
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Owner), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_NoEmails_Throws(Organization organization, OrganizationUser invitor,
|
|
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
invite.Emails = null;
|
|
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
await Assert.ThrowsAsync<NotFoundException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize, OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_DuplicateEmails_PassesWithoutDuplicates(Organization organization, OrganizationUser invitor,
|
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
|
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
|
|
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
|
|
sutProvider.Create();
|
|
|
|
invite.Emails = invite.Emails.Append(invite.Emails.First());
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
|
|
.Returns(new[] { owner });
|
|
|
|
// Must set guids in order for dictionary of guids to not throw aggregate exceptions
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) });
|
|
|
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().Received(1)
|
|
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(request =>
|
|
request.Users.DistinctBy(x => x.Email).Count() == invite.Emails.Distinct().Count() &&
|
|
request.Organization == organization));
|
|
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Admin,
|
|
InvitorUserType = OrganizationUserType.Owner
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_NoOwner_Throws(Organization organization, OrganizationUser invitor,
|
|
OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Owner,
|
|
InvitorUserType = OrganizationUserType.Admin
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_NonOwnerConfiguringOwner_Throws(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
currentContext.OrganizationAdmin(organization.Id).Returns(true);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
Assert.Contains("only an owner", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Custom,
|
|
InvitorUserType = OrganizationUserType.User
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_NonAdminConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.UseCustomPermissions = true;
|
|
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
currentContext.OrganizationUser(organization.Id).Returns(true);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
Assert.Contains("your account does not have permission to manage users", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Custom,
|
|
InvitorUserType = OrganizationUserType.Admin
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_WithCustomType_WhenUseCustomPermissionsIsFalse_Throws(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.UseCustomPermissions = false;
|
|
|
|
invite.Permissions = null;
|
|
invitor.Status = OrganizationUserStatusType.Confirmed;
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
|
|
.Returns(new[] { invitor });
|
|
currentContext.OrganizationOwner(organization.Id).Returns(true);
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
Assert.Contains("to enable custom permissions", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Custom,
|
|
InvitorUserType = OrganizationUserType.Admin
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_WithCustomType_WhenUseCustomPermissionsIsTrue_Passes(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.Seats = 10;
|
|
organization.UseCustomPermissions = true;
|
|
|
|
invite.Permissions = null;
|
|
invitor.Status = OrganizationUserStatusType.Confirmed;
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
|
|
currentContext.OrganizationOwner(organization.Id).Returns(true);
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) });
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(OrganizationUserType.Admin)]
|
|
[BitAutoData(OrganizationUserType.Owner)]
|
|
[BitAutoData(OrganizationUserType.User)]
|
|
public async Task InviteUsers_WithNonCustomType_WhenUseCustomPermissionsIsFalse_Passes(OrganizationUserType inviteUserType, Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.Seats = 10;
|
|
organization.UseCustomPermissions = false;
|
|
|
|
invite.Type = inviteUserType;
|
|
invite.Permissions = null;
|
|
invitor.Status = OrganizationUserStatusType.Confirmed;
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
|
|
currentContext.OrganizationOwner(organization.Id).Returns(true);
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) });
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_CustomUserWithoutManageUsersConfiguringUser_Throws(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = false },
|
|
new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
});
|
|
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
currentContext.OrganizationCustom(organization.Id).Returns(true);
|
|
currentContext.ManageUsers(organization.Id).Returns(false);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
Assert.Contains("account does not have permission", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Admin,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_CustomUserConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
|
new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
});
|
|
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
currentContext.OrganizationCustom(organization.Id).Returns(true);
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) }));
|
|
Assert.Contains("can not manage admins", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Owner
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_NoPermissionsObject_Passes(Organization organization, OrganizationUserInvite invite,
|
|
OrganizationUser invitor, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
invite.Permissions = null;
|
|
invitor.Status = OrganizationUserStatusType.Confirmed;
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
|
|
currentContext.OrganizationOwner(organization.Id).Returns(true);
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, new (OrganizationUserInvite, string)[] { (invite, null) });
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, string externalId,
|
|
OrganizationUser invitor,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// This method is only used to invite 1 user at a time
|
|
invite.Emails = new[] { invite.Emails.First() };
|
|
|
|
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
|
|
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
|
|
sutProvider.Create();
|
|
|
|
InviteUser_ArrangeCurrentContextPermissions(organization, sutProvider);
|
|
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
|
|
// Mock tokenable factory to return a token that expires in 5 days
|
|
// sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
|
|
// .CreateToken(Arg.Any<OrganizationUser>())
|
|
// .Returns(
|
|
// info => new OrgUserInviteTokenable(info.Arg<OrganizationUser>())
|
|
// {
|
|
// ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5))
|
|
// }
|
|
// );
|
|
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
await sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, systemUser: null, invite, externalId);
|
|
|
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().Received(1)
|
|
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(request =>
|
|
request.Users.Length == 1 &&
|
|
request.Organization == organization));
|
|
|
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUser_InvitingMoreThanOneUser_Throws(Organization organization, OrganizationUserInvite invite, string externalId,
|
|
OrganizationUser invitor,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, systemUser: null, invite, externalId));
|
|
Assert.Contains("This method can only be used to invite a single user.", exception.Message);
|
|
|
|
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
|
|
.SendOrganizationInviteEmailsAsync(default);
|
|
await sutProvider.GetDependency<IEventService>().DidNotReceive()
|
|
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>());
|
|
await sutProvider.GetDependency<IEventService>().DidNotReceive()
|
|
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUser_UserAlreadyInvited_Throws(Organization organization, OrganizationUserInvite invite, string externalId,
|
|
OrganizationUser invitor,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// This method is only used to invite 1 user at a time
|
|
invite.Emails = new[] { invite.Emails.First() };
|
|
|
|
// The user has already been invited
|
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
|
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
|
|
.Returns(new List<string> { invite.Emails.First() });
|
|
|
|
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
|
|
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
|
|
sutProvider.Create();
|
|
|
|
InviteUser_ArrangeCurrentContextPermissions(organization, sutProvider);
|
|
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut
|
|
.InviteUserAsync(organization.Id, invitor.UserId, systemUser: null, invite, externalId));
|
|
Assert.Contains("This user has already been invited", exception.Message);
|
|
|
|
// SendOrganizationInvitesCommand and EventService are still called, but with no OrgUsers
|
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().Received(1)
|
|
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(info =>
|
|
info.Organization == organization &&
|
|
info.Users.Length == 0));
|
|
await sutProvider.GetDependency<IEventService>().Received(1)
|
|
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events => !events.Any()));
|
|
}
|
|
|
|
private void InviteUser_ArrangeCurrentContextPermissions(Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
currentContext.AccessReports(organization.Id).Returns(true);
|
|
currentContext.ManageGroups(organization.Id).Returns(true);
|
|
currentContext.ManagePolicies(organization.Id).Returns(true);
|
|
currentContext.ManageScim(organization.Id).Returns(true);
|
|
currentContext.ManageSso(organization.Id).Returns(true);
|
|
currentContext.AccessEventLogs(organization.Id).Returns(true);
|
|
currentContext.AccessImportExport(organization.Id).Returns(true);
|
|
currentContext.EditAnyCollection(organization.Id).Returns(true);
|
|
currentContext.ManageResetPassword(organization.Id).Returns(true);
|
|
currentContext.GetOrganization(organization.Id)
|
|
.Returns(new CurrentContextOrganization()
|
|
{
|
|
Permissions = new Permissions
|
|
{
|
|
CreateNewCollections = true,
|
|
DeleteAnyCollection = true
|
|
}
|
|
});
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
|
|
OrganizationUser invitor,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
|
|
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
|
|
sutProvider.Create();
|
|
|
|
InviteUser_ArrangeCurrentContextPermissions(organization, sutProvider);
|
|
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, invites);
|
|
|
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().Received(1)
|
|
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(info =>
|
|
info.Organization == organization &&
|
|
info.Users.Length == invites.SelectMany(x => x.invite.Emails).Distinct().Count()));
|
|
|
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.User,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), OrganizationCustomize, BitAutoData]
|
|
public async Task InviteUsers_WithEventSystemUser_Passes(Organization organization, EventSystemUser eventSystemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
|
|
OrganizationUser invitor,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
|
|
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
|
|
sutProvider.Create();
|
|
|
|
invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true },
|
|
new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
});
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
|
|
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
|
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
|
|
.Returns(true);
|
|
|
|
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
|
|
currentContext.ManageUsers(organization.Id).Returns(true);
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitingUserId: null, eventSystemUser, invites);
|
|
|
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>().Received(1)
|
|
.SendInvitesAsync(Arg.Is<SendInvitesRequest>(info =>
|
|
info.Users.Length == invites.SelectMany(i => i.invite.Emails).Count() &&
|
|
info.Organization == organization));
|
|
|
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>());
|
|
}
|
|
|
|
[Theory, BitAutoData, OrganizationCustomize, OrganizationInviteCustomize]
|
|
public async Task InviteUsers_WithSecretsManager_Passes(Organization organization,
|
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
|
|
OrganizationUser savingUser, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.PlanType = PlanType.EnterpriseAnnually;
|
|
InviteUserHelper_ArrangeValidPermissions(organization, savingUser, sutProvider);
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
|
|
// Set up some invites to grant access to SM
|
|
invites.First().invite.AccessSecretsManager = true;
|
|
var invitedSmUsers = invites.First().invite.Emails.Count();
|
|
foreach (var (invite, externalId) in invites.Skip(1))
|
|
{
|
|
invite.AccessSecretsManager = false;
|
|
}
|
|
|
|
// Assume we need to add seats for all invited SM users
|
|
sutProvider.GetDependency<ICountNewSmSeatsRequiredQuery>()
|
|
.CountNewSmSeatsRequiredAsync(organization.Id, invitedSmUsers).Returns(invitedSmUsers);
|
|
|
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
|
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
|
|
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
|
|
|
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType));
|
|
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites);
|
|
|
|
await sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>().Received(1)
|
|
.UpdateSubscriptionAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(update =>
|
|
update.SmSeats == organization.SmSeats + invitedSmUsers &&
|
|
!update.SmServiceAccountsChanged &&
|
|
!update.MaxAutoscaleSmSeatsChanged &&
|
|
!update.MaxAutoscaleSmSeatsChanged));
|
|
}
|
|
|
|
[Theory, BitAutoData, OrganizationCustomize, OrganizationInviteCustomize]
|
|
public async Task InviteUsers_WithSecretsManager_WhenErrorIsThrown_RevertsAutoscaling(Organization organization,
|
|
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
|
|
OrganizationUser savingUser, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var initialSmSeats = organization.SmSeats;
|
|
InviteUserHelper_ArrangeValidPermissions(organization, savingUser, sutProvider);
|
|
|
|
// Set up some invites to grant access to SM
|
|
invites.First().invite.AccessSecretsManager = true;
|
|
var invitedSmUsers = invites.First().invite.Emails.Count();
|
|
foreach (var (invite, externalId) in invites.Skip(1))
|
|
{
|
|
invite.AccessSecretsManager = false;
|
|
}
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
|
|
// Assume we need to add seats for all invited SM users
|
|
sutProvider.GetDependency<ICountNewSmSeatsRequiredQuery>()
|
|
.CountNewSmSeatsRequiredAsync(organization.Id, invitedSmUsers).Returns(invitedSmUsers);
|
|
|
|
// Mock SecretsManagerSubscriptionUpdateCommand to actually change the organization's subscription in memory
|
|
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
|
|
.UpdateSubscriptionAsync(Arg.Any<SecretsManagerSubscriptionUpdate>())
|
|
.ReturnsForAnyArgs(Task.FromResult(0)).AndDoes(x => organization.SmSeats += invitedSmUsers);
|
|
|
|
sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
|
|
.SendInvitesAsync(Arg.Any<SendInvitesRequest>()).ThrowsAsync<Exception>();
|
|
|
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
|
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
|
|
await Assert.ThrowsAsync<AggregateException>(async () =>
|
|
await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites));
|
|
|
|
// OrgUser is reverted
|
|
// Note: we don't know what their guids are so comparing length is the best we can do
|
|
var invitedEmails = invites.SelectMany(i => i.invite.Emails);
|
|
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).DeleteManyAsync(
|
|
Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == invitedEmails.Count()));
|
|
|
|
Received.InOrder(() =>
|
|
{
|
|
// Initial autoscaling
|
|
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
|
|
.UpdateSubscriptionAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(update =>
|
|
update.SmSeats == initialSmSeats + invitedSmUsers &&
|
|
!update.SmServiceAccountsChanged &&
|
|
!update.MaxAutoscaleSmSeatsChanged &&
|
|
!update.MaxAutoscaleSmSeatsChanged));
|
|
|
|
// Revert autoscaling
|
|
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
|
|
.UpdateSubscriptionAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(update =>
|
|
update.SmSeats == initialSmSeats &&
|
|
!update.SmServiceAccountsChanged &&
|
|
!update.MaxAutoscaleSmSeatsChanged &&
|
|
!update.MaxAutoscaleSmSeatsChanged));
|
|
});
|
|
}
|
|
|
|
private void InviteUserHelper_ArrangeValidPermissions(Organization organization, OrganizationUser savingUser,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
|
}
|
|
|
|
[Theory]
|
|
[PaidOrganizationCustomize(CheckedPlanType = PlanType.EnterpriseAnnually)]
|
|
[BitAutoData("Cannot set max seat autoscaling below seat count", 1, 0, 2, 2)]
|
|
[BitAutoData("Cannot set max seat autoscaling below seat count", 4, -1, 6, 6)]
|
|
public async Task Enterprise_UpdateMaxSeatAutoscaling_BadInputThrows(string expectedMessage,
|
|
int? maxAutoscaleSeats, int seatAdjustment, int? currentSeats, int? currentMaxAutoscaleSeats,
|
|
Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
=> await UpdateSubscription_BadInputThrows(expectedMessage, maxAutoscaleSeats, seatAdjustment, currentSeats,
|
|
currentMaxAutoscaleSeats, organization, sutProvider);
|
|
[Theory]
|
|
[FreeOrganizationCustomize]
|
|
[BitAutoData("Your plan does not allow seat autoscaling", 10, 0, null, null)]
|
|
public async Task Free_UpdateMaxSeatAutoscaling_BadInputThrows(string expectedMessage,
|
|
int? maxAutoscaleSeats, int seatAdjustment, int? currentSeats, int? currentMaxAutoscaleSeats,
|
|
Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
=> await UpdateSubscription_BadInputThrows(expectedMessage, maxAutoscaleSeats, seatAdjustment, currentSeats,
|
|
currentMaxAutoscaleSeats, organization, sutProvider);
|
|
|
|
private async Task UpdateSubscription_BadInputThrows(string expectedMessage,
|
|
int? maxAutoscaleSeats, int seatAdjustment, int? currentSeats, int? currentMaxAutoscaleSeats,
|
|
Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.Seats = currentSeats;
|
|
organization.MaxAutoscaleSeats = currentMaxAutoscaleSeats;
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
|
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
|
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscription(organization.Id,
|
|
seatAdjustment, maxAutoscaleSeats));
|
|
|
|
Assert.Contains(expectedMessage, exception.Message);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateSubscription_NoOrganization_Throws(Guid organizationId, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns((Organization)null);
|
|
|
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateSubscription(organizationId, 0, null));
|
|
}
|
|
|
|
[Theory, SecretsManagerOrganizationCustomize]
|
|
[BitAutoData("You cannot have more Secrets Manager seats than Password Manager seats.", -1)]
|
|
public async Task UpdateSubscription_PmSeatAdjustmentLessThanSmSeats_Throws(string expectedMessage,
|
|
int seatAdjustment, Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.Seats = 100;
|
|
organization.SmSeats = 100;
|
|
|
|
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
|
.Returns(StaticStore.GetPlan(organization.PlanType));
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts
|
|
{
|
|
Sponsored = 0,
|
|
Users = 1
|
|
});
|
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
|
|
|
var actual = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSubscription(organization.Id, seatAdjustment, null));
|
|
Assert.Contains(expectedMessage, actual.Message);
|
|
}
|
|
|
|
[Theory, PaidOrganizationCustomize]
|
|
[BitAutoData(0, 100, null, true, "")]
|
|
[BitAutoData(0, 100, 100, true, "")]
|
|
[BitAutoData(0, null, 100, true, "")]
|
|
[BitAutoData(1, 100, null, true, "")]
|
|
[BitAutoData(1, 100, 100, false, "Seat limit has been reached")]
|
|
public async Task CanScaleAsync(int seatsToAdd, int? currentSeats, int? maxAutoscaleSeats,
|
|
bool expectedResult, string expectedFailureMessage, Organization organization,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.Seats = currentSeats;
|
|
organization.MaxAutoscaleSeats = maxAutoscaleSeats;
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).ReturnsNull();
|
|
|
|
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, seatsToAdd);
|
|
|
|
if (expectedFailureMessage == string.Empty)
|
|
{
|
|
Assert.Empty(failureMessage);
|
|
}
|
|
else
|
|
{
|
|
Assert.Contains(expectedFailureMessage, failureMessage);
|
|
}
|
|
Assert.Equal(expectedResult, result);
|
|
}
|
|
|
|
[Theory, PaidOrganizationCustomize, BitAutoData]
|
|
public async Task CanScaleAsync_FailsOnSelfHosted(Organization organization,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
|
|
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, 10);
|
|
|
|
Assert.False(result);
|
|
Assert.Contains("Cannot autoscale on self-hosted instance", failureMessage);
|
|
}
|
|
|
|
[Theory, PaidOrganizationCustomize, BitAutoData]
|
|
public async Task CanScaleAsync_FailsOnResellerManagedOrganization(
|
|
Organization organization,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var provider = new Provider
|
|
{
|
|
Enabled = true,
|
|
Type = ProviderType.Reseller
|
|
};
|
|
|
|
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
|
|
|
|
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, 10);
|
|
|
|
Assert.False(result);
|
|
Assert.Contains("Seat limit has been reached. Contact your provider to purchase additional seats.", failureMessage);
|
|
}
|
|
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.TeamsAnnually)]
|
|
[BitAutoData(PlanType.TeamsMonthly)]
|
|
[BitAutoData(PlanType.TeamsStarter)]
|
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
[BitAutoData(PlanType.EnterpriseMonthly)]
|
|
public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = 0,
|
|
AdditionalServiceAccounts = 5,
|
|
AdditionalSeats = 2
|
|
};
|
|
|
|
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
|
|
Assert.Contains("You do not have any Secrets Manager seats!", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.Free)]
|
|
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = -1,
|
|
AdditionalServiceAccounts = 5
|
|
};
|
|
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
|
|
Assert.Contains("You can't subtract Secrets Manager seats!", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.Free)]
|
|
public void ValidateSecretsManagerPlan_ThrowsException_WhenPlanDoesNotAllowAdditionalServiceAccounts(
|
|
PlanType planType,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = 2,
|
|
AdditionalServiceAccounts = 5,
|
|
AdditionalSeats = 3
|
|
};
|
|
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
|
|
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.TeamsAnnually)]
|
|
[BitAutoData(PlanType.TeamsMonthly)]
|
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
[BitAutoData(PlanType.EnterpriseMonthly)]
|
|
public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = 4,
|
|
AdditionalServiceAccounts = 5,
|
|
AdditionalSeats = 3
|
|
};
|
|
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
|
|
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats.", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.TeamsAnnually)]
|
|
[BitAutoData(PlanType.TeamsMonthly)]
|
|
[BitAutoData(PlanType.TeamsStarter)]
|
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
[BitAutoData(PlanType.EnterpriseMonthly)]
|
|
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingServiceAccounts(
|
|
PlanType planType,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = 4,
|
|
AdditionalServiceAccounts = -5,
|
|
AdditionalSeats = 5
|
|
};
|
|
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
|
|
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.Free)]
|
|
public void ValidateSecretsManagerPlan_ThrowsException_WhenPlanDoesNotAllowAdditionalUsers(
|
|
PlanType planType,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = 2,
|
|
AdditionalServiceAccounts = 0,
|
|
AdditionalSeats = 5
|
|
};
|
|
var exception = Assert.Throws<BadRequestException>(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup));
|
|
Assert.Contains("Plan does not allow additional users.", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(PlanType.TeamsAnnually)]
|
|
[BitAutoData(PlanType.TeamsMonthly)]
|
|
[BitAutoData(PlanType.TeamsStarter)]
|
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
[BitAutoData(PlanType.EnterpriseMonthly)]
|
|
public void ValidateSecretsManagerPlan_ValidPlan_NoExceptionThrown(
|
|
PlanType planType,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var plan = StaticStore.GetPlan(planType);
|
|
var signup = new OrganizationUpgrade
|
|
{
|
|
UseSecretsManager = true,
|
|
AdditionalSmSeats = 2,
|
|
AdditionalServiceAccounts = 0,
|
|
AdditionalSeats = 4
|
|
};
|
|
|
|
sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup);
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Custom,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), BitAutoData]
|
|
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomPermission_WhenSavingUserHasCustomPermission_Passes(
|
|
CurrentContextOrganization organization,
|
|
OrganizationUserInvite organizationUserInvite,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var invitePermissions = new Permissions { AccessReports = true };
|
|
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
|
|
sutProvider.GetDependency<ICurrentContext>().AccessReports(organization.Id).Returns(true);
|
|
|
|
await sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organization.Id, organizationUserInvite.Type.Value, null, invitePermissions);
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Owner,
|
|
InvitorUserType = OrganizationUserType.Admin
|
|
), BitAutoData]
|
|
public async Task ValidateOrganizationUserUpdatePermissions_WithAdminAddingOwner_Throws(
|
|
Guid organizationId,
|
|
OrganizationUserInvite organizationUserInvite,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
|
|
|
|
Assert.Contains("only an owner can configure another owner's account.", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Admin,
|
|
InvitorUserType = OrganizationUserType.Owner
|
|
), BitAutoData]
|
|
public async Task ValidateOrganizationUserUpdatePermissions_WithoutManageUsersPermission_Throws(
|
|
Guid organizationId,
|
|
OrganizationUserInvite organizationUserInvite,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
|
|
|
|
Assert.Contains("your account does not have permission to manage users.", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Admin,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), BitAutoData]
|
|
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomAddingAdmin_Throws(
|
|
Guid organizationId,
|
|
OrganizationUserInvite organizationUserInvite,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organizationId).Returns(true);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, organizationUserInvite.Permissions));
|
|
|
|
Assert.Contains("custom users can not manage admins or owners.", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[OrganizationInviteCustomize(
|
|
InviteeUserType = OrganizationUserType.Custom,
|
|
InvitorUserType = OrganizationUserType.Custom
|
|
), BitAutoData]
|
|
public async Task ValidateOrganizationUserUpdatePermissions_WithCustomAddingUser_WithoutPermissions_Throws(
|
|
Guid organizationId,
|
|
OrganizationUserInvite organizationUserInvite,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
var invitePermissions = new Permissions { AccessReports = true };
|
|
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organizationId).Returns(true);
|
|
sutProvider.GetDependency<ICurrentContext>().AccessReports(organizationId).Returns(false);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.ValidateOrganizationUserUpdatePermissions(organizationId, organizationUserInvite.Type.Value, null, invitePermissions));
|
|
|
|
Assert.Contains("custom users can only grant the same custom permissions that they have.", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(OrganizationUserType.Owner)]
|
|
[BitAutoData(OrganizationUserType.Admin)]
|
|
[BitAutoData(OrganizationUserType.User)]
|
|
public async Task ValidateOrganizationCustomPermissionsEnabledAsync_WithNotCustomType_IsValid(
|
|
OrganizationUserType newType,
|
|
Guid organizationId,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
await sutProvider.Sut.ValidateOrganizationCustomPermissionsEnabledAsync(organizationId, newType);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task ValidateOrganizationCustomPermissionsEnabledAsync_NotExistingOrg_ThrowsNotFound(
|
|
Guid organizationId,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.ValidateOrganizationCustomPermissionsEnabledAsync(organizationId, OrganizationUserType.Custom));
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task ValidateOrganizationCustomPermissionsEnabledAsync_WithUseCustomPermissionsDisabled_ThrowsBadRequest(
|
|
Organization organization,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.UseCustomPermissions = false;
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetByIdAsync(organization.Id)
|
|
.Returns(organization);
|
|
|
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.ValidateOrganizationCustomPermissionsEnabledAsync(organization.Id, OrganizationUserType.Custom));
|
|
|
|
Assert.Contains("to enable custom permissions", exception.Message.ToLowerInvariant());
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task ValidateOrganizationCustomPermissionsEnabledAsync_WithUseCustomPermissionsEnabled_IsValid(
|
|
Organization organization,
|
|
SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
organization.UseCustomPermissions = true;
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetByIdAsync(organization.Id)
|
|
.Returns(organization);
|
|
|
|
await sutProvider.Sut.ValidateOrganizationCustomPermissionsEnabledAsync(organization.Id, OrganizationUserType.Custom);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateAsync_WhenValidOrganization_AndUpdateBillingIsTrue_UpdateStripeCustomerAndOrganization(Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Arrange
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
|
var eventService = sutProvider.GetDependency<IEventService>();
|
|
|
|
var requestOptionsReturned = new CustomerUpdateOptions
|
|
{
|
|
Email = organization.BillingEmail,
|
|
Description = organization.DisplayBusinessName(),
|
|
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
|
{
|
|
// This overwrites the existing custom fields for this organization
|
|
CustomFields =
|
|
[
|
|
new CustomerInvoiceSettingsCustomFieldOptions
|
|
{
|
|
Name = organization.SubscriberType(),
|
|
Value = organization.DisplayName()[..30]
|
|
}
|
|
]
|
|
},
|
|
};
|
|
organizationRepository
|
|
.GetByIdentifierAsync(organization.Identifier!)
|
|
.Returns(organization);
|
|
|
|
// Act
|
|
await sutProvider.Sut.UpdateAsync(organization, updateBilling: true);
|
|
|
|
// Assert
|
|
await organizationRepository
|
|
.Received(1)
|
|
.GetByIdentifierAsync(Arg.Is<string>(id => id == organization.Identifier));
|
|
await stripeAdapter
|
|
.Received(1)
|
|
.CustomerUpdateAsync(
|
|
Arg.Is<string>(id => id == organization.GatewayCustomerId),
|
|
Arg.Is<CustomerUpdateOptions>(options => options.Email == requestOptionsReturned.Email
|
|
&& options.Description == requestOptionsReturned.Description
|
|
&& options.InvoiceSettings.CustomFields.First().Name == requestOptionsReturned.InvoiceSettings.CustomFields.First().Name
|
|
&& options.InvoiceSettings.CustomFields.First().Value == requestOptionsReturned.InvoiceSettings.CustomFields.First().Value)); ;
|
|
await organizationRepository
|
|
.Received(1)
|
|
.ReplaceAsync(Arg.Is<Organization>(org => org == organization));
|
|
await applicationCacheService
|
|
.Received(1)
|
|
.UpsertOrganizationAbilityAsync(Arg.Is<Organization>(org => org == organization));
|
|
await eventService
|
|
.Received(1)
|
|
.LogOrganizationEventAsync(Arg.Is<Organization>(org => org == organization),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_Updated));
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateAsync_WhenValidOrganization_AndUpdateBillingIsFalse_UpdateOrganization(Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Arrange
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
|
var eventService = sutProvider.GetDependency<IEventService>();
|
|
|
|
organizationRepository
|
|
.GetByIdentifierAsync(organization.Identifier!)
|
|
.Returns(organization);
|
|
|
|
// Act
|
|
await sutProvider.Sut.UpdateAsync(organization, updateBilling: false);
|
|
|
|
// Assert
|
|
await organizationRepository
|
|
.Received(1)
|
|
.GetByIdentifierAsync(Arg.Is<string>(id => id == organization.Identifier));
|
|
await stripeAdapter
|
|
.DidNotReceiveWithAnyArgs()
|
|
.CustomerUpdateAsync(Arg.Any<string>(), Arg.Any<CustomerUpdateOptions>());
|
|
await organizationRepository
|
|
.Received(1)
|
|
.ReplaceAsync(Arg.Is<Organization>(org => org == organization));
|
|
await applicationCacheService
|
|
.Received(1)
|
|
.UpsertOrganizationAbilityAsync(Arg.Is<Organization>(org => org == organization));
|
|
await eventService
|
|
.Received(1)
|
|
.LogOrganizationEventAsync(Arg.Is<Organization>(org => org == organization),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_Updated));
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateAsync_WhenOrganizationHasNoId_ThrowsApplicationException(Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Arrange
|
|
organization.Id = Guid.Empty;
|
|
|
|
// Act/Assert
|
|
var exception = await Assert.ThrowsAnyAsync<ApplicationException>(() => sutProvider.Sut.UpdateAsync(organization));
|
|
Assert.Equal("Cannot create org this way. Call SignUpAsync.", exception.Message);
|
|
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateAsync_WhenIdentifierAlreadyExistsForADifferentOrganization_ThrowsBadRequestException(Organization organization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Arrange
|
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
|
var differentOrganization = new Organization { Id = Guid.NewGuid() };
|
|
|
|
organizationRepository
|
|
.GetByIdentifierAsync(organization.Identifier!)
|
|
.Returns(differentOrganization);
|
|
|
|
// Act/Assert
|
|
var exception = await Assert.ThrowsAnyAsync<BadRequestException>(() => sutProvider.Sut.UpdateAsync(organization));
|
|
Assert.Equal("Identifier already in use by another organization.", exception.Message);
|
|
|
|
await organizationRepository
|
|
.Received(1)
|
|
.GetByIdentifierAsync(Arg.Is<string>(id => id == organization.Identifier));
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(false, true, false, true)]
|
|
[BitAutoData(true, false, true, false)]
|
|
public async Task UpdateCollectionManagementSettingsAsync_WhenSettingsChanged_LogsSpecificEvents(
|
|
bool newLimitCollectionCreation,
|
|
bool newLimitCollectionDeletion,
|
|
bool newLimitItemDeletion,
|
|
bool newAllowAdminAccessToAllCollectionItems,
|
|
Organization existingOrganization, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Arrange
|
|
existingOrganization.LimitCollectionCreation = false;
|
|
existingOrganization.LimitCollectionDeletion = false;
|
|
existingOrganization.LimitItemDeletion = false;
|
|
existingOrganization.AllowAdminAccessToAllCollectionItems = false;
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetByIdAsync(existingOrganization.Id)
|
|
.Returns(existingOrganization);
|
|
|
|
var settings = new OrganizationCollectionManagementSettings
|
|
{
|
|
LimitCollectionCreation = newLimitCollectionCreation,
|
|
LimitCollectionDeletion = newLimitCollectionDeletion,
|
|
LimitItemDeletion = newLimitItemDeletion,
|
|
AllowAdminAccessToAllCollectionItems = newAllowAdminAccessToAllCollectionItems
|
|
};
|
|
|
|
// Act
|
|
await sutProvider.Sut.UpdateCollectionManagementSettingsAsync(existingOrganization.Id, settings);
|
|
|
|
// Assert
|
|
var eventService = sutProvider.GetDependency<IEventService>();
|
|
if (newLimitCollectionCreation)
|
|
{
|
|
await eventService.Received(1).LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled));
|
|
}
|
|
else
|
|
{
|
|
await eventService.DidNotReceive().LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled));
|
|
}
|
|
|
|
if (newLimitCollectionDeletion)
|
|
{
|
|
await eventService.Received(1).LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled));
|
|
}
|
|
else
|
|
{
|
|
await eventService.DidNotReceive().LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled));
|
|
}
|
|
|
|
if (newLimitItemDeletion)
|
|
{
|
|
await eventService.Received(1).LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitItemDeletionEnabled));
|
|
}
|
|
else
|
|
{
|
|
await eventService.DidNotReceive().LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_LimitItemDeletionEnabled));
|
|
}
|
|
|
|
if (newAllowAdminAccessToAllCollectionItems)
|
|
{
|
|
await eventService.Received(1).LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled));
|
|
}
|
|
else
|
|
{
|
|
await eventService.DidNotReceive().LogOrganizationEventAsync(
|
|
Arg.Is<Organization>(org => org.Id == existingOrganization.Id),
|
|
Arg.Is<EventType>(e => e == EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled));
|
|
}
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task UpdateCollectionManagementSettingsAsync_WhenOrganizationNotFound_ThrowsNotFoundException(
|
|
Guid organizationId, OrganizationCollectionManagementSettings settings, SutProvider<OrganizationService> sutProvider)
|
|
{
|
|
// Arrange
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetByIdAsync(organizationId)
|
|
.Returns((Organization)null);
|
|
|
|
// Act/Assert
|
|
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateCollectionManagementSettingsAsync(organizationId, settings));
|
|
|
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
|
.Received(1)
|
|
.GetByIdAsync(organizationId);
|
|
}
|
|
|
|
// Must set real guids in order for dictionary of guids to not throw aggregate exceptions
|
|
private void SetupOrgUserRepositoryCreateManyAsyncMock(IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
organizationUserRepository.CreateManyAsync(Arg.Any<IEnumerable<OrganizationUser>>()).Returns(
|
|
info =>
|
|
{
|
|
var orgUsers = info.Arg<IEnumerable<OrganizationUser>>();
|
|
foreach (var orgUser in orgUsers)
|
|
{
|
|
orgUser.Id = Guid.NewGuid();
|
|
}
|
|
|
|
return Task.FromResult<ICollection<Guid>>(orgUsers.Select(u => u.Id).ToList());
|
|
}
|
|
);
|
|
|
|
organizationUserRepository.CreateAsync(Arg.Any<OrganizationUser>(), Arg.Any<IEnumerable<CollectionAccessSelection>>()).Returns(
|
|
info =>
|
|
{
|
|
var orgUser = info.Arg<OrganizationUser>();
|
|
orgUser.Id = Guid.NewGuid();
|
|
return Task.FromResult<Guid>(orgUser.Id);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Must set real guids in order for dictionary of guids to not throw aggregate exceptions
|
|
private void SetupOrgUserRepositoryCreateAsyncMock(IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
organizationUserRepository.CreateAsync(Arg.Any<OrganizationUser>(),
|
|
Arg.Any<IEnumerable<CollectionAccessSelection>>()).Returns(
|
|
info =>
|
|
{
|
|
var orgUser = info.Arg<OrganizationUser>();
|
|
orgUser.Id = Guid.NewGuid();
|
|
return Task.FromResult<Guid>(orgUser.Id);
|
|
}
|
|
);
|
|
}
|
|
}
|