mirror of
https://github.com/bitwarden/browser
synced 2026-02-20 19:34:03 +00:00
[PM-29087] [PM-29088] Remove FF: pm-26793-fetch-premium-price-from-pricing-service - Logic + Flag (#18946)
* refactor(billing): remove PM-26793 feature flag from subscription pricing service * test(billing): update subscription pricing tests for PM-26793 feature flag removal * chore: remove PM-26793 feature flag from keys
This commit is contained in:
@@ -231,6 +231,7 @@ describe("DefaultSubscriptionPricingService", () => {
|
||||
},
|
||||
storage: {
|
||||
price: 4,
|
||||
provided: 1,
|
||||
},
|
||||
} as PremiumPlanResponse;
|
||||
|
||||
@@ -350,7 +351,7 @@ describe("DefaultSubscriptionPricingService", () => {
|
||||
|
||||
billingApiService.getPlans.mockResolvedValue(mockPlansResponse);
|
||||
billingApiService.getPremiumPlan.mockResolvedValue(mockPremiumPlanResponse);
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false)); // Default to false (use hardcoded value)
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
setupEnvironmentService(environmentService);
|
||||
|
||||
service = new DefaultSubscriptionPricingService(
|
||||
@@ -915,7 +916,7 @@ describe("DefaultSubscriptionPricingService", () => {
|
||||
const testError = new Error("Premium plan API error");
|
||||
errorBillingApiService.getPlans.mockResolvedValue(mockPlansResponse);
|
||||
errorBillingApiService.getPremiumPlan.mockRejectedValue(testError);
|
||||
errorConfigService.getFeatureFlag$.mockReturnValue(of(true)); // Enable feature flag to use premium plan API
|
||||
errorConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
setupEnvironmentService(errorEnvironmentService);
|
||||
|
||||
const errorService = new DefaultSubscriptionPricingService(
|
||||
@@ -959,71 +960,16 @@ describe("DefaultSubscriptionPricingService", () => {
|
||||
expect(getPlansResponse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should share premium plan API response between multiple subscriptions when feature flag is enabled", () => {
|
||||
// Create a new mock to avoid conflicts with beforeEach setup
|
||||
const newBillingApiService = mock<BillingApiServiceAbstraction>();
|
||||
const newConfigService = mock<ConfigService>();
|
||||
const newEnvironmentService = mock<EnvironmentService>();
|
||||
|
||||
newBillingApiService.getPlans.mockResolvedValue(mockPlansResponse);
|
||||
newBillingApiService.getPremiumPlan.mockResolvedValue(mockPremiumPlanResponse);
|
||||
newConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
setupEnvironmentService(newEnvironmentService);
|
||||
|
||||
const getPremiumPlanSpy = jest.spyOn(newBillingApiService, "getPremiumPlan");
|
||||
|
||||
// Create a new service instance with the feature flag enabled
|
||||
const newService = new DefaultSubscriptionPricingService(
|
||||
newBillingApiService,
|
||||
newConfigService,
|
||||
i18nService,
|
||||
logService,
|
||||
newEnvironmentService,
|
||||
);
|
||||
it("should share premium plan API response between multiple subscriptions", () => {
|
||||
const getPremiumPlanSpy = jest.spyOn(billingApiService, "getPremiumPlan");
|
||||
|
||||
// Subscribe to the premium pricing tier multiple times
|
||||
newService.getPersonalSubscriptionPricingTiers$().subscribe();
|
||||
newService.getPersonalSubscriptionPricingTiers$().subscribe();
|
||||
service.getPersonalSubscriptionPricingTiers$().subscribe();
|
||||
service.getPersonalSubscriptionPricingTiers$().subscribe();
|
||||
|
||||
// API should only be called once due to shareReplay on premiumPlanResponse$
|
||||
expect(getPremiumPlanSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should use hardcoded premium price when feature flag is disabled", (done) => {
|
||||
// Create a new mock to test from scratch
|
||||
const newBillingApiService = mock<BillingApiServiceAbstraction>();
|
||||
const newConfigService = mock<ConfigService>();
|
||||
const newEnvironmentService = mock<EnvironmentService>();
|
||||
|
||||
newBillingApiService.getPlans.mockResolvedValue(mockPlansResponse);
|
||||
newBillingApiService.getPremiumPlan.mockResolvedValue({
|
||||
seat: { price: 999 }, // Different price to verify hardcoded value is used
|
||||
storage: { price: 999 },
|
||||
} as PremiumPlanResponse);
|
||||
newConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
setupEnvironmentService(newEnvironmentService);
|
||||
|
||||
// Create a new service instance with the feature flag disabled
|
||||
const newService = new DefaultSubscriptionPricingService(
|
||||
newBillingApiService,
|
||||
newConfigService,
|
||||
i18nService,
|
||||
logService,
|
||||
newEnvironmentService,
|
||||
);
|
||||
|
||||
// Subscribe with feature flag disabled
|
||||
newService.getPersonalSubscriptionPricingTiers$().subscribe((tiers) => {
|
||||
const premiumTier = tiers.find(
|
||||
(tier) => tier.id === PersonalSubscriptionPricingTierIds.Premium,
|
||||
);
|
||||
|
||||
// Should use hardcoded value of 10, not the API response value of 999
|
||||
expect(premiumTier!.passwordManager.annualPrice).toBe(10);
|
||||
expect(premiumTier!.passwordManager.annualPricePerAdditionalStorageGB).toBe(4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Self-hosted environment behavior", () => {
|
||||
@@ -1035,7 +981,7 @@ describe("DefaultSubscriptionPricingService", () => {
|
||||
const getPlansSpy = jest.spyOn(selfHostedBillingApiService, "getPlans");
|
||||
const getPremiumPlanSpy = jest.spyOn(selfHostedBillingApiService, "getPremiumPlan");
|
||||
|
||||
selfHostedConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
selfHostedConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
setupEnvironmentService(selfHostedEnvironmentService, Region.SelfHosted);
|
||||
|
||||
const selfHostedService = new DefaultSubscriptionPricingService(
|
||||
@@ -1061,7 +1007,7 @@ describe("DefaultSubscriptionPricingService", () => {
|
||||
const selfHostedConfigService = mock<ConfigService>();
|
||||
const selfHostedEnvironmentService = mock<EnvironmentService>();
|
||||
|
||||
selfHostedConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
selfHostedConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
setupEnvironmentService(selfHostedEnvironmentService, Region.SelfHosted);
|
||||
|
||||
const selfHostedService = new DefaultSubscriptionPricingService(
|
||||
|
||||
@@ -33,16 +33,6 @@ import {
|
||||
} from "../types/subscription-pricing-tier";
|
||||
|
||||
export class DefaultSubscriptionPricingService implements SubscriptionPricingServiceAbstraction {
|
||||
/**
|
||||
* Fallback premium pricing used when the feature flag is disabled.
|
||||
* These values represent the legacy pricing model and will not reflect
|
||||
* server-side price changes. They are retained for backward compatibility
|
||||
* during the feature flag rollout period.
|
||||
*/
|
||||
private static readonly FALLBACK_PREMIUM_SEAT_PRICE = 10;
|
||||
private static readonly FALLBACK_PREMIUM_STORAGE_PRICE = 4;
|
||||
private static readonly FALLBACK_PREMIUM_PROVIDED_STORAGE_GB = 1;
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
@@ -123,45 +113,27 @@ export class DefaultSubscriptionPricingService implements SubscriptionPricingSer
|
||||
shareReplay({ bufferSize: 1, refCount: false }),
|
||||
);
|
||||
|
||||
private premium$: Observable<PersonalSubscriptionPricingTier> = this.configService
|
||||
.getFeatureFlag$(FeatureFlag.PM26793_FetchPremiumPriceFromPricingService)
|
||||
.pipe(
|
||||
take(1), // Lock behavior at first subscription to prevent switching data sources mid-stream
|
||||
switchMap((fetchPremiumFromPricingService) =>
|
||||
fetchPremiumFromPricingService
|
||||
? this.premiumPlanResponse$.pipe(
|
||||
map((premiumPlan) => ({
|
||||
seat: premiumPlan.seat?.price,
|
||||
storage: premiumPlan.storage?.price,
|
||||
provided: premiumPlan.storage?.provided,
|
||||
})),
|
||||
)
|
||||
: of({
|
||||
seat: DefaultSubscriptionPricingService.FALLBACK_PREMIUM_SEAT_PRICE,
|
||||
storage: DefaultSubscriptionPricingService.FALLBACK_PREMIUM_STORAGE_PRICE,
|
||||
provided: DefaultSubscriptionPricingService.FALLBACK_PREMIUM_PROVIDED_STORAGE_GB,
|
||||
}),
|
||||
),
|
||||
map((premiumPrices) => ({
|
||||
id: PersonalSubscriptionPricingTierIds.Premium,
|
||||
name: this.i18nService.t("premium"),
|
||||
description: this.i18nService.t("advancedOnlineSecurity"),
|
||||
availableCadences: [SubscriptionCadenceIds.Annually],
|
||||
passwordManager: {
|
||||
type: "standalone",
|
||||
annualPrice: premiumPrices.seat,
|
||||
annualPricePerAdditionalStorageGB: premiumPrices.storage,
|
||||
providedStorageGB: premiumPrices.provided,
|
||||
features: [
|
||||
this.featureTranslations.builtInAuthenticator(),
|
||||
this.featureTranslations.secureFileStorage(),
|
||||
this.featureTranslations.emergencyAccess(),
|
||||
this.featureTranslations.breachMonitoring(),
|
||||
this.featureTranslations.andMoreFeatures(),
|
||||
],
|
||||
},
|
||||
})),
|
||||
);
|
||||
private premium$: Observable<PersonalSubscriptionPricingTier> = this.premiumPlanResponse$.pipe(
|
||||
map((premiumPlan) => ({
|
||||
id: PersonalSubscriptionPricingTierIds.Premium,
|
||||
name: this.i18nService.t("premium"),
|
||||
description: this.i18nService.t("advancedOnlineSecurity"),
|
||||
availableCadences: [SubscriptionCadenceIds.Annually],
|
||||
passwordManager: {
|
||||
type: "standalone",
|
||||
annualPrice: premiumPlan.seat?.price,
|
||||
annualPricePerAdditionalStorageGB: premiumPlan.storage?.price,
|
||||
providedStorageGB: premiumPlan.storage?.provided,
|
||||
features: [
|
||||
this.featureTranslations.builtInAuthenticator(),
|
||||
this.featureTranslations.secureFileStorage(),
|
||||
this.featureTranslations.emergencyAccess(),
|
||||
this.featureTranslations.breachMonitoring(),
|
||||
this.featureTranslations.andMoreFeatures(),
|
||||
],
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
private families$: Observable<PersonalSubscriptionPricingTier> =
|
||||
this.organizationPlansResponse$.pipe(
|
||||
|
||||
@@ -31,7 +31,6 @@ export enum FeatureFlag {
|
||||
/* Billing */
|
||||
TrialPaymentOptional = "PM-8163-trial-payment",
|
||||
PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button",
|
||||
PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service",
|
||||
PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog",
|
||||
PM26462_Milestone_3 = "pm-26462-milestone-3",
|
||||
PM23341_Milestone_2 = "pm-23341-milestone-2",
|
||||
@@ -146,7 +145,6 @@ export const DefaultFeatureFlagValue = {
|
||||
/* Billing */
|
||||
[FeatureFlag.TrialPaymentOptional]: FALSE,
|
||||
[FeatureFlag.PM24032_NewNavigationPremiumUpgradeButton]: FALSE,
|
||||
[FeatureFlag.PM26793_FetchPremiumPriceFromPricingService]: FALSE,
|
||||
[FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog]: FALSE,
|
||||
[FeatureFlag.PM26462_Milestone_3]: FALSE,
|
||||
[FeatureFlag.PM23341_Milestone_2]: FALSE,
|
||||
|
||||
Reference in New Issue
Block a user