From c5b52256428bcb391bd2619ff5f9bb000db29e3d Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Tue, 22 Aug 2023 09:55:39 +1000 Subject: [PATCH] [AC-1568] Refactor UpdateSecretsManagerSubscriptionCommandTests to use new ctor (#3210) --- .../AutoFixture/OrganizationFixtures.cs | 28 +- .../SecretsManagerSubscriptionUpdateTests.cs | 31 + ...eSecretsManagerSubscriptionCommandTests.cs | 934 ++++++++---------- 3 files changed, 459 insertions(+), 534 deletions(-) create mode 100644 test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs diff --git a/test/Core.Test/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AutoFixture/OrganizationFixtures.cs index ee0c13dd2..8e6cfd060 100644 --- a/test/Core.Test/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AutoFixture/OrganizationFixtures.cs @@ -20,11 +20,11 @@ public class OrganizationCustomization : ICustomization public void Customize(IFixture fixture) { var organizationId = Guid.NewGuid(); - var maxConnections = (short)new Random().Next(10, short.MaxValue); + var maxCollections = (short)new Random().Next(10, short.MaxValue); fixture.Customize(composer => composer .With(o => o.Id, organizationId) - .With(o => o.MaxCollections, maxConnections) + .With(o => o.MaxCollections, maxCollections) .With(o => o.UseGroups, UseGroups)); fixture.Customize(composer => @@ -127,6 +127,24 @@ internal class OrganizationInvite : ICustomization } } +public class SecretsManagerOrganizationCustomization : ICustomization +{ + public void Customize(IFixture fixture) + { + var organizationId = Guid.NewGuid(); + var planType = PlanType.EnterpriseAnnually; + + fixture.Customize(composer => composer + .With(o => o.Id, organizationId) + .With(o => o.UseSecretsManager, true) + .With(o => o.PlanType, planType) + .With(o => o.Plan, StaticStore.GetPasswordManagerPlan(planType).Name) + .With(o => o.MaxAutoscaleSmSeats, (int?)null) + .With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null) + ); + } +} + internal class OrganizationCustomizeAttribute : BitCustomizeAttribute { public bool UseGroups { get; set; } @@ -162,3 +180,9 @@ internal class OrganizationInviteCustomizeAttribute : BitCustomizeAttribute PermissionsBlob = PermissionsBlob, }; } + +internal class SecretsManagerOrganizationCustomizeAttribute : BitCustomizeAttribute +{ + public override ICustomization GetCustomization() => + new SecretsManagerOrganizationCustomization(); +} diff --git a/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs new file mode 100644 index 000000000..0adef6b47 --- /dev/null +++ b/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs @@ -0,0 +1,31 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Business; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Models.Business; + +[SecretsManagerOrganizationCustomize] +public class SecretsManagerSubscriptionUpdateTests +{ + [Theory] + [BitAutoData(PlanType.Custom)] + [BitAutoData(PlanType.FamiliesAnnually)] + [BitAutoData(PlanType.FamiliesAnnually2019)] + [BitAutoData(PlanType.EnterpriseMonthly2019)] + [BitAutoData(PlanType.EnterpriseAnnually2019)] + [BitAutoData(PlanType.TeamsMonthly2019)] + [BitAutoData(PlanType.TeamsAnnually2019)] + public async Task UpdateSubscriptionAsync_WithNonSecretsManagerPlanType_ThrowsBadRequestException( + PlanType planType, + Organization organization) + { + organization.PlanType = planType; + + var exception = Assert.Throws(() => new SecretsManagerSubscriptionUpdate(organization, false)); + Assert.Contains("Invalid Secrets Manager plan", exception.Message, StringComparison.InvariantCultureIgnoreCase); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs index 329ad60af..f40011561 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs @@ -8,6 +8,7 @@ using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -17,314 +18,58 @@ using Xunit; namespace Bit.Core.Test.OrganizationFeatures.OrganizationSubscriptionUpdate; [SutProviderCustomize] +[SecretsManagerOrganizationCustomize] public class UpdateSecretsManagerSubscriptionCommandTests { - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_NoOrganization_Throws( - SecretsManagerSubscriptionUpdate secretsManagerSubscriptionUpdate, - SutProvider sutProvider) - { - secretsManagerSubscriptionUpdate.Organization = null; - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateSubscriptionAsync(secretsManagerSubscriptionUpdate)); - - Assert.Contains("Organization is not found", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_NoSecretsManagerAccess_ThrowsException( - SutProvider sutProvider) - { - var organization = new Organization - { - SmSeats = 10, - SmServiceAccounts = 5, - UseSecretsManager = false, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 10 - }; - - var secretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, seatAdjustment: 0, maxAutoscaleSeats: null, serviceAccountAdjustment: 0, maxAutoscaleServiceAccounts: null); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateSubscriptionAsync(secretsManagerSubscriptionUpdate)); - - Assert.Contains("Organization has no access to Secrets Manager.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_SeatsAdjustmentGreaterThanMaxAutoscaleSeats_ThrowsException( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 10, - UseSecretsManager = true, - SmServiceAccounts = 5, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 10, - PlanType = PlanType.EnterpriseAnnually, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 15, maxAutoscaleSeats: 10, serviceAccountAdjustment: 0, maxAutoscaleServiceAccounts: null); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Cannot set max seat autoscaling below seat count.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_ServiceAccountsGreaterThanMaxAutoscaleSeats_ThrowsException( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 10, - UseSecretsManager = true, - SmServiceAccounts = 5, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 10, - PlanType = PlanType.EnterpriseAnnually, - GatewayCustomerId = "1", - GatewaySubscriptionId = "9" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: 15, serviceAccountAdjustment: 11, maxAutoscaleServiceAccounts: 10); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Cannot set max service accounts autoscaling below service account amount", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_NullGatewayCustomerId_ThrowsException( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 10, - SmServiceAccounts = 5, - UseSecretsManager = true, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 15, - PlanType = PlanType.EnterpriseAnnually - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: 15, serviceAccountAdjustment: 1, maxAutoscaleServiceAccounts: 15); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("No payment method found.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_NullGatewaySubscriptionId_ThrowsException( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 10, - UseSecretsManager = true, - SmServiceAccounts = 5, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 15, - PlanType = PlanType.EnterpriseAnnually, - GatewayCustomerId = "1" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: 15, serviceAccountAdjustment: 1, maxAutoscaleServiceAccounts: 15); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("No subscription found.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_OrgWithNullSmSeatOnSeatsAdjustment_ThrowsException( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = null, - UseSecretsManager = true, - SmServiceAccounts = 5, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 15, - PlanType = PlanType.EnterpriseAnnually, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: 15, serviceAccountAdjustment: 1, maxAutoscaleServiceAccounts: 15); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - - Assert.Contains("Organization has no Secrets Manager seat limit, no need to adjust seats", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData(PlanType.Custom)] - [BitAutoData(PlanType.FamiliesAnnually)] - [BitAutoData(PlanType.FamiliesAnnually2019)] - [BitAutoData(PlanType.EnterpriseMonthly2019)] - [BitAutoData(PlanType.EnterpriseAnnually2019)] - [BitAutoData(PlanType.TeamsMonthly2019)] - [BitAutoData(PlanType.TeamsAnnually2019)] - public async Task UpdateSubscriptionAsync_WithNonSecretsManagerPlanType_ThrowsBadRequestException( - PlanType planType, - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 10, - UseSecretsManager = true, - SmServiceAccounts = 200, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 300, - PlanType = PlanType.EnterpriseAnnually, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: 15, serviceAccountAdjustment: 1, maxAutoscaleServiceAccounts: 300); - organization.PlanType = planType; - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Existing plan not found", exception.Message, StringComparison.InvariantCultureIgnoreCase); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData(PlanType.Free)] - public async Task UpdateSubscriptionAsync_WithHasAdditionalSeatsOptionFalse_ThrowsBadRequestException( - PlanType planType, - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 2, - SmServiceAccounts = 3, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: null, serviceAccountAdjustment: 0, maxAutoscaleServiceAccounts: null); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("You have reached the maximum number of Secrets Manager seats (2) for this plan", - exception.Message, StringComparison.InvariantCultureIgnoreCase); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData(PlanType.Free)] - public async Task UpdateSubscriptionAsync_WithHasAdditionalServiceAccountOptionFalse_ThrowsBadRequestException( - PlanType planType, - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 2, - SmServiceAccounts = 3, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 0, maxAutoscaleSeats: null, serviceAccountAdjustment: 1, maxAutoscaleServiceAccounts: null); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("You have reached the maximum number of service accounts (3) for this plan", - exception.Message, StringComparison.InvariantCultureIgnoreCase); - await VerifyDependencyNotCalledAsync(sutProvider); - } - [Theory] [BitAutoData(PlanType.EnterpriseAnnually)] [BitAutoData(PlanType.EnterpriseMonthly)] [BitAutoData(PlanType.TeamsMonthly)] [BitAutoData(PlanType.TeamsAnnually)] - public async Task UpdateSubscriptionAsync_ValidInput_Passes( + public async Task UpdateSubscriptionAsync_UpdateEverything_ValidInput_Passes( PlanType planType, - Guid organizationId, + Organization organization, SutProvider sutProvider) { - const int organizationServiceAccounts = 200; - const int seatAdjustment = 5; - const int maxAutoscaleSeats = 15; - const int serviceAccountAdjustment = 100; - const int maxAutoScaleServiceAccounts = 300; + organization.PlanType = planType; + organization.SmSeats = 10; + organization.MaxAutoscaleSmSeats = 20; + organization.SmServiceAccounts = 200; + organization.MaxAutoscaleSmServiceAccounts = 350; - var organization = new Organization + var updateSmSeats = 15; + var updateSmServiceAccounts = 300; + var updateMaxAutoscaleSmSeats = 16; + var updateMaxAutoscaleSmServiceAccounts = 301; + + var update = new SecretsManagerSubscriptionUpdate(organization, false) { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 10, - MaxAutoscaleSmSeats = 20, - SmServiceAccounts = organizationServiceAccounts, - MaxAutoscaleSmServiceAccounts = 350, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" + SmSeats = updateSmSeats, + SmServiceAccounts = updateSmServiceAccounts, + MaxAutoscaleSmSeats = updateMaxAutoscaleSmSeats, + MaxAutoscaleSmServiceAccounts = updateMaxAutoscaleSmServiceAccounts }; + await sutProvider.Sut.UpdateSubscriptionAsync(update); + var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType); - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, - seatAdjustment: seatAdjustment, maxAutoscaleSeats: maxAutoscaleSeats, - serviceAccountAdjustment: serviceAccountAdjustment, maxAutoscaleServiceAccounts: maxAutoScaleServiceAccounts); - - await sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate); - await sutProvider.GetDependency().Received(1) - .AdjustSeatsAsync(organization, plan, organizationUpdate.SmSeatsExcludingBase); + .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); await sutProvider.GetDependency().Received(1) - .AdjustServiceAccountsAsync(organization, plan, organizationUpdate.SmServiceAccountsExcludingBase); + .AdjustServiceAccountsAsync(organization, plan, update.SmServiceAccountsExcludingBase); // TODO: call ReferenceEventService - see AC-1481 AssertUpdatedOrganization(() => Arg.Is(org => - org.Id == organizationId - && org.SmSeats == organizationUpdate.SmSeats - && org.MaxAutoscaleSmSeats == organizationUpdate.MaxAutoscaleSmSeats - && org.SmServiceAccounts == (organizationServiceAccounts + serviceAccountAdjustment) - && org.MaxAutoscaleSmServiceAccounts == organizationUpdate.MaxAutoscaleSmServiceAccounts), sutProvider); + org.Id == organization.Id && + org.SmSeats == updateSmSeats && + org.MaxAutoscaleSmSeats == updateMaxAutoscaleSmSeats && + org.SmServiceAccounts == updateSmServiceAccounts && + org.MaxAutoscaleSmServiceAccounts == updateMaxAutoscaleSmServiceAccounts), + sutProvider); - await sutProvider.GetDependency().Received(1).SendSecretsManagerMaxSeatLimitReachedEmailAsync(organization, organization.MaxAutoscaleSmSeats.Value, Arg.Any>()); - await sutProvider.GetDependency().Received(1).SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(organization, organization.MaxAutoscaleSmServiceAccounts.Value, Arg.Any>()); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SendSecretsManagerMaxSeatLimitReachedEmailAsync(default, default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(default, default, default); } [Theory] @@ -334,246 +79,115 @@ public class UpdateSecretsManagerSubscriptionCommandTests [BitAutoData(PlanType.TeamsAnnually)] public async Task UpdateSubscriptionAsync_ValidInput_WithNullMaxAutoscale_Passes( PlanType planType, - Guid organizationId, + Organization organization, SutProvider sutProvider) { - int organizationServiceAccounts = 200; - int seatAdjustment = 5; - int? maxAutoscaleSeats = null; - int serviceAccountAdjustment = 100; - int? maxAutoScaleServiceAccounts = null; + organization.PlanType = planType; - var organization = new Organization + const int updateSmSeats = 15; + const int updateSmServiceAccounts = 450; + var update = new SecretsManagerSubscriptionUpdate(organization, false) { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 10, - MaxAutoscaleSmSeats = 20, - SmServiceAccounts = organizationServiceAccounts, - MaxAutoscaleSmServiceAccounts = 350, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" + SmSeats = updateSmSeats, + MaxAutoscaleSmSeats = null, + SmServiceAccounts = updateSmServiceAccounts, + MaxAutoscaleSmServiceAccounts = null }; + await sutProvider.Sut.UpdateSubscriptionAsync(update); + var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType); - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, - seatAdjustment: seatAdjustment, maxAutoscaleSeats: maxAutoscaleSeats, - serviceAccountAdjustment: serviceAccountAdjustment, maxAutoscaleServiceAccounts: maxAutoScaleServiceAccounts); - - await sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate); - await sutProvider.GetDependency().Received(1) - .AdjustSeatsAsync(organization, plan, organizationUpdate.SmSeatsExcludingBase); + .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); await sutProvider.GetDependency().Received(1) - .AdjustServiceAccountsAsync(organization, plan, organizationUpdate.SmServiceAccountsExcludingBase); + .AdjustServiceAccountsAsync(organization, plan, update.SmServiceAccountsExcludingBase); // TODO: call ReferenceEventService - see AC-1481 AssertUpdatedOrganization(() => Arg.Is(org => - org.Id == organizationId - && org.SmSeats == organizationUpdate.SmSeats - && org.MaxAutoscaleSmSeats == organizationUpdate.MaxAutoscaleSmSeats - && org.SmServiceAccounts == (organizationServiceAccounts + serviceAccountAdjustment) - && org.MaxAutoscaleSmServiceAccounts == organizationUpdate.MaxAutoscaleSmServiceAccounts), sutProvider); + org.Id == organization.Id && + org.SmSeats == updateSmSeats && + org.MaxAutoscaleSmSeats == null && + org.SmServiceAccounts == updateSmServiceAccounts && + org.MaxAutoscaleSmServiceAccounts == null), + sutProvider); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SendSecretsManagerMaxSeatLimitReachedEmailAsync(default, default, default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(default, default, default); } [Theory] - [BitAutoData] - public async Task UpdateSubscriptionAsync_ThrowsBadRequestException_WhenMaxAutoscaleSeatsBelowSeatCount( - Guid organizationId, + [BitAutoData(false, "Cannot update subscription on a self-hosted instance.")] + [BitAutoData(true, "Cannot autoscale on a self-hosted instance.")] + public async Task UpdatingSubscription_WhenSelfHosted_ThrowsBadRequestException( + bool autoscaling, + string expectedError, + Organization organization, SutProvider sutProvider) { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 5, - SmServiceAccounts = 200, - MaxAutoscaleSmSeats = 4, - MaxAutoscaleSmServiceAccounts = 300, - PlanType = PlanType.EnterpriseAnnually, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - var update = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 1, maxAutoscaleSeats: 4, serviceAccountAdjustment: 5, maxAutoscaleServiceAccounts: 300); + var update = new SecretsManagerSubscriptionUpdate(organization, autoscaling); + update.AdjustSeats(2); + + sutProvider.GetDependency().SelfHosted.Returns(true); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains("Cannot set max seat autoscaling below seat count.", exception.Message); + Assert.Contains(expectedError, exception.Message); await VerifyDependencyNotCalledAsync(sutProvider); } [Theory] [BitAutoData] - public async Task UpdateSubscriptionAsync_ThrowsBadRequestException_WhenOccupiedSeatsExceedNewSeatTotal( - Guid organizationId, - SutProvider sutProvider) + public async Task UpdateSubscriptionAsync_NoSecretsManagerAccess_ThrowsException( + SutProvider sutProvider, + Organization organization) { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 10, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2", - PlanType = PlanType.EnterpriseAnnually - }; - var update = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: -3, maxAutoscaleSeats: 7, serviceAccountAdjustment: 5, maxAutoscaleServiceAccounts: 300); + organization.UseSecretsManager = false; + var update = new SecretsManagerSubscriptionUpdate(organization, false); - sutProvider.GetDependency().GetOccupiedSmSeatCountByOrganizationIdAsync(organizationId).Returns(8); + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateSubscriptionAsync(update)); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains("Your organization currently has 8 Secrets Manager seats. Your plan only allows 7 Secrets Manager seats. Remove some Secrets Manager users", exception.Message); + Assert.Contains("Organization has no access to Secrets Manager.", exception.Message); await VerifyDependencyNotCalledAsync(sutProvider); } - [Theory] - [BitAutoData] - public async Task AdjustServiceAccountsAsync_ThrowsBadRequestException_WhenSmServiceAccountsIsNull( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 10, - UseSecretsManager = true, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2", - SmServiceAccounts = null, - PlanType = PlanType.EnterpriseAnnually - }; - var update = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 10, maxAutoscaleSeats: 21, serviceAccountAdjustment: 1, maxAutoscaleServiceAccounts: 250); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains("Organization has no Service Accounts limit, no need to adjust Service Accounts", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData] - public async Task AutoscaleSeatsAsync_ThrowsBadRequestException_WhenMaxAutoscaleSeatsExceedPlanMaxUsers( - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - SmSeats = 3, - UseSecretsManager = true, - SmServiceAccounts = 100, - PlanType = PlanType.Free, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2", - }; - - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 0, maxAutoscaleSeats: 15, serviceAccountAdjustment: 0, maxAutoscaleServiceAccounts: 200); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Your plan has a Secrets Manager seat limit of 2, but you have specified a max autoscale count of 15.Reduce your max autoscale count.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData(PlanType.Free)] - public async Task AutoscaleSeatsAsync_ThrowsBadRequestException_WhenPlanDoesNotAllowSeatAutoscale( - PlanType planType, - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 1, - SmServiceAccounts = 200, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 350, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 0, maxAutoscaleSeats: 1, serviceAccountAdjustment: 0, maxAutoscaleServiceAccounts: 300); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Your plan does not allow Secrets Manager seat autoscaling", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - - } - - [Theory] - [BitAutoData(PlanType.Free)] - public async Task UpdateServiceAccountAutoscaling_ThrowsBadRequestException_WhenPlanDoesNotAllowServiceAccountAutoscale( - PlanType planType, - Guid organizationId, - SutProvider sutProvider) - { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 10, - SmServiceAccounts = 200, - MaxAutoscaleSmSeats = 20, - MaxAutoscaleSmServiceAccounts = 350, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; - - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 0, maxAutoscaleSeats: null, serviceAccountAdjustment: 0, maxAutoscaleServiceAccounts: 300); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Your plan does not allow Service Accounts autoscaling.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - - } - [Theory] [BitAutoData(PlanType.EnterpriseAnnually)] [BitAutoData(PlanType.EnterpriseMonthly)] [BitAutoData(PlanType.TeamsMonthly)] [BitAutoData(PlanType.TeamsAnnually)] - public async Task UpdateServiceAccountAutoscaling_WhenCurrentServiceAccountsIsGreaterThanNew_ThrowsBadRequestException( + public async Task UpdateSubscriptionAsync_PaidPlan_NullGatewayCustomerId_ThrowsException( PlanType planType, - Guid organizationId, + Organization organization, SutProvider sutProvider) { - var organization = new Organization - { - Id = organizationId, - UseSecretsManager = true, - SmSeats = 10, - MaxAutoscaleSmSeats = 20, - SmServiceAccounts = 301, - MaxAutoscaleSmServiceAccounts = 350, - PlanType = planType, - GatewayCustomerId = "1", - GatewaySubscriptionId = "2" - }; + organization.PlanType = planType; + organization.GatewayCustomerId = null; + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustSeats(1); - var organizationUpdate = new SecretsManagerSubscriptionUpdate( - organization, seatAdjustment: 5, maxAutoscaleSeats: 15, serviceAccountAdjustment: -100, maxAutoscaleServiceAccounts: 300); - var currentServiceAccounts = 301; + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("No payment method found.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } - sutProvider.GetDependency() - .GetServiceAccountCountByOrganizationIdAsync(organization.Id) - .Returns(currentServiceAccounts); + [Theory] + [BitAutoData(PlanType.EnterpriseAnnually)] + [BitAutoData(PlanType.EnterpriseMonthly)] + [BitAutoData(PlanType.TeamsMonthly)] + [BitAutoData(PlanType.TeamsAnnually)] + public async Task UpdateSubscriptionAsync_PaidPlan_NullGatewaySubscriptionId_ThrowsException( + PlanType planType, + Organization organization, + SutProvider sutProvider) + { + organization.PlanType = planType; + organization.GatewaySubscriptionId = null; + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustSeats(1); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(organizationUpdate)); - Assert.Contains("Your organization currently has 301 Service Accounts. Your plan only allows 201 Service Accounts. Remove some Service Accounts", exception.Message); - await sutProvider.GetDependency().Received(1).GetServiceAccountCountByOrganizationIdAsync(organization.Id); + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("No subscription found.", exception.Message); await VerifyDependencyNotCalledAsync(sutProvider); } @@ -609,7 +223,10 @@ public class UpdateSecretsManagerSubscriptionCommandTests var expectedSmServiceAccounts = organizationServiceAccounts + smServiceAccountsAdjustment; var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault(); - await sutProvider.Sut.AdjustServiceAccountsAsync(organization, smServiceAccountsAdjustment); + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustServiceAccounts(10); + + await sutProvider.Sut.UpdateSubscriptionAsync(update); await sutProvider.GetDependency().Received(1).AdjustServiceAccountsAsync( Arg.Is(o => o.Id == organizationId), @@ -625,52 +242,78 @@ public class UpdateSecretsManagerSubscriptionCommandTests } [Theory] - [BitAutoData(PlanType.EnterpriseAnnually)] - public async Task ServiceAccountAutoscaling_MaxLimitReached_ThrowsBadRequestException( + [BitAutoData] + public async Task UpdateSubscriptionAsync_UpdateSeatsToAutoscaleLimit_EmailsOwners( + Organization organization, + SutProvider sutProvider) + { + organization.SmSeats = 9; + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmSeats = 10, + MaxAutoscaleSmSeats = 10 + }; + + await sutProvider.Sut.UpdateSubscriptionAsync(update); + + await sutProvider.GetDependency().Received(1).SendSecretsManagerMaxSeatLimitReachedEmailAsync( + organization, organization.MaxAutoscaleSmSeats.Value, Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_OrgWithNullSmSeatOnSeatsAdjustment_ThrowsException( + Organization organization, + SutProvider sutProvider) + { + organization.SmSeats = null; + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustSeats(1); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateSubscriptionAsync(update)); + + Assert.Contains("Organization has no Secrets Manager seat limit, no need to adjust seats", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_SmSeatAutoscaling_Subtracting_ThrowsBadRequestException( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, true); + update.AdjustSeats(-2); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Cannot use autoscaling to subtract seats.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData(PlanType.Free)] + public async Task UpdateSubscriptionAsync_WithHasAdditionalSeatsOptionFalse_ThrowsBadRequestException( PlanType planType, Organization organization, SutProvider sutProvider) { organization.PlanType = planType; - organization.UseSecretsManager = true; - organization.SmServiceAccounts = 9; - organization.MaxAutoscaleSmServiceAccounts = 10; - - var update = new SecretsManagerSubscriptionUpdate(organization, true); - update.AdjustServiceAccounts(2); + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustSeats(1); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains("Secrets Manager service account limit has been reached.", exception.Message); + Assert.Contains("You have reached the maximum number of Secrets Manager seats (2) for this plan", + exception.Message, StringComparison.InvariantCultureIgnoreCase); await VerifyDependencyNotCalledAsync(sutProvider); } [Theory] - [BitAutoData(PlanType.EnterpriseAnnually)] - public async Task ServiceAccountAutoscaling_Subtracting_ThrowsBadRequestException( - PlanType planType, - Organization organization, - SutProvider sutProvider) - { - organization.PlanType = planType; - organization.UseSecretsManager = true; - - var update = new SecretsManagerSubscriptionUpdate(organization, true); - update.AdjustServiceAccounts(-2); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains("Cannot use autoscaling to subtract service accounts.", exception.Message); - await VerifyDependencyNotCalledAsync(sutProvider); - } - - [Theory] - [BitAutoData(PlanType.EnterpriseAnnually)] + [BitAutoData] public async Task SmSeatAutoscaling_MaxLimitReached_ThrowsBadRequestException( - PlanType planType, Organization organization, SutProvider sutProvider) { - organization.PlanType = planType; - organization.UseSecretsManager = true; organization.SmSeats = 9; organization.MaxAutoscaleSmSeats = 10; @@ -683,42 +326,269 @@ public class UpdateSecretsManagerSubscriptionCommandTests } [Theory] - [BitAutoData(PlanType.EnterpriseAnnually)] - public async Task SmSeatAutoscaling_Subtracting_ThrowsBadRequestException( + [BitAutoData] + public async Task UpdateSubscriptionAsync_SeatsAdjustmentGreaterThanMaxAutoscaleSeats_ThrowsException( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmSeats = 15, + MaxAutoscaleSmSeats = 10 + }; + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Cannot set max seat autoscaling below seat count.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_ThrowsBadRequestException_WhenSmSeatsLessThanOne( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmSeats = 0, + }; + + sutProvider.GetDependency().GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id).Returns(8); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("You must have at least 1 Secrets Manager seat.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_ThrowsBadRequestException_WhenOccupiedSeatsExceedNewSeatTotal( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmSeats = 7, + }; + + sutProvider.GetDependency().GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id).Returns(8); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Your organization currently has 8 Secrets Manager seats. Your plan only allows 7 Secrets Manager seats. Remove some Secrets Manager users", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_UpdateServiceAccountsToAutoscaleLimit_EmailsOwners( + Organization organization, + SutProvider sutProvider) + { + organization.SmServiceAccounts = 250; + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmServiceAccounts = 300, + MaxAutoscaleSmServiceAccounts = 300 + }; + + await sutProvider.Sut.UpdateSubscriptionAsync(update); + + await sutProvider.GetDependency().Received(1).SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync( + organization, organization.MaxAutoscaleSmServiceAccounts.Value, Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AdjustServiceAccountsAsync_ThrowsBadRequestException_WhenSmServiceAccountsIsNull( + Organization organization, + SutProvider sutProvider) + { + organization.SmServiceAccounts = null; + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustServiceAccounts(1); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Organization has no Service Accounts limit, no need to adjust Service Accounts", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_ServiceAccountAutoscaling_Subtracting_ThrowsBadRequestException( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, true); + update.AdjustServiceAccounts(-2); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Cannot use autoscaling to subtract service accounts.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData(PlanType.Free)] + public async Task UpdateSubscriptionAsync_WithHasAdditionalServiceAccountOptionFalse_ThrowsBadRequestException( PlanType planType, Organization organization, SutProvider sutProvider) { organization.PlanType = planType; - organization.UseSecretsManager = true; - - var update = new SecretsManagerSubscriptionUpdate(organization, true); - update.AdjustSeats(-2); + var update = new SecretsManagerSubscriptionUpdate(organization, false); + update.AdjustServiceAccounts(1); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains("Cannot use autoscaling to subtract seats.", exception.Message); + Assert.Contains("You have reached the maximum number of service accounts (3) for this plan", + exception.Message, StringComparison.InvariantCultureIgnoreCase); await VerifyDependencyNotCalledAsync(sutProvider); } [Theory] - [BitAutoData(false, "Cannot update subscription on a self-hosted instance.")] - [BitAutoData(true, "Cannot autoscale on a self-hosted instance.")] - public async Task UpdatingSubscription_WhenSelfHosted_ThrowsBadRequestException( - bool autoscaling, - string expectedError, + [BitAutoData] + public async Task ServiceAccountAutoscaling_MaxLimitReached_ThrowsBadRequestException( Organization organization, SutProvider sutProvider) { - organization.PlanType = PlanType.EnterpriseAnnually; - organization.UseSecretsManager = true; + organization.SmServiceAccounts = 9; + organization.MaxAutoscaleSmServiceAccounts = 10; - var update = new SecretsManagerSubscriptionUpdate(organization, autoscaling); - update.AdjustSeats(2); - - sutProvider.GetDependency().SelfHosted.Returns(true); + var update = new SecretsManagerSubscriptionUpdate(organization, true); + update.AdjustServiceAccounts(2); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); - Assert.Contains(expectedError, exception.Message); + Assert.Contains("Secrets Manager service account limit has been reached.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_ServiceAccountsGreaterThanMaxAutoscaleSeats_ThrowsException( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmServiceAccounts = 15, + MaxAutoscaleSmServiceAccounts = 10 + }; + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Cannot set max service accounts autoscaling below service account amount", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_ServiceAccountsLessThanPlanMinimum_ThrowsException( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmServiceAccounts = 199, + }; + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Plan has a minimum of 200 Service Accounts", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData(PlanType.EnterpriseAnnually)] + [BitAutoData(PlanType.EnterpriseMonthly)] + [BitAutoData(PlanType.TeamsMonthly)] + [BitAutoData(PlanType.TeamsAnnually)] + public async Task UpdateSmServiceAccounts_WhenCurrentServiceAccountsIsGreaterThanNew_ThrowsBadRequestException( + PlanType planType, + Organization organization, + SutProvider sutProvider) + { + var currentServiceAccounts = 301; + organization.PlanType = planType; + organization.SmServiceAccounts = currentServiceAccounts; + var update = new SecretsManagerSubscriptionUpdate(organization, false) { SmServiceAccounts = 201 }; + + sutProvider.GetDependency() + .GetServiceAccountCountByOrganizationIdAsync(organization.Id) + .Returns(currentServiceAccounts); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Your organization currently has 301 Service Accounts. Your plan only allows 201 Service Accounts. Remove some Service Accounts", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task UpdateSubscriptionAsync_ThrowsBadRequestException_WhenMaxAutoscaleSeatsBelowSeatCount( + Organization organization, + SutProvider sutProvider) + { + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + SmSeats = 10, + MaxAutoscaleSmSeats = 5 + }; + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Cannot set max seat autoscaling below seat count.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData(PlanType.Free)] + public async Task UpdateMaxAutoscaleSmSeats_ThrowsBadRequestException_WhenExceedsPlanMaxUsers( + PlanType planType, + Organization organization, + SutProvider sutProvider) + { + organization.PlanType = planType; + organization.SmSeats = 2; + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + MaxAutoscaleSmSeats = 3 + }; + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Your plan has a Secrets Manager seat limit of 2, but you have specified a max autoscale count of 3.Reduce your max autoscale count.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData(PlanType.Free)] + public async Task UpdateMaxAutoscaleSmSeats_ThrowsBadRequestException_WhenPlanDoesNotAllowAutoscale( + PlanType planType, + Organization organization, + SutProvider sutProvider) + { + organization.PlanType = planType; + organization.SmSeats = 2; + var update = new SecretsManagerSubscriptionUpdate(organization, false) + { + MaxAutoscaleSmSeats = 2 + }; + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Your plan does not allow Secrets Manager seat autoscaling", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + + [Theory] + [BitAutoData(PlanType.Free)] + public async Task UpdateMaxAutoscaleSmServiceAccounts_ThrowsBadRequestException_WhenPlanDoesNotAllowAutoscale( + PlanType planType, + Organization organization, + SutProvider sutProvider) + { + organization.PlanType = planType; + organization.SmServiceAccounts = 3; + + var update = new SecretsManagerSubscriptionUpdate(organization, false) { MaxAutoscaleSmServiceAccounts = 3 }; + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); + Assert.Contains("Your plan does not allow Service Accounts autoscaling.", exception.Message); await VerifyDependencyNotCalledAsync(sutProvider); }