[PM-24067] Check for unverified bank account in free trial / inactive subscription warning (#6117)

* [NO LOGIC] Move query to core

* Check for unverified bank account in free trial and inactive subscription warnings

* Run dotnet format

* fix test

* Run dotnet format

* Remove errant file
This commit is contained in:
Alex Morask 2025-07-24 09:59:23 -05:00 committed by GitHub
parent 988b994624
commit 2d1f914eae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 200 additions and 99 deletions

View File

@ -3,10 +3,10 @@ using System.Diagnostics;
using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Billing.Models.Requests; using Bit.Api.Billing.Models.Requests;
using Bit.Api.Billing.Models.Responses; using Bit.Api.Billing.Models.Responses;
using Bit.Api.Billing.Queries.Organizations;
using Bit.Core.Billing.Enums; using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Models; using Bit.Core.Billing.Models;
using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Organizations.Queries;
using Bit.Core.Billing.Organizations.Services; using Bit.Core.Billing.Organizations.Services;
using Bit.Core.Billing.Pricing; using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Providers.Services; using Bit.Core.Billing.Providers.Services;
@ -28,7 +28,7 @@ public class OrganizationBillingController(
ICurrentContext currentContext, ICurrentContext currentContext,
IOrganizationBillingService organizationBillingService, IOrganizationBillingService organizationBillingService,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationWarningsQuery organizationWarningsQuery, IGetOrganizationWarningsQuery getOrganizationWarningsQuery,
IPaymentService paymentService, IPaymentService paymentService,
IPricingClient pricingClient, IPricingClient pricingClient,
ISubscriberService subscriberService, ISubscriberService subscriberService,
@ -363,7 +363,7 @@ public class OrganizationBillingController(
public async Task<IResult> GetWarningsAsync([FromRoute] Guid organizationId) public async Task<IResult> GetWarningsAsync([FromRoute] Guid organizationId)
{ {
/* /*
* We'll keep these available at the User level, because we're hiding any pertinent information and * We'll keep these available at the User level because we're hiding any pertinent information, and
* we want to throw as few errors as possible since these are not core features. * we want to throw as few errors as possible since these are not core features.
*/ */
if (!await currentContext.OrganizationUser(organizationId)) if (!await currentContext.OrganizationUser(organizationId))
@ -378,9 +378,9 @@ public class OrganizationBillingController(
return Error.NotFound(); return Error.NotFound();
} }
var response = await organizationWarningsQuery.Run(organization); var warnings = await getOrganizationWarningsQuery.Run(organization);
return TypedResults.Ok(response); return TypedResults.Ok(warnings);
} }

View File

@ -1,11 +0,0 @@
using Bit.Api.Billing.Queries.Organizations;
namespace Bit.Api.Billing;
public static class Registrations
{
public static void AddBillingQueries(this IServiceCollection services)
{
services.AddTransient<IOrganizationWarningsQuery, OrganizationWarningsQuery>();
}
}

View File

@ -27,7 +27,6 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
using Bit.Api.Auth.Models.Request.WebAuthn; using Bit.Api.Auth.Models.Request.WebAuthn;
using Bit.Api.Billing;
using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Identity.TokenProviders; using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Tools.ImportFeatures; using Bit.Core.Tools.ImportFeatures;
@ -184,7 +183,6 @@ public class Startup
services.AddImportServices(); services.AddImportServices();
services.AddPhishingDomainServices(globalSettings); services.AddPhishingDomainServices(globalSettings);
services.AddBillingQueries();
services.AddSendServices(); services.AddSendServices();
// Authorization Handlers // Authorization Handlers

View File

@ -33,6 +33,7 @@ public static class ServiceCollectionExtensions
services.AddTransient<IPreviewTaxAmountCommand, PreviewTaxAmountCommand>(); services.AddTransient<IPreviewTaxAmountCommand, PreviewTaxAmountCommand>();
services.AddPaymentOperations(); services.AddPaymentOperations();
services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationLicenseCommandsQueries();
services.AddTransient<IGetOrganizationWarningsQuery, GetOrganizationWarningsQuery>();
} }
private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services) private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services)

View File

@ -1,7 +1,6 @@
#nullable enable namespace Bit.Core.Billing.Organizations.Models;
namespace Bit.Api.Billing.Models.Responses.Organizations;
public record OrganizationWarningsResponse public record OrganizationWarnings
{ {
public FreeTrialWarning? FreeTrial { get; set; } public FreeTrialWarning? FreeTrial { get; set; }
public InactiveSubscriptionWarning? InactiveSubscription { get; set; } public InactiveSubscriptionWarning? InactiveSubscription { get; set; }

View File

@ -1,42 +1,44 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
#nullable enable
using Bit.Api.Billing.Models.Responses.Organizations;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Caches;
using Bit.Core.Billing.Constants; using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Services; using Bit.Core.Billing.Services;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Services; using Bit.Core.Services;
using Stripe; using Stripe;
using static Bit.Core.Billing.Utilities; using FreeTrialWarning = Bit.Core.Billing.Organizations.Models.OrganizationWarnings.FreeTrialWarning;
using FreeTrialWarning = Bit.Api.Billing.Models.Responses.Organizations.OrganizationWarningsResponse.FreeTrialWarning;
using InactiveSubscriptionWarning = using InactiveSubscriptionWarning =
Bit.Api.Billing.Models.Responses.Organizations.OrganizationWarningsResponse.InactiveSubscriptionWarning; Bit.Core.Billing.Organizations.Models.OrganizationWarnings.InactiveSubscriptionWarning;
using ResellerRenewalWarning = using ResellerRenewalWarning =
Bit.Api.Billing.Models.Responses.Organizations.OrganizationWarningsResponse.ResellerRenewalWarning; Bit.Core.Billing.Organizations.Models.OrganizationWarnings.ResellerRenewalWarning;
namespace Bit.Api.Billing.Queries.Organizations; namespace Bit.Core.Billing.Organizations.Queries;
public interface IOrganizationWarningsQuery using static StripeConstants;
public interface IGetOrganizationWarningsQuery
{ {
Task<OrganizationWarningsResponse> Run( Task<OrganizationWarnings> Run(
Organization organization); Organization organization);
} }
public class OrganizationWarningsQuery( public class GetOrganizationWarningsQuery(
ICurrentContext currentContext, ICurrentContext currentContext,
IProviderRepository providerRepository, IProviderRepository providerRepository,
ISetupIntentCache setupIntentCache,
IStripeAdapter stripeAdapter, IStripeAdapter stripeAdapter,
ISubscriberService subscriberService) : IOrganizationWarningsQuery ISubscriberService subscriberService) : IGetOrganizationWarningsQuery
{ {
public async Task<OrganizationWarningsResponse> Run( public async Task<OrganizationWarnings> Run(
Organization organization) Organization organization)
{ {
var response = new OrganizationWarningsResponse(); var response = new OrganizationWarnings();
var subscription = var subscription =
await subscriberService.GetSubscription(organization, await subscriberService.GetSubscription(organization,
@ -69,7 +71,7 @@ public class OrganizationWarningsQuery(
if (subscription is not if (subscription is not
{ {
Status: StripeConstants.SubscriptionStatus.Trialing, Status: SubscriptionStatus.Trialing,
TrialEnd: not null, TrialEnd: not null,
Customer: not null Customer: not null
}) })
@ -79,10 +81,13 @@ public class OrganizationWarningsQuery(
var customer = subscription.Customer; var customer = subscription.Customer;
var hasUnverifiedBankAccount = await HasUnverifiedBankAccount(organization);
var hasPaymentMethod = var hasPaymentMethod =
!string.IsNullOrEmpty(customer.InvoiceSettings.DefaultPaymentMethodId) || !string.IsNullOrEmpty(customer.InvoiceSettings.DefaultPaymentMethodId) ||
!string.IsNullOrEmpty(customer.DefaultSourceId) || !string.IsNullOrEmpty(customer.DefaultSourceId) ||
customer.Metadata.ContainsKey(StripeConstants.MetadataKeys.BraintreeCustomerId); hasUnverifiedBankAccount ||
customer.Metadata.ContainsKey(MetadataKeys.BraintreeCustomerId);
if (hasPaymentMethod) if (hasPaymentMethod)
{ {
@ -101,49 +106,58 @@ public class OrganizationWarningsQuery(
Provider? provider, Provider? provider,
Subscription subscription) Subscription subscription)
{ {
if (organization.Enabled && subscription.Status is StripeConstants.SubscriptionStatus.Trialing) var isOrganizationOwner = await currentContext.OrganizationOwner(organization.Id);
{
var isStripeCustomerWithoutPayment =
subscription.Customer.InvoiceSettings.DefaultPaymentMethodId is null;
var isBraintreeCustomer =
subscription.Customer.Metadata.ContainsKey(BraintreeCustomerIdKey);
var hasNoPaymentMethod = isStripeCustomerWithoutPayment && !isBraintreeCustomer;
if (hasNoPaymentMethod && await currentContext.OrganizationOwner(organization.Id)) switch (organization.Enabled)
{
return new InactiveSubscriptionWarning { Resolution = "add_payment_method_optional_trial" };
}
}
if (organization.Enabled ||
subscription.Status is not StripeConstants.SubscriptionStatus.Unpaid
and not StripeConstants.SubscriptionStatus.Canceled)
{ {
return null; // Member of an enabled, trialing organization.
} case true when subscription.Status is SubscriptionStatus.Trialing:
if (provider != null)
{
return new InactiveSubscriptionWarning { Resolution = "contact_provider" };
}
if (await currentContext.OrganizationOwner(organization.Id))
{
return subscription.Status switch
{
StripeConstants.SubscriptionStatus.Unpaid => new InactiveSubscriptionWarning
{ {
Resolution = "add_payment_method" var hasUnverifiedBankAccount = await HasUnverifiedBankAccount(organization);
},
StripeConstants.SubscriptionStatus.Canceled => new InactiveSubscriptionWarning
{
Resolution = "resubscribe"
},
_ => null
};
}
return new InactiveSubscriptionWarning { Resolution = "contact_owner" }; var hasPaymentMethod =
!string.IsNullOrEmpty(subscription.Customer.InvoiceSettings.DefaultPaymentMethodId) ||
!string.IsNullOrEmpty(subscription.Customer.DefaultSourceId) ||
hasUnverifiedBankAccount ||
subscription.Customer.Metadata.ContainsKey(MetadataKeys.BraintreeCustomerId);
// If this member is the owner and there's no payment method on file, ask them to add one.
return isOrganizationOwner && !hasPaymentMethod
? new InactiveSubscriptionWarning { Resolution = "add_payment_method_optional_trial" }
: null;
}
// Member of disabled and unpaid or canceled organization.
case false when subscription.Status is SubscriptionStatus.Unpaid or SubscriptionStatus.Canceled:
{
// If the organization is managed by a provider, return a warning asking them to contact the provider.
if (provider != null)
{
return new InactiveSubscriptionWarning { Resolution = "contact_provider" };
}
/* If the organization is not managed by a provider and this user is the owner, return an action warning based
on the subscription status. */
if (isOrganizationOwner)
{
return subscription.Status switch
{
SubscriptionStatus.Unpaid => new InactiveSubscriptionWarning
{
Resolution = "add_payment_method"
},
SubscriptionStatus.Canceled => new InactiveSubscriptionWarning
{
Resolution = "resubscribe"
},
_ => null
};
}
// Otherwise, this member is not the owner, and we need to ask them to contact the owner.
return new InactiveSubscriptionWarning { Resolution = "contact_owner" };
}
default: return null;
}
} }
private async Task<ResellerRenewalWarning?> GetResellerRenewalWarning( private async Task<ResellerRenewalWarning?> GetResellerRenewalWarning(
@ -158,7 +172,7 @@ public class OrganizationWarningsQuery(
return null; return null;
} }
if (subscription.CollectionMethod != StripeConstants.CollectionMethod.SendInvoice) if (subscription.CollectionMethod != CollectionMethod.SendInvoice)
{ {
return null; return null;
} }
@ -168,8 +182,8 @@ public class OrganizationWarningsQuery(
// ReSharper disable once ConvertIfStatementToSwitchStatement // ReSharper disable once ConvertIfStatementToSwitchStatement
if (subscription is if (subscription is
{ {
Status: StripeConstants.SubscriptionStatus.Trialing or StripeConstants.SubscriptionStatus.Active, Status: SubscriptionStatus.Trialing or SubscriptionStatus.Active,
LatestInvoice: null or { Status: StripeConstants.InvoiceStatus.Paid } LatestInvoice: null or { Status: InvoiceStatus.Paid }
} && (subscription.CurrentPeriodEnd - now).TotalDays <= 14) } && (subscription.CurrentPeriodEnd - now).TotalDays <= 14)
{ {
return new ResellerRenewalWarning return new ResellerRenewalWarning
@ -184,8 +198,8 @@ public class OrganizationWarningsQuery(
if (subscription is if (subscription is
{ {
Status: StripeConstants.SubscriptionStatus.Active, Status: SubscriptionStatus.Active,
LatestInvoice: { Status: StripeConstants.InvoiceStatus.Open, DueDate: not null } LatestInvoice: { Status: InvoiceStatus.Open, DueDate: not null }
} && subscription.LatestInvoice.DueDate > now) } && subscription.LatestInvoice.DueDate > now)
{ {
return new ResellerRenewalWarning return new ResellerRenewalWarning
@ -200,7 +214,7 @@ public class OrganizationWarningsQuery(
} }
// ReSharper disable once InvertIf // ReSharper disable once InvertIf
if (subscription.Status == StripeConstants.SubscriptionStatus.PastDue) if (subscription.Status == SubscriptionStatus.PastDue)
{ {
var openInvoices = await stripeAdapter.InvoiceSearchAsync(new InvoiceSearchOptions var openInvoices = await stripeAdapter.InvoiceSearchAsync(new InvoiceSearchOptions
{ {
@ -226,4 +240,22 @@ public class OrganizationWarningsQuery(
return null; return null;
} }
private async Task<bool> HasUnverifiedBankAccount(
Organization organization)
{
var setupIntentId = await setupIntentCache.Get(organization.Id);
if (string.IsNullOrEmpty(setupIntentId))
{
return false;
}
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
{
Expand = ["payment_method"]
});
return setupIntent.IsUnverifiedBankAccount();
}
} }

View File

@ -14,7 +14,7 @@ using NSubstitute;
using Xunit; using Xunit;
using JsonSerializer = System.Text.Json.JsonSerializer; using JsonSerializer = System.Text.Json.JsonSerializer;
namespace Bit.Core.Test.Billing.OrganizationFeatures.OrganizationLicenses; namespace Bit.Core.Test.Billing.Organizations.Commands;
[SutProviderCustomize] [SutProviderCustomize]
public class UpdateOrganizationLicenseCommandTests public class UpdateOrganizationLicenseCommandTests

View File

@ -18,7 +18,7 @@ using NSubstitute.ReturnsExtensions;
using Stripe; using Stripe;
using Xunit; using Xunit;
namespace Bit.Core.Test.Billing.OrganizationFeatures.OrganizationLicenses; namespace Bit.Core.Test.Billing.Organizations.Queries;
[SubscriptionInfoCustomize] [SubscriptionInfoCustomize]
[OrganizationLicenseCustomize] [OrganizationLicenseCustomize]

View File

@ -1,12 +1,13 @@
using Bit.Api.Billing.Queries.Organizations; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Caches;
using Bit.Core.Billing.Constants; using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Organizations.Queries;
using Bit.Core.Billing.Services; using Bit.Core.Billing.Services;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute; using NSubstitute;
@ -15,17 +16,17 @@ using Stripe;
using Stripe.TestHelpers; using Stripe.TestHelpers;
using Xunit; using Xunit;
namespace Bit.Api.Test.Billing.Queries.Organizations; namespace Bit.Core.Test.Billing.Organizations.Queries;
[SutProviderCustomize] [SutProviderCustomize]
public class OrganizationWarningsQueryTests public class GetOrganizationWarningsQueryTests
{ {
private static readonly string[] _requiredExpansions = ["customer", "latest_invoice", "test_clock"]; private static readonly string[] _requiredExpansions = ["customer", "latest_invoice", "test_clock"];
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_NoSubscription_NoWarnings( public async Task Run_NoSubscription_NoWarnings(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
sutProvider.GetDependency<ISubscriberService>() sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options => .GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
@ -46,7 +47,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_FreeTrialWarning( public async Task Run_Has_FreeTrialWarning(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -70,6 +71,7 @@ public class OrganizationWarningsQueryTests
}); });
sutProvider.GetDependency<ICurrentContext>().EditSubscription(organization.Id).Returns(true); sutProvider.GetDependency<ICurrentContext>().EditSubscription(organization.Id).Returns(true);
sutProvider.GetDependency<ISetupIntentCache>().Get(organization.Id).Returns((string?)null);
var response = await sutProvider.Sut.Run(organization); var response = await sutProvider.Sut.Run(organization);
@ -79,10 +81,90 @@ public class OrganizationWarningsQueryTests
}); });
} }
[Theory, BitAutoData]
public async Task Run_Has_FreeTrialWarning_WithUnverifiedBankAccount_NoWarning(
Organization organization,
SutProvider<GetOrganizationWarningsQuery> sutProvider)
{
var now = DateTime.UtcNow;
const string setupIntentId = "setup_intent_id";
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
options.Expand.SequenceEqual(_requiredExpansions)
))
.Returns(new Subscription
{
Status = StripeConstants.SubscriptionStatus.Trialing,
TrialEnd = now.AddDays(7),
Customer = new Customer
{
InvoiceSettings = new CustomerInvoiceSettings(),
Metadata = new Dictionary<string, string>()
},
TestClock = new TestClock
{
FrozenTime = now
}
});
sutProvider.GetDependency<ICurrentContext>().EditSubscription(organization.Id).Returns(true);
sutProvider.GetDependency<ISetupIntentCache>().Get(organization.Id).Returns(setupIntentId);
sutProvider.GetDependency<IStripeAdapter>().SetupIntentGet(setupIntentId, Arg.Is<SetupIntentGetOptions>(
options => options.Expand.Contains("payment_method"))).Returns(new SetupIntent
{
Status = "requires_action",
NextAction = new SetupIntentNextAction
{
VerifyWithMicrodeposits = new SetupIntentNextActionVerifyWithMicrodeposits()
},
PaymentMethod = new PaymentMethod
{
UsBankAccount = new PaymentMethodUsBankAccount()
}
});
var response = await sutProvider.Sut.Run(organization);
Assert.Null(response.FreeTrial);
}
[Theory, BitAutoData]
public async Task Run_Has_InactiveSubscriptionWarning_AddPaymentMethodOptionalTrial(
Organization organization,
SutProvider<GetOrganizationWarningsQuery> sutProvider)
{
organization.Enabled = true;
sutProvider.GetDependency<ISubscriberService>()
.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
options.Expand.SequenceEqual(_requiredExpansions)
))
.Returns(new Subscription
{
Status = StripeConstants.SubscriptionStatus.Trialing,
Customer = new Customer
{
InvoiceSettings = new CustomerInvoiceSettings(),
Metadata = new Dictionary<string, string>()
}
});
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ISetupIntentCache>().Get(organization.Id).Returns((string?)null);
var response = await sutProvider.Sut.Run(organization);
Assert.True(response is
{
InactiveSubscription.Resolution: "add_payment_method_optional_trial"
});
}
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_InactiveSubscriptionWarning_ContactProvider( public async Task Run_Has_InactiveSubscriptionWarning_ContactProvider(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
organization.Enabled = false; organization.Enabled = false;
@ -109,7 +191,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_InactiveSubscriptionWarning_AddPaymentMethod( public async Task Run_Has_InactiveSubscriptionWarning_AddPaymentMethod(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
organization.Enabled = false; organization.Enabled = false;
@ -135,7 +217,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_InactiveSubscriptionWarning_Resubscribe( public async Task Run_Has_InactiveSubscriptionWarning_Resubscribe(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
organization.Enabled = false; organization.Enabled = false;
@ -161,7 +243,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_InactiveSubscriptionWarning_ContactOwner( public async Task Run_Has_InactiveSubscriptionWarning_ContactOwner(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
organization.Enabled = false; organization.Enabled = false;
@ -187,7 +269,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_ResellerRenewalWarning_Upcoming( public async Task Run_Has_ResellerRenewalWarning_Upcoming(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -225,7 +307,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_ResellerRenewalWarning_Issued( public async Task Run_Has_ResellerRenewalWarning_Issued(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -269,7 +351,7 @@ public class OrganizationWarningsQueryTests
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task Run_Has_ResellerRenewalWarning_PastDue( public async Task Run_Has_ResellerRenewalWarning_PastDue(
Organization organization, Organization organization,
SutProvider<OrganizationWarningsQuery> sutProvider) SutProvider<GetOrganizationWarningsQuery> sutProvider)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;

View File

@ -12,7 +12,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
using Xunit; using Xunit;
namespace Bit.Core.Test.Billing.OrganizationFeatures.OrganizationLicenses; namespace Bit.Core.Test.Billing.Organizations.Queries;
[SutProviderCustomize] [SutProviderCustomize]
public class GetSelfHostedOrganizationLicenseQueryTests public class GetSelfHostedOrganizationLicenseQueryTests