using Bit.Admin.AdminConsole.Controllers; using Bit.Admin.AdminConsole.Models; using Bit.Admin.Enums; using Bit.Admin.Services; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Providers.Services; using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewFeatures; using NSubstitute; using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers; namespace Admin.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(OrganizationsController))] [SutProviderCustomize] public class OrganizationsControllerTests { #region Edit (POST) [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_NonBillableProvider_NoOp( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false }; var organization = new Organization { Id = organizationId }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Created }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .ScaleSeats(Arg.Any(), Arg.Any(), Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_UnmanagedOrganization_NoOp( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false }; var organization = new Organization { Id = organizationId, Status = OrganizationStatusType.Created }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .ScaleSeats(Arg.Any(), Arg.Any(), Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_NonCBPlanType_NoOp( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, Seats = 10, PlanType = PlanType.FamiliesAnnually }; var organization = new Organization { Id = organizationId, Status = OrganizationStatusType.Managed, Seats = 10 }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .ScaleSeats(Arg.Any(), Arg.Any(), Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_NoUpdateRequired_NoOp( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, Seats = 10, PlanType = PlanType.EnterpriseMonthly }; var organization = new Organization { Id = organizationId, Status = OrganizationStatusType.Managed, Seats = 10, PlanType = PlanType.EnterpriseMonthly }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .ScaleSeats(Arg.Any(), Arg.Any(), Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_PlanTypesUpdate_ScalesSeatsCorrectly( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, Seats = 10, PlanType = PlanType.EnterpriseMonthly }; var organization = new Organization { Id = organizationId, Status = OrganizationStatusType.Managed, Seats = 10, PlanType = PlanType.TeamsMonthly }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert var providerBillingService = sutProvider.GetDependency(); await providerBillingService.Received(1).ScaleSeats(provider, organization.PlanType, -organization.Seats.Value); await providerBillingService.Received(1).ScaleSeats(provider, update.PlanType!.Value, organization.Seats.Value); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_SeatsUpdate_ScalesSeatsCorrectly( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, Seats = 15, PlanType = PlanType.EnterpriseMonthly }; var organization = new Organization { Id = organizationId, Status = OrganizationStatusType.Managed, Seats = 10, PlanType = PlanType.EnterpriseMonthly }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert var providerBillingService = sutProvider.GetDependency(); await providerBillingService.Received(1).ScaleSeats(provider, organization.PlanType, update.Seats!.Value - organization.Seats.Value); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_ProviderSeatScaling_FullUpdate_ScalesSeatsCorrectly( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, Seats = 15, PlanType = PlanType.EnterpriseMonthly }; var organization = new Organization { Id = organizationId, Status = OrganizationStatusType.Managed, Seats = 10, PlanType = PlanType.TeamsMonthly }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency().GetByOrganizationIdAsync(organizationId).Returns(provider); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert var providerBillingService = sutProvider.GetDependency(); await providerBillingService.Received(1).ScaleSeats(provider, organization.PlanType, -organization.Seats.Value); await providerBillingService.Received(1).ScaleSeats(provider, update.PlanType!.Value, update.Seats!.Value - organization.Seats.Value + organization.Seats.Value); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_UseAutomaticUserConfirmation_FullUpdate_SavesFeatureCorrectly( Organization organization, SutProvider sutProvider) { // Arrange var update = new OrganizationEditModel { PlanType = PlanType.TeamsMonthly, UseAutomaticUserConfirmation = true }; organization.UseAutomaticUserConfirmation = false; sutProvider.GetDependency() .UserHasPermission(Permission.Org_Plan_Edit) .Returns(true); var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organization.Id); sutProvider.GetDependency() .IsOrganizationCompliantAsync(Arg.Any()) .Returns(Valid(request)); // Act _ = await sutProvider.Sut.Edit(organization.Id, update); // Assert await organizationRepository.Received(1).ReplaceAsync(Arg.Is(o => o.Id == organization.Id && o.UseAutomaticUserConfirmation == true)); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_EnableUseAutomaticUserConfirmation_ValidationFails_RedirectsWithError( Organization organization, SutProvider sutProvider) { // Arrange var update = new OrganizationEditModel { PlanType = PlanType.TeamsMonthly, UseAutomaticUserConfirmation = true }; organization.UseAutomaticUserConfirmation = false; sutProvider.GetDependency() .UserHasPermission(Permission.Org_Plan_Edit) .Returns(true); var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organization.Id); sutProvider.GetDependency() .IsOrganizationCompliantAsync(Arg.Any()) .Returns(Invalid(request, new UserNotCompliantWithSingleOrganization())); sutProvider.Sut.TempData = new TempDataDictionary(new DefaultHttpContext(), Substitute.For()); // Act var result = await sutProvider.Sut.Edit(organization.Id, update); // Assert var redirectResult = Assert.IsType(result); Assert.Equal("Edit", redirectResult.ActionName); Assert.Equal(organization.Id, redirectResult.RouteValues!["id"]); await organizationRepository.DidNotReceive().ReplaceAsync(Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_EnableUseAutomaticUserConfirmation_ProviderValidationFails_RedirectsWithError( Organization organization, SutProvider sutProvider) { // Arrange var update = new OrganizationEditModel { PlanType = PlanType.TeamsMonthly, UseAutomaticUserConfirmation = true }; organization.UseAutomaticUserConfirmation = false; sutProvider.GetDependency() .UserHasPermission(Permission.Org_Plan_Edit) .Returns(true); var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); var request = new AutomaticUserConfirmationOrganizationPolicyComplianceValidatorRequest(organization.Id); sutProvider.GetDependency() .IsOrganizationCompliantAsync(Arg.Any()) .Returns(Invalid(request, new ProviderExistsInOrganization())); sutProvider.Sut.TempData = new TempDataDictionary(new DefaultHttpContext(), Substitute.For()); // Act var result = await sutProvider.Sut.Edit(organization.Id, update); // Assert var redirectResult = Assert.IsType(result); Assert.Equal("Edit", redirectResult.ActionName); await organizationRepository.DidNotReceive().ReplaceAsync(Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_UseAutomaticUserConfirmation_NotChanged_DoesNotCallValidator( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, UseAutomaticUserConfirmation = false }; var organization = new Organization { Id = organizationId, UseAutomaticUserConfirmation = false }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert await sutProvider.GetDependency() .DidNotReceive() .IsOrganizationCompliantAsync(Arg.Any()); } [BitAutoData] [SutProviderCustomize] [Theory] public async Task Edit_UseAutomaticUserConfirmation_AlreadyEnabled_DoesNotCallValidator( SutProvider sutProvider) { // Arrange var organizationId = new Guid(); var update = new OrganizationEditModel { UseSecretsManager = false, UseAutomaticUserConfirmation = true }; var organization = new Organization { Id = organizationId, UseAutomaticUserConfirmation = true }; sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(organization); // Act _ = await sutProvider.Sut.Edit(organizationId, update); // Assert await sutProvider.GetDependency() .DidNotReceive() .IsOrganizationCompliantAsync(Arg.Any()); } #endregion }