mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 08:32:50 -05:00
[AC-1486] Feature: SM Billing (#3073)
* [AC-1423] Add AddonProduct and BitwardenProduct properties to BillingSubscriptionItem (#3037) * [AC-1423] Add AddonProduct and BitwardenProduct properties to BillingSubscriptionItem * [AC-1423] Add helper to StaticStore.cs to find a Plan by StripePlanId * [AC-1423] Use the helper method to set SubscriptionInfo.BitwardenProduct * Add SecretsManagerBilling feature flag to Constants * [AC 1409] Secrets Manager Subscription Stripe Integration (#3019) * [AC-1418] Add missing SecretsManagerPlan property to OrganizationResponseModel (#3055) * [AC 1460] Update Stripe Configuration (#3070) * [AC 1410] Secrets Manager subscription adjustment back-end changes (#3036) * Create UpgradeSecretsManagerSubscription command * [AC-1495] Extract UpgradePlanAsync into a command (#3081) * This is a pure lift & shift with no refactors * [AC-1503] Fix Stripe integration on organization upgrade (#3084) * Fix SM parameters not being passed to Stripe * [AC-1504] Allow SM max autoscale limits to be disabled (#3085) * [AC-1488] Changed SM Signup and Upgrade paths to set SmServiceAccounts to include the plan BaseServiceAccount (#3086) * [AC-1510] Enable access to Secrets Manager to Organization owner for new Subscription (#3089) * Revert changes to ReferenceEvent code (#3091) This will be done in AC-1481 * Add UsePasswordManager to sync data (#3114) * [AC-1522] Fix service account check on upgrading (#3111) * [AC-1521] Address checkmarx security feedback (#3124) * Reinstate target attribute but add noopener noreferrer * Update date on migration script --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: Thomas Rittson <trittson@bitwarden.com> Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: cyprain-okeke <cokeke@bitwarden.com> Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Conner Turnbull <cturnbull@bitwarden.com> Co-authored-by: Rui Tome <rtome@bitwarden.com>
This commit is contained in:
@ -0,0 +1,748 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSubscriptionUpdate;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpdateSecretsManagerSubscriptionCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_NoOrganization_Throws(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns((Organization)null);
|
||||
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = null,
|
||||
SmSeatsAdjustment = 0
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Organization is not found", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_NoSecretsManagerAccess_ThrowsException(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = 10,
|
||||
SmServiceAccounts = 5,
|
||||
UseSecretsManager = false,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 10
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organizationId)
|
||||
.Returns(organization);
|
||||
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmSeats = 1
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Organization has no access to Secrets Manager.", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_SeatsAdustmentGreaterThanMaxAutoscaleSeats_ThrowsException(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = 10,
|
||||
UseSecretsManager = true,
|
||||
SmServiceAccounts = 5,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 10,
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
};
|
||||
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 10,
|
||||
SmSeatsAdjustment = 15,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() + 10,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() + 10) - plan.BaseSeats,
|
||||
SmServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + 5,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + 5) - (int)plan.BaseServiceAccount
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Cannot set max seat autoscaling below seat count.", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_ServiceAccountsGreaterThanMaxAutoscaleSeats_ThrowsException(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> 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 plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 10,
|
||||
SmServiceAccountsAdjustment = 11,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() + 1,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() + 1) - plan.BaseSeats,
|
||||
SmServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + 11,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + 11) - (int)plan.BaseServiceAccount
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Cannot set max Service Accounts autoscaling below Service Accounts count", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_NullGatewayCustomerId_ThrowsException(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = 10,
|
||||
SmServiceAccounts = 5,
|
||||
UseSecretsManager = true,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 15,
|
||||
PlanType = PlanType.EnterpriseAnnually
|
||||
};
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 15,
|
||||
SmServiceAccountsAdjustment = 1
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("No payment method found.", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_NullGatewaySubscriptionId_ThrowsException(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> 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
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 15,
|
||||
SmServiceAccountsAdjustment = 1
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("No subscription found.", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_OrgWithNullSmSeatOnSeatsAdjustment_ThrowsException(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = null,
|
||||
UseSecretsManager = true,
|
||||
SmServiceAccounts = 5,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 15,
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
GatewayCustomerId = "1"
|
||||
};
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 15,
|
||||
SmServiceAccountsAdjustment = 1
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateSecretsManagerSubscription(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 UpdateSecretsManagerSubscription_WithNonSecretsManagerPlanType_ThrowsBadRequestException(
|
||||
PlanType planType,
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = 10,
|
||||
UseSecretsManager = true,
|
||||
SmServiceAccounts = 200,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
PlanType = planType,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2"
|
||||
};
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 1
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Existing plan not found", exception.Message, StringComparison.InvariantCultureIgnoreCase);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.Free)]
|
||||
public async Task UpdateSecretsManagerSubscription_WithHasAdditionalSeatsOptionfalse_ThrowsBadRequestException(
|
||||
PlanType planType,
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
UseSecretsManager = true,
|
||||
SmSeats = 10,
|
||||
SmServiceAccounts = 200,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
PlanType = planType,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2"
|
||||
};
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 1
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Plan does not allow additional Secrets Manager seats.", exception.Message, StringComparison.InvariantCultureIgnoreCase);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.Free)]
|
||||
public async Task UpdateSecretsManagerSubscription_WithHasAdditionalServiceAccountOptionFalse_ThrowsBadRequestException(
|
||||
PlanType planType,
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
UseSecretsManager = true,
|
||||
SmSeats = 10,
|
||||
SmServiceAccounts = 200,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
PlanType = planType,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2"
|
||||
};
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 0,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 1
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Plan does not allow additional Service Accounts", exception.Message, StringComparison.InvariantCultureIgnoreCase);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public async Task UpdateSecretsManagerSubscription_ValidInput_Passes(
|
||||
PlanType planType,
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
const int organizationServiceAccounts = 200;
|
||||
const int seatAdjustment = 5;
|
||||
const int maxAutoscaleSeats = 15;
|
||||
const int serviceAccountAdjustment = 100;
|
||||
const int maxAutoScaleServiceAccounts = 300;
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
UseSecretsManager = true,
|
||||
SmSeats = 10,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
SmServiceAccounts = organizationServiceAccounts,
|
||||
MaxAutoscaleSmServiceAccounts = 350,
|
||||
PlanType = planType,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2"
|
||||
};
|
||||
|
||||
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
SmSeatsAdjustment = seatAdjustment,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() + seatAdjustment,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() + seatAdjustment) - plan.BaseSeats,
|
||||
MaxAutoscaleSmSeats = maxAutoscaleSeats,
|
||||
|
||||
SmServiceAccountsAdjustment = serviceAccountAdjustment,
|
||||
SmServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + serviceAccountAdjustment,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + serviceAccountAdjustment) - (int)plan.BaseServiceAccount,
|
||||
MaxAutoscaleSmServiceAccounts = maxAutoScaleServiceAccounts,
|
||||
|
||||
MaxAutoscaleSmSeatsChanged = maxAutoscaleSeats != organization.MaxAutoscaleSeats.GetValueOrDefault(),
|
||||
MaxAutoscaleSmServiceAccountsChanged = 200 != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate);
|
||||
|
||||
if (organizationUpdate.SmSeatsAdjustment != 0)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1)
|
||||
.AdjustServiceAccountsAsync(organization, plan, organizationUpdate.SmServiceAccountsExcludingBase);
|
||||
|
||||
// TODO: call ReferenceEventService - see AC-1481
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(org => org.SmSeats == organizationUpdate.SmSeats));
|
||||
}
|
||||
|
||||
if (organizationUpdate.SmServiceAccountsAdjustment != 0)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1)
|
||||
.AdjustSeatsAsync(organization, plan, organizationUpdate.SmSeatsExcludingBase);
|
||||
|
||||
// TODO: call ReferenceEventService - see AC-1481
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(org =>
|
||||
org.SmServiceAccounts == (organizationServiceAccounts + organizationUpdate.SmServiceAccountsAdjustment)));
|
||||
}
|
||||
|
||||
if (organizationUpdate.MaxAutoscaleSmSeats != organization.MaxAutoscaleSmSeats)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(org =>
|
||||
org.MaxAutoscaleSmSeats == organizationUpdate.MaxAutoscaleSmServiceAccounts));
|
||||
}
|
||||
|
||||
if (organizationUpdate.MaxAutoscaleSmServiceAccounts != organization.MaxAutoscaleSmServiceAccounts)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(org =>
|
||||
org.MaxAutoscaleSmServiceAccounts == organizationUpdate.MaxAutoscaleSmServiceAccounts));
|
||||
}
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendSecretsManagerMaxSeatLimitReachedEmailAsync(organization, organization.MaxAutoscaleSmSeats.Value, Arg.Any<IEnumerable<string>>());
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(organization, organization.MaxAutoscaleSmServiceAccounts.Value, Arg.Any<IEnumerable<string>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_ThrowsBadRequestException_WhenMaxAutoscaleSeatsBelowSeatCount(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> 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 plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 4,
|
||||
SmSeatsAdjustment = 1,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 5,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() + 1,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() + 1) - plan.BaseSeats,
|
||||
SmServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + 5,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + 5) - (int)plan.BaseServiceAccount
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(update));
|
||||
Assert.Contains("Cannot set max seat autoscaling below seat count.", exception.Message);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateSecretsManagerSubscription_ThrowsBadRequestException_WhenOccupiedSeatsExceedNewSeatTotal(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
UseSecretsManager = true,
|
||||
SmSeats = 10,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2",
|
||||
PlanType = PlanType.EnterpriseAnnually
|
||||
};
|
||||
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 7,
|
||||
SmSeatsAdjustment = -3,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 5,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() - 3,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() - 3) - plan.BaseSeats,
|
||||
SmServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + 5,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + 5) - (int)plan.BaseServiceAccount
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetOccupiedSmSeatCountByOrganizationIdAsync(organizationId).Returns(8);
|
||||
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(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 AdjustServiceAccountsAsync_ThrowsBadRequestException_WhenSmServiceAccountsIsNull(
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = 10,
|
||||
UseSecretsManager = true,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2",
|
||||
SmServiceAccounts = null,
|
||||
PlanType = PlanType.EnterpriseAnnually
|
||||
};
|
||||
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 21,
|
||||
SmSeatsAdjustment = 10,
|
||||
MaxAutoscaleSmServiceAccounts = 250,
|
||||
SmServiceAccountsAdjustment = 1,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() + 10,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() + 10) - plan.BaseSeats,
|
||||
SmServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + 1,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + 1) - (int)plan.BaseServiceAccount
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(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<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
SmSeats = 3,
|
||||
UseSecretsManager = true,
|
||||
SmServiceAccounts = 100,
|
||||
PlanType = PlanType.Free,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2",
|
||||
};
|
||||
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 0,
|
||||
MaxAutoscaleSmServiceAccounts = 200,
|
||||
SmServiceAccountsAdjustment = 0,
|
||||
MaxAutoscaleSmSeatsChanged = 15 != organization.MaxAutoscaleSeats.GetValueOrDefault(),
|
||||
MaxAutoscaleSmServiceAccountsChanged = 200 != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(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<UpdateSecretsManagerSubscriptionCommand> 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
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 1,
|
||||
SmSeatsAdjustment = 0,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 0,
|
||||
MaxAutoscaleSmSeatsChanged = 1 != organization.MaxAutoscaleSeats.GetValueOrDefault(),
|
||||
MaxAutoscaleSmServiceAccountsChanged = 200 != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(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<UpdateSecretsManagerSubscriptionCommand> 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
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = null,
|
||||
SmSeatsAdjustment = 0,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 0,
|
||||
MaxAutoscaleSmSeatsChanged = false,
|
||||
MaxAutoscaleSmServiceAccountsChanged = 300 != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(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(
|
||||
PlanType planType,
|
||||
Guid organizationId,
|
||||
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
var organization = new Organization
|
||||
{
|
||||
Id = organizationId,
|
||||
UseSecretsManager = true,
|
||||
SmSeats = 10,
|
||||
SmServiceAccounts = 301,
|
||||
MaxAutoscaleSmSeats = 20,
|
||||
MaxAutoscaleSmServiceAccounts = 350,
|
||||
PlanType = planType,
|
||||
GatewayCustomerId = "1",
|
||||
GatewaySubscriptionId = "2"
|
||||
};
|
||||
|
||||
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
|
||||
var organizationUpdate = new SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
MaxAutoscaleSmSeats = 15,
|
||||
SmSeatsAdjustment = 5,
|
||||
MaxAutoscaleSmServiceAccounts = 300,
|
||||
SmServiceAccountsAdjustment = 100,
|
||||
SmSeats = organization.SmSeats.GetValueOrDefault() + 5,
|
||||
SmSeatsExcludingBase = (organization.SmSeats.GetValueOrDefault() + 5) - plan.BaseSeats,
|
||||
SmServiceAccounts = 300,
|
||||
SmServiceAccountsExcludingBase = (organization.SmServiceAccounts.GetValueOrDefault() + 100) - (int)plan.BaseServiceAccount,
|
||||
MaxAutoscaleSmSeatsChanged = 15 != organization.MaxAutoscaleSeats.GetValueOrDefault(),
|
||||
MaxAutoscaleSmServiceAccountsChanged = 200 != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
|
||||
};
|
||||
var currentServiceAccounts = 301;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.GetServiceAccountCountByOrganizationIdAsync(organization.Id)
|
||||
.Returns(currentServiceAccounts);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateSecretsManagerSubscription(organizationUpdate));
|
||||
Assert.Contains("Your organization currently has 301 Service Accounts. Your plan only allows 300 Service Accounts. Remove some Service Accounts", exception.Message);
|
||||
await sutProvider.GetDependency<IServiceAccountRepository>().Received(1).GetServiceAccountCountByOrganizationIdAsync(organization.Id);
|
||||
await VerifyDependencyNotCalledAsync(sutProvider);
|
||||
}
|
||||
|
||||
private static async Task VerifyDependencyNotCalledAsync(SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().DidNotReceive()
|
||||
.AdjustSeatsAsync(Arg.Any<Organization>(), Arg.Any<Plan>(), Arg.Any<int>());
|
||||
await sutProvider.GetDependency<IPaymentService>().DidNotReceive()
|
||||
.AdjustServiceAccountsAsync(Arg.Any<Organization>(), Arg.Any<Plan>(), Arg.Any<int>());
|
||||
// TODO: call ReferenceEventService - see AC-1481
|
||||
await sutProvider.GetDependency<IOrganizationService>().DidNotReceive()
|
||||
.ReplaceAndUpdateCacheAsync(Arg.Any<Organization>());
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationMaxSeatLimitReachedEmailAsync(Arg.Any<Organization>(), Arg.Any<int>(),
|
||||
Arg.Any<IEnumerable<string>>());
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Organization = Bit.Core.Entities.Organization;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSubscriptionUpdate;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpgradeOrganizationPlanCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpgradePlan_OrganizationIsNull_Throws(Guid organizationId, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(Task.FromResult<Organization>(null));
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organizationId, upgrade));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpgradePlan_GatewayCustomIdIsNull_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
organization.GatewayCustomerId = string.Empty;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("no payment method", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpgradePlan_AlreadyInPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.Plan = organization.PlanType;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("already on this plan", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpgradePlan_SM_AlreadyInPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.Plan = organization.PlanType;
|
||||
upgrade.UseSecretsManager = true;
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalServiceAccounts = 10;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("already on this plan", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize(CheckedPlanType = PlanType.Free), BitAutoData]
|
||||
public async Task UpgradePlan_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("can only upgrade", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize(CheckedPlanType = PlanType.Free), BitAutoData]
|
||||
public async Task UpgradePlan_SM_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.UseSecretsManager = true;
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalServiceAccounts = 10;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("can only upgrade", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[FreeOrganizationUpgradeCustomize, BitAutoData]
|
||||
public async Task UpgradePlan_Passes(Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalSeats = 10;
|
||||
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(organization);
|
||||
}
|
||||
|
||||
[Theory, FreeOrganizationUpgradeCustomize]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public async Task UpgradePlan_SM_Passes(PlanType planType, Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.Plan = planType;
|
||||
|
||||
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(upgrade.Plan);
|
||||
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(upgrade.Plan);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
upgrade.AdditionalSeats = 15;
|
||||
upgrade.AdditionalSmSeats = 10;
|
||||
upgrade.AdditionalServiceAccounts = 20;
|
||||
|
||||
var result = await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == passwordManagerPlan.BaseSeats + upgrade.AdditionalSeats
|
||||
&& o.SmSeats == secretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats
|
||||
&& o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + upgrade.AdditionalServiceAccounts));
|
||||
|
||||
Assert.True(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
}
|
||||
|
||||
|
||||
[Theory, FreeOrganizationUpgradeCustomize]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public async Task UpgradePlan_SM_NotEnoughSmSeats_Throws(PlanType planType, Organization organization, OrganizationUpgrade upgrade,
|
||||
SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.Plan = planType;
|
||||
upgrade.AdditionalSeats = 15;
|
||||
upgrade.AdditionalSmSeats = 1;
|
||||
upgrade.AdditionalServiceAccounts = 0;
|
||||
|
||||
organization.SmSeats = 2;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id).Returns(2);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains("Your organization currently has 2 Secrets Manager seats filled. Your new plan only has", exception.Message);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs().ReplaceAndUpdateCacheAsync(default);
|
||||
}
|
||||
|
||||
[Theory, FreeOrganizationUpgradeCustomize]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly, 201)]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually, 201)]
|
||||
[BitAutoData(PlanType.TeamsMonthly, 51)]
|
||||
[BitAutoData(PlanType.TeamsAnnually, 51)]
|
||||
public async Task UpgradePlan_SM_NotEnoughServiceAccounts_Throws(PlanType planType, int currentServiceAccounts,
|
||||
Organization organization, OrganizationUpgrade upgrade, SutProvider<UpgradeOrganizationPlanCommand> sutProvider)
|
||||
{
|
||||
upgrade.Plan = planType;
|
||||
upgrade.AdditionalSeats = 15;
|
||||
upgrade.AdditionalSmSeats = 1;
|
||||
upgrade.AdditionalServiceAccounts = 0;
|
||||
|
||||
organization.SmSeats = 1;
|
||||
organization.SmServiceAccounts = currentServiceAccounts;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id).Returns(1);
|
||||
sutProvider.GetDependency<IServiceAccountRepository>()
|
||||
.GetServiceAccountCountByOrganizationIdAsync(organization.Id).Returns(currentServiceAccounts);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
|
||||
Assert.Contains($"Your organization currently has {currentServiceAccounts} service accounts. Your new plan only allows", exception.Message);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs().ReplaceAndUpdateCacheAsync(default);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user