1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

Revert "[PM-25379] Refactor org metadata (#6418)" (#6439)

This reverts commit 3bef57259d.
This commit is contained in:
Kyle Denney
2025-10-10 09:06:58 -05:00
committed by GitHub
parent c9970a0782
commit 3272586e31
12 changed files with 97 additions and 498 deletions

1
.gitignore vendored
View File

@@ -231,4 +231,3 @@ bitwarden_license/src/Sso/Sso.zip
/identity.json /identity.json
/api.json /api.json
/api.public.json /api.public.json
.serena/

View File

@@ -38,7 +38,9 @@ public class OrganizationBillingController(
return Error.NotFound(); return Error.NotFound();
} }
return TypedResults.Ok(metadata); var response = OrganizationMetadataResponse.From(metadata);
return TypedResults.Ok(response);
} }
[HttpGet("history")] [HttpGet("history")]

View File

@@ -4,7 +4,6 @@ using Bit.Api.Billing.Attributes;
using Bit.Api.Billing.Models.Requests.Payment; using Bit.Api.Billing.Models.Requests.Payment;
using Bit.Api.Billing.Models.Requests.Subscriptions; using Bit.Api.Billing.Models.Requests.Subscriptions;
using Bit.Api.Billing.Models.Requirements; using Bit.Api.Billing.Models.Requirements;
using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Commands; using Bit.Core.Billing.Commands;
using Bit.Core.Billing.Organizations.Queries; using Bit.Core.Billing.Organizations.Queries;
@@ -26,7 +25,6 @@ public class OrganizationBillingVNextController(
ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand, ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand,
IGetBillingAddressQuery getBillingAddressQuery, IGetBillingAddressQuery getBillingAddressQuery,
IGetCreditQuery getCreditQuery, IGetCreditQuery getCreditQuery,
IGetOrganizationMetadataQuery getOrganizationMetadataQuery,
IGetOrganizationWarningsQuery getOrganizationWarningsQuery, IGetOrganizationWarningsQuery getOrganizationWarningsQuery,
IGetPaymentMethodQuery getPaymentMethodQuery, IGetPaymentMethodQuery getPaymentMethodQuery,
IRestartSubscriptionCommand restartSubscriptionCommand, IRestartSubscriptionCommand restartSubscriptionCommand,
@@ -115,23 +113,6 @@ public class OrganizationBillingVNextController(
return Handle(result); return Handle(result);
} }
[Authorize<MemberOrProviderRequirement>]
[HttpGet("metadata")]
[RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)]
[InjectOrganization]
public async Task<IResult> GetMetadataAsync(
[BindNever] Organization organization)
{
var metadata = await getOrganizationMetadataQuery.Run(organization);
if (metadata == null)
{
return TypedResults.NotFound();
}
return TypedResults.Ok(metadata);
}
[Authorize<MemberOrProviderRequirement>] [Authorize<MemberOrProviderRequirement>]
[HttpGet("warnings")] [HttpGet("warnings")]
[InjectOrganization] [InjectOrganization]

View File

@@ -0,0 +1,31 @@
using Bit.Core.Billing.Organizations.Models;
namespace Bit.Api.Billing.Models.Responses;
public record OrganizationMetadataResponse(
bool IsEligibleForSelfHost,
bool IsManaged,
bool IsOnSecretsManagerStandalone,
bool IsSubscriptionUnpaid,
bool HasSubscription,
bool HasOpenInvoice,
bool IsSubscriptionCanceled,
DateTime? InvoiceDueDate,
DateTime? InvoiceCreatedDate,
DateTime? SubPeriodEndDate,
int OrganizationOccupiedSeats)
{
public static OrganizationMetadataResponse From(OrganizationMetadata metadata)
=> new(
metadata.IsEligibleForSelfHost,
metadata.IsManaged,
metadata.IsOnSecretsManagerStandalone,
metadata.IsSubscriptionUnpaid,
metadata.HasSubscription,
metadata.HasOpenInvoice,
metadata.IsSubscriptionCanceled,
metadata.InvoiceDueDate,
metadata.InvoiceCreatedDate,
metadata.SubPeriodEndDate,
metadata.OrganizationOccupiedSeats);
}

View File

@@ -31,7 +31,6 @@ public static class ServiceCollectionExtensions
services.AddPaymentOperations(); services.AddPaymentOperations();
services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationLicenseCommandsQueries();
services.AddPremiumCommands(); services.AddPremiumCommands();
services.AddTransient<IGetOrganizationMetadataQuery, GetOrganizationMetadataQuery>();
services.AddTransient<IGetOrganizationWarningsQuery, GetOrganizationWarningsQuery>(); services.AddTransient<IGetOrganizationWarningsQuery, GetOrganizationWarningsQuery>();
services.AddTransient<IRestartSubscriptionCommand, RestartSubscriptionCommand>(); services.AddTransient<IRestartSubscriptionCommand, RestartSubscriptionCommand>();
services.AddTransient<IPreviewOrganizationTaxCommand, PreviewOrganizationTaxCommand>(); services.AddTransient<IPreviewOrganizationTaxCommand, PreviewOrganizationTaxCommand>();

View File

@@ -1,10 +1,28 @@
namespace Bit.Core.Billing.Organizations.Models; namespace Bit.Core.Billing.Organizations.Models;
public record OrganizationMetadata( public record OrganizationMetadata(
bool IsEligibleForSelfHost,
bool IsManaged,
bool IsOnSecretsManagerStandalone, bool IsOnSecretsManagerStandalone,
bool IsSubscriptionUnpaid,
bool HasSubscription,
bool HasOpenInvoice,
bool IsSubscriptionCanceled,
DateTime? InvoiceDueDate,
DateTime? InvoiceCreatedDate,
DateTime? SubPeriodEndDate,
int OrganizationOccupiedSeats) int OrganizationOccupiedSeats)
{ {
public static OrganizationMetadata Default => new OrganizationMetadata( public static OrganizationMetadata Default => new OrganizationMetadata(
false, false,
false,
false,
false,
false,
false,
false,
null,
null,
null,
0); 0);
} }

View File

@@ -1,95 +0,0 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Stripe;
namespace Bit.Core.Billing.Organizations.Queries;
public interface IGetOrganizationMetadataQuery
{
Task<OrganizationMetadata?> Run(Organization organization);
}
public class GetOrganizationMetadataQuery(
IGlobalSettings globalSettings,
IOrganizationRepository organizationRepository,
IPricingClient pricingClient,
ISubscriberService subscriberService) : IGetOrganizationMetadataQuery
{
public async Task<OrganizationMetadata?> Run(Organization organization)
{
if (organization == null)
{
return null;
}
if (globalSettings.SelfHosted)
{
return OrganizationMetadata.Default;
}
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
return OrganizationMetadata.Default with
{
OrganizationOccupiedSeats = orgOccupiedSeats.Total
};
}
var customer = await subscriberService.GetCustomer(organization,
new CustomerGetOptions { Expand = ["discount.coupon.applies_to"] });
var subscription = await subscriberService.GetSubscription(organization);
if (customer == null || subscription == null)
{
return OrganizationMetadata.Default with
{
OrganizationOccupiedSeats = orgOccupiedSeats.Total
};
}
var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription);
return new OrganizationMetadata(
isOnSecretsManagerStandalone,
orgOccupiedSeats.Total);
}
private async Task<bool> IsOnSecretsManagerStandalone(
Organization organization,
Customer? customer,
Subscription? subscription)
{
if (customer == null || subscription == null)
{
return false;
}
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
if (!plan.SupportsSecretsManager)
{
return false;
}
var hasCoupon = customer.Discount?.Coupon?.Id == StripeConstants.CouponIDs.SecretsManagerStandalone;
if (!hasCoupon)
{
return false;
}
var subscriptionProductIds = subscription.Items.Data.Select(item => item.Plan.ProductId);
var couponAppliesTo = customer.Discount?.Coupon?.AppliesTo?.Products;
return subscriptionProductIds.Intersect(couponAppliesTo ?? []).Any();
}
}

View File

@@ -74,12 +74,16 @@ public class OrganizationBillingService(
return OrganizationMetadata.Default; return OrganizationMetadata.Default;
} }
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); var isEligibleForSelfHost = await IsEligibleForSelfHostAsync(organization);
var isManaged = organization.Status == OrganizationStatusType.Managed;
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{ {
return OrganizationMetadata.Default with return OrganizationMetadata.Default with
{ {
IsEligibleForSelfHost = isEligibleForSelfHost,
IsManaged = isManaged,
OrganizationOccupiedSeats = orgOccupiedSeats.Total OrganizationOccupiedSeats = orgOccupiedSeats.Total
}; };
} }
@@ -93,14 +97,28 @@ public class OrganizationBillingService(
{ {
return OrganizationMetadata.Default with return OrganizationMetadata.Default with
{ {
OrganizationOccupiedSeats = orgOccupiedSeats.Total IsEligibleForSelfHost = isEligibleForSelfHost,
IsManaged = isManaged
}; };
} }
var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription); var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription);
var invoice = !string.IsNullOrEmpty(subscription.LatestInvoiceId)
? await stripeAdapter.InvoiceGetAsync(subscription.LatestInvoiceId, new InvoiceGetOptions())
: null;
return new OrganizationMetadata( return new OrganizationMetadata(
isEligibleForSelfHost,
isManaged,
isOnSecretsManagerStandalone, isOnSecretsManagerStandalone,
subscription.Status == StripeConstants.SubscriptionStatus.Unpaid,
true,
invoice?.Status == StripeConstants.InvoiceStatus.Open,
subscription.Status == StripeConstants.SubscriptionStatus.Canceled,
invoice?.DueDate,
invoice?.Created,
subscription.CurrentPeriodEnd,
orgOccupiedSeats.Total); orgOccupiedSeats.Total);
} }
@@ -518,6 +536,16 @@ public class OrganizationBillingService(
return customer; return customer;
} }
private async Task<bool> IsEligibleForSelfHostAsync(
Organization organization)
{
var plans = await pricingClient.ListPlans();
var eligibleSelfHostPlans = plans.Where(plan => plan.HasSelfHost).Select(plan => plan.Type);
return eligibleSelfHostPlans.Contains(organization.PlanType);
}
private async Task<bool> IsOnSecretsManagerStandalone( private async Task<bool> IsOnSecretsManagerStandalone(
Organization organization, Organization organization,
Customer? customer, Customer? customer,

View File

@@ -179,7 +179,6 @@ public static class FeatureFlagKeys
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates"; public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover"; public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover";
public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings"; public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings";
public const string PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure";
public const string PM24996ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog"; public const string PM24996ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog";
public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button"; public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button";
public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog"; public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog";

View File

@@ -1,4 +1,5 @@
using Bit.Api.Billing.Controllers; using Bit.Api.Billing.Controllers;
using Bit.Api.Billing.Models.Responses;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Models; using Bit.Core.Billing.Models;
using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Organizations.Models;
@@ -52,16 +53,19 @@ public class OrganizationBillingControllerTests
{ {
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId) sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
.Returns(new OrganizationMetadata(true, 10)); .Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null, 0));
var result = await sutProvider.Sut.GetMetadataAsync(organizationId); var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
Assert.IsType<Ok<OrganizationMetadata>>(result); Assert.IsType<Ok<OrganizationMetadataResponse>>(result);
var response = ((Ok<OrganizationMetadata>)result).Value; var response = ((Ok<OrganizationMetadataResponse>)result).Value;
Assert.True(response.IsEligibleForSelfHost);
Assert.True(response.IsManaged);
Assert.True(response.IsOnSecretsManagerStandalone); Assert.True(response.IsOnSecretsManagerStandalone);
Assert.Equal(10, response.OrganizationOccupiedSeats); Assert.True(response.IsSubscriptionUnpaid);
Assert.True(response.HasSubscription);
} }
[Theory, BitAutoData] [Theory, BitAutoData]

View File

@@ -1,369 +0,0 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Organizations.Queries;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Stripe;
using Xunit;
namespace Bit.Core.Test.Billing.Organizations.Queries;
[SutProviderCustomize]
public class GetOrganizationMetadataQueryTests
{
[Theory, BitAutoData]
public async Task Run_NullOrganization_ReturnsNull(
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
var result = await sutProvider.Sut.Run(null);
Assert.Null(result);
}
[Theory, BitAutoData]
public async Task Run_SelfHosted_ReturnsDefault(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
var result = await sutProvider.Sut.Run(organization);
Assert.Equal(OrganizationMetadata.Default, result);
}
[Theory, BitAutoData]
public async Task Run_NoGatewaySubscriptionId_ReturnsDefaultWithOccupiedSeats(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = null;
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 10, Sponsored = 0 });
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.False(result.IsOnSecretsManagerStandalone);
Assert.Equal(10, result.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]
public async Task Run_NullCustomer_ReturnsDefaultWithOccupiedSeats(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = "sub_123";
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 5, Sponsored = 0 });
sutProvider.GetDependency<ISubscriberService>()
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
options.Expand.Contains("discount.coupon.applies_to")))
.ReturnsNull();
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.False(result.IsOnSecretsManagerStandalone);
Assert.Equal(5, result.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]
public async Task Run_NullSubscription_ReturnsDefaultWithOccupiedSeats(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = "sub_123";
var customer = new Customer();
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 7, Sponsored = 0 });
sutProvider.GetDependency<ISubscriberService>()
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
options.Expand.Contains("discount.coupon.applies_to")))
.Returns(customer);
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization)
.ReturnsNull();
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.False(result.IsOnSecretsManagerStandalone);
Assert.Equal(7, result.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]
public async Task Run_WithSecretsManagerStandaloneCoupon_ReturnsMetadataWithFlag(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = "sub_123";
organization.PlanType = PlanType.EnterpriseAnnually;
var productId = "product_123";
var customer = new Customer
{
Discount = new Discount
{
Coupon = new Coupon
{
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
AppliesTo = new CouponAppliesTo
{
Products = [productId]
}
}
}
};
var subscription = new Subscription
{
Items = new StripeList<SubscriptionItem>
{
Data =
[
new SubscriptionItem
{
Plan = new Plan
{
ProductId = productId
}
}
]
}
};
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 15, Sponsored = 0 });
sutProvider.GetDependency<ISubscriberService>()
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
options.Expand.Contains("discount.coupon.applies_to")))
.Returns(customer);
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization)
.Returns(subscription);
sutProvider.GetDependency<IPricingClient>()
.GetPlanOrThrow(organization.PlanType)
.Returns(StaticStore.GetPlan(organization.PlanType));
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.True(result.IsOnSecretsManagerStandalone);
Assert.Equal(15, result.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]
public async Task Run_WithoutSecretsManagerStandaloneCoupon_ReturnsMetadataWithoutFlag(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = "sub_123";
organization.PlanType = PlanType.TeamsAnnually;
var customer = new Customer
{
Discount = null
};
var subscription = new Subscription
{
Items = new StripeList<SubscriptionItem>
{
Data =
[
new SubscriptionItem
{
Plan = new Plan
{
ProductId = "product_123"
}
}
]
}
};
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 20, Sponsored = 0 });
sutProvider.GetDependency<ISubscriberService>()
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
options.Expand.Contains("discount.coupon.applies_to")))
.Returns(customer);
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization)
.Returns(subscription);
sutProvider.GetDependency<IPricingClient>()
.GetPlanOrThrow(organization.PlanType)
.Returns(StaticStore.GetPlan(organization.PlanType));
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.False(result.IsOnSecretsManagerStandalone);
Assert.Equal(20, result.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]
public async Task Run_CouponDoesNotApplyToSubscriptionProducts_ReturnsFalseForStandaloneFlag(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = "sub_123";
organization.PlanType = PlanType.EnterpriseAnnually;
var customer = new Customer
{
Discount = new Discount
{
Coupon = new Coupon
{
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
AppliesTo = new CouponAppliesTo
{
Products = ["different_product_id"]
}
}
}
};
var subscription = new Subscription
{
Items = new StripeList<SubscriptionItem>
{
Data =
[
new SubscriptionItem
{
Plan = new Plan
{
ProductId = "product_123"
}
}
]
}
};
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 12, Sponsored = 0 });
sutProvider.GetDependency<ISubscriberService>()
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
options.Expand.Contains("discount.coupon.applies_to")))
.Returns(customer);
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization)
.Returns(subscription);
sutProvider.GetDependency<IPricingClient>()
.GetPlanOrThrow(organization.PlanType)
.Returns(StaticStore.GetPlan(organization.PlanType));
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.False(result.IsOnSecretsManagerStandalone);
Assert.Equal(12, result.OrganizationOccupiedSeats);
}
[Theory, BitAutoData]
public async Task Run_PlanDoesNotSupportSecretsManager_ReturnsFalseForStandaloneFlag(
Organization organization,
SutProvider<GetOrganizationMetadataQuery> sutProvider)
{
organization.GatewaySubscriptionId = "sub_123";
organization.PlanType = PlanType.FamiliesAnnually;
var productId = "product_123";
var customer = new Customer
{
Discount = new Discount
{
Coupon = new Coupon
{
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
AppliesTo = new CouponAppliesTo
{
Products = [productId]
}
}
}
};
var subscription = new Subscription
{
Items = new StripeList<SubscriptionItem>
{
Data =
[
new SubscriptionItem
{
Plan = new Plan
{
ProductId = productId
}
}
]
}
};
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(false);
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 8, Sponsored = 0 });
sutProvider.GetDependency<ISubscriberService>()
.GetCustomer(organization, Arg.Is<CustomerGetOptions>(options =>
options.Expand.Contains("discount.coupon.applies_to")))
.Returns(customer);
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization)
.Returns(subscription);
sutProvider.GetDependency<IPricingClient>()
.GetPlanOrThrow(organization.PlanType)
.Returns(StaticStore.GetPlan(organization.PlanType));
var result = await sutProvider.Sut.Run(organization);
Assert.NotNull(result);
Assert.False(result.IsOnSecretsManagerStandalone);
Assert.Equal(8, result.OrganizationOccupiedSeats);
}
}

View File

@@ -96,10 +96,6 @@ public class OrganizationBillingServiceTests
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType) sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
.Returns(StaticStore.GetPlan(organization.PlanType)); .Returns(StaticStore.GetPlan(organization.PlanType));
sutProvider.GetDependency<IOrganizationRepository>()
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
.Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 });
var subscriberService = sutProvider.GetDependency<ISubscriberService>(); var subscriberService = sutProvider.GetDependency<ISubscriberService>();
// Set up subscriber service to return null for customer // Set up subscriber service to return null for customer
@@ -114,7 +110,13 @@ public class OrganizationBillingServiceTests
Assert.NotNull(metadata); Assert.NotNull(metadata);
Assert.False(metadata!.IsOnSecretsManagerStandalone); Assert.False(metadata!.IsOnSecretsManagerStandalone);
Assert.Equal(1, metadata.OrganizationOccupiedSeats); Assert.False(metadata.HasSubscription);
Assert.False(metadata.IsSubscriptionUnpaid);
Assert.False(metadata.HasOpenInvoice);
Assert.False(metadata.IsSubscriptionCanceled);
Assert.Null(metadata.InvoiceDueDate);
Assert.Null(metadata.InvoiceCreatedDate);
Assert.Null(metadata.SubPeriodEndDate);
} }
#endregion #endregion