diff --git a/.gitignore b/.gitignore index 3b1f40e673bd..fd24c7713d56 100644 --- a/.gitignore +++ b/.gitignore @@ -231,3 +231,4 @@ bitwarden_license/src/Sso/Sso.zip /identity.json /api.json /api.public.json +.serena/ diff --git a/src/Api/Billing/Controllers/OrganizationBillingController.cs b/src/Api/Billing/Controllers/OrganizationBillingController.cs index 1d6bf5166185..6e4cacc15597 100644 --- a/src/Api/Billing/Controllers/OrganizationBillingController.cs +++ b/src/Api/Billing/Controllers/OrganizationBillingController.cs @@ -38,9 +38,7 @@ public async Task GetMetadataAsync([FromRoute] Guid organizationId) return Error.NotFound(); } - var response = OrganizationMetadataResponse.From(metadata); - - return TypedResults.Ok(response); + return TypedResults.Ok(metadata); } [HttpGet("history")] diff --git a/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs b/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs index 2f825f2cb9b6..64ec068a5e8a 100644 --- a/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs +++ b/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs @@ -4,6 +4,7 @@ using Bit.Api.Billing.Models.Requests.Payment; using Bit.Api.Billing.Models.Requests.Subscriptions; using Bit.Api.Billing.Models.Requirements; +using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Commands; using Bit.Core.Billing.Organizations.Queries; @@ -25,6 +26,7 @@ public class OrganizationBillingVNextController( ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand, IGetBillingAddressQuery getBillingAddressQuery, IGetCreditQuery getCreditQuery, + IGetOrganizationMetadataQuery getOrganizationMetadataQuery, IGetOrganizationWarningsQuery getOrganizationWarningsQuery, IGetPaymentMethodQuery getPaymentMethodQuery, IRestartSubscriptionCommand restartSubscriptionCommand, @@ -113,6 +115,23 @@ public async Task RestartSubscriptionAsync( return Handle(result); } + [Authorize] + [HttpGet("metadata")] + [RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)] + [InjectOrganization] + public async Task GetMetadataAsync( + [BindNever] Organization organization) + { + var metadata = await getOrganizationMetadataQuery.Run(organization); + + if (metadata == null) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(metadata); + } + [Authorize] [HttpGet("warnings")] [InjectOrganization] diff --git a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs deleted file mode 100644 index a13f267c3b02..000000000000 --- a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs +++ /dev/null @@ -1,31 +0,0 @@ -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); -} diff --git a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs index 7aec422a4b14..d6593f5365df 100644 --- a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs +++ b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ public static void AddBillingOperations(this IServiceCollection services) services.AddPaymentOperations(); services.AddOrganizationLicenseCommandsQueries(); services.AddPremiumCommands(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs b/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs index 2bcd213dbf77..fedd0ad78c5b 100644 --- a/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs +++ b/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs @@ -1,28 +1,10 @@ namespace Bit.Core.Billing.Organizations.Models; public record OrganizationMetadata( - bool IsEligibleForSelfHost, - bool IsManaged, bool IsOnSecretsManagerStandalone, - bool IsSubscriptionUnpaid, - bool HasSubscription, - bool HasOpenInvoice, - bool IsSubscriptionCanceled, - DateTime? InvoiceDueDate, - DateTime? InvoiceCreatedDate, - DateTime? SubPeriodEndDate, int OrganizationOccupiedSeats) { public static OrganizationMetadata Default => new OrganizationMetadata( false, - false, - false, - false, - false, - false, - false, - null, - null, - null, 0); } diff --git a/src/Core/Billing/Organizations/Queries/GetOrganizationMetadataQuery.cs b/src/Core/Billing/Organizations/Queries/GetOrganizationMetadataQuery.cs new file mode 100644 index 000000000000..63da0477a1aa --- /dev/null +++ b/src/Core/Billing/Organizations/Queries/GetOrganizationMetadataQuery.cs @@ -0,0 +1,95 @@ +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 Run(Organization organization); +} + +public class GetOrganizationMetadataQuery( + IGlobalSettings globalSettings, + IOrganizationRepository organizationRepository, + IPricingClient pricingClient, + ISubscriberService subscriberService) : IGetOrganizationMetadataQuery +{ + public async Task 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 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(); + } +} diff --git a/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs b/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs index ce8a9a877b08..077225c36418 100644 --- a/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs +++ b/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs @@ -72,16 +72,12 @@ public async Task Finalize(OrganizationSale sale) return OrganizationMetadata.Default; } - var isEligibleForSelfHost = await IsEligibleForSelfHostAsync(organization); - - var isManaged = organization.Status == OrganizationStatusType.Managed; var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); + if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { return OrganizationMetadata.Default with { - IsEligibleForSelfHost = isEligibleForSelfHost, - IsManaged = isManaged, OrganizationOccupiedSeats = orgOccupiedSeats.Total }; } @@ -95,28 +91,14 @@ public async Task Finalize(OrganizationSale sale) { return OrganizationMetadata.Default with { - IsEligibleForSelfHost = isEligibleForSelfHost, - IsManaged = isManaged + OrganizationOccupiedSeats = orgOccupiedSeats.Total }; } var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription); - var invoice = !string.IsNullOrEmpty(subscription.LatestInvoiceId) - ? await stripeAdapter.InvoiceGetAsync(subscription.LatestInvoiceId, new InvoiceGetOptions()) - : null; - return new OrganizationMetadata( - isEligibleForSelfHost, - isManaged, 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); } @@ -534,16 +516,6 @@ ProductTierType.TeamsStarter or return customer; } - private async Task 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 IsOnSecretsManagerStandalone( Organization organization, Customer? customer, diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index cbe1b04bda47..64fde21a009f 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -181,6 +181,7 @@ public static class FeatureFlagKeys public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates"; public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover"; 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 PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button"; public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog"; diff --git a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs index 51866320ee09..d79bfde89363 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs @@ -1,5 +1,4 @@ using Bit.Api.Billing.Controllers; -using Bit.Api.Billing.Models.Responses; using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Models; using Bit.Core.Billing.Organizations.Models; @@ -53,19 +52,16 @@ public async Task GetMetadataAsync_OK( { sutProvider.GetDependency().OrganizationUser(organizationId).Returns(true); sutProvider.GetDependency().GetMetadata(organizationId) - .Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null, 0)); + .Returns(new OrganizationMetadata(true, 10)); var result = await sutProvider.Sut.GetMetadataAsync(organizationId); - Assert.IsType>(result); + Assert.IsType>(result); - var response = ((Ok)result).Value; + var response = ((Ok)result).Value; - Assert.True(response.IsEligibleForSelfHost); - Assert.True(response.IsManaged); Assert.True(response.IsOnSecretsManagerStandalone); - Assert.True(response.IsSubscriptionUnpaid); - Assert.True(response.HasSubscription); + Assert.Equal(10, response.OrganizationOccupiedSeats); } [Theory, BitAutoData] diff --git a/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs new file mode 100644 index 000000000000..21081112d7b4 --- /dev/null +++ b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs @@ -0,0 +1,369 @@ +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 sutProvider) + { + var result = await sutProvider.Sut.Run(null); + + Assert.Null(result); + } + + [Theory, BitAutoData] + public async Task Run_SelfHosted_ReturnsDefault( + Organization organization, + SutProvider sutProvider) + { + sutProvider.GetDependency().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 sutProvider) + { + organization.GatewaySubscriptionId = null; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .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 sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 5, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(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 sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + + var customer = new Customer(); + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 7, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .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 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 + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = productId + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 15, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .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 sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + organization.PlanType = PlanType.TeamsAnnually; + + var customer = new Customer + { + Discount = null + }; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = "product_123" + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 20, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .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 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 + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = "product_123" + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 12, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .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 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 + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = productId + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 8, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .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); + } +} diff --git a/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs b/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs index 7edc60a26af7..77dce8101c0f 100644 --- a/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs +++ b/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs @@ -96,6 +96,10 @@ public async Task GetMetadata_WhenCustomerOrSubscriptionIsNull_ReturnsDefaultMet sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) .Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 }); + var subscriberService = sutProvider.GetDependency(); // Set up subscriber service to return null for customer @@ -110,13 +114,7 @@ public async Task GetMetadata_WhenCustomerOrSubscriptionIsNull_ReturnsDefaultMet Assert.NotNull(metadata); Assert.False(metadata!.IsOnSecretsManagerStandalone); - 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); + Assert.Equal(1, metadata.OrganizationOccupiedSeats); } #endregion