From ade30877e6e648086f88d5fd2aba20eabf6c21e3 Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 9 Oct 2025 08:28:57 +0530 Subject: [PATCH 1/3] chore: we now seperate isSubscriber and paidSubscriber in entitlments earlier, we always set paidSubscriber to true even when they were trial users, now its more clear to directly use paidSubscriber for only paid users and isSubscriber for general users in trial/paid --- .../new-project/assets/js/code-editor.js | 2 +- src/index.html | 6 +- src/services/EntitlementsManager.js | 8 +- src/services/login-service.js | 24 +++--- src/services/login-utils.js | 5 +- src/services/profile-menu.js | 14 ++-- src/services/promotions.js | 2 +- test/spec/login-browser-integ-test.js | 4 +- test/spec/login-desktop-integ-test.js | 4 +- test/spec/login-shared.js | 28 +++---- test/spec/login-utils-test.js | 74 +++++++++---------- test/spec/promotions-integ-test.js | 16 ++-- 12 files changed, 100 insertions(+), 87 deletions(-) diff --git a/src/assets/new-project/assets/js/code-editor.js b/src/assets/new-project/assets/js/code-editor.js index 22de25f873..77cb07768b 100644 --- a/src/assets/new-project/assets/js/code-editor.js +++ b/src/assets/new-project/assets/js/code-editor.js @@ -197,7 +197,7 @@ function _updateProBranding() { // Get plan info from window.top.Phoenix.pro.plan const planInfo = window.top.Phoenix && window.top.Phoenix.pro && window.top.Phoenix.pro.plan; - if (planInfo && planInfo.paidSubscriber) { + if (planInfo && planInfo.isSubscriber) { // Hide free title, show pro title $freeTitle.addClass('forced-hidden'); $proTitle.removeClass('forced-hidden'); diff --git a/src/index.html b/src/index.html index 830fa6a5e5..24d37ffa98 100644 --- a/src/index.html +++ b/src/index.html @@ -447,8 +447,10 @@ } async function _startRequireLoop() { - // If running on localhost, fetch proxy config to get dynamic account URL - if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + // If running on localhost, `npm run serve`, `npm run serveLocalAccount` targets puts in a fetch proxy. + // tTe proxy helps work around cookie domain set by phcode.dev as the dev urls are localhost. so to use + // either the actual services endpoint or localhost endpoints in dev, this is needed. + if (!Phoenix.isTestWindow && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) { try { const response = await fetch('/proxy/config'); if (response.ok) { diff --git a/src/services/EntitlementsManager.js b/src/services/EntitlementsManager.js index e5b9acfacc..fb5c9d512c 100644 --- a/src/services/EntitlementsManager.js +++ b/src/services/EntitlementsManager.js @@ -87,8 +87,9 @@ define(function (require, exports, module) { /** * Get the plan details from entitlements with fallback to free plan defaults. If the user is - * in pro trial(isInProTrial API), then paidSubscriber will always be true as we need to treat user as paid. - * you should use isInProTrial API to check if user is in pro trial if some trial-related logic needs to be done. + * in pro trial(isInProTrial API), then isSubscriber will always be true as we need to treat + * user as subcriber. you should use isInProTrial API to check if user is in pro trial if some + * trial-related logic needs to be done. * @returns {Promise} Plan details object */ async function getPlanDetails() { @@ -101,6 +102,7 @@ define(function (require, exports, module) { // Fallback to free plan defaults const currentDate = Date.now(); return { + isSubscriber: false, paidSubscriber: false, name: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE, validTill: currentDate + (FREE_PLAN_VALIDITY_DAYS * MS_IN_DAY) @@ -108,7 +110,7 @@ define(function (require, exports, module) { } /** - * Check if user is in a pro trial. IF the user is in pro trail, then `plan.paidSubscriber` will always be true. + * Check if user is in a pro trial. IF the user is in pro trail, then `plan.isSubscriber` will always be true. * @returns {Promise} True if user is in pro trial, false otherwise */ async function isInProTrial() { diff --git a/src/services/login-service.js b/src/services/login-service.js index 63da795327..f58757f498 100644 --- a/src/services/login-service.js +++ b/src/services/login-service.js @@ -461,6 +461,7 @@ define(function (require, exports, module) { if(entitlements.plan && (!entitlements.plan.validTill || currentDate > entitlements.plan.validTill)) { entitlements.plan = { ...entitlements.plan, + isSubscriber: false, paidSubscriber: false, name: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE, fullName: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE, @@ -498,7 +499,8 @@ define(function (require, exports, module) { * ```javascript * { * plan: { - * paidSubscriber: true, // Always true for trial users + * isSubscriber: true, // Always true for trial users + * paidSubscriber: false, // if the user is a paid for the plan, or is it an unpaid promo * name: "Phoenix Pro" * fullName: "Phoenix Pro" // this can be deceptive name like "Phoenix Pro For Education" to use in * // profile popup, not main branding @@ -514,11 +516,12 @@ define(function (require, exports, module) { * ``` * * **For logged-in trial users:** - * - If remote response has `plan.paidSubscriber: false`, injects `paidSubscriber: true` + * - If remote response has `plan.isSubscriber: false`, injects `isSubscriber: true` * - Adds `isInProTrial: true` and `trialDaysRemaining` * - Injects `entitlements.liveEdit.activated: true` - * - Note: Trial users may not be actual paid subscribers, but `paidSubscriber: true` is set - * so all Phoenix code treats them as paid subscribers + * - Note: Trial users may not be actual paid subscribers, but `isSubscriber: true` is set + * so all Phoenix code treats them as subscribers. to check if they actually paid or not, use + * `paidSubscriber` field. * * **For logged-in users (full remote response):** * ```javascript @@ -529,6 +532,7 @@ define(function (require, exports, module) { * name: "Phoenix Pro", * fullName: "Phoenix Pro" // this can be deceptive name like "Phoenix Pro For Education" to use in * // profile popup, not main branding + * isSubscriber: boolean, * paidSubscriber: boolean, * validTill: number // Timestamp * }, @@ -574,7 +578,7 @@ define(function (require, exports, module) { * * // Get current entitlements * const entitlements = await LoginService.getEffectiveEntitlements(); - * if (entitlements?.plan?.paidSubscriber) { + * if (entitlements?.plan?.isSubscriber) { * // Enable pro features * } * if (entitlements?.entitlements?.liveEdit?.activated) { @@ -597,8 +601,8 @@ define(function (require, exports, module) { // now we need to grant trial, as user is entitled to trial if he is here. // User has active server plan(either with login or device license) if (serverEntitlements && serverEntitlements.plan) { - if (serverEntitlements.plan.paidSubscriber) { - // Already a paid subscriber(or has device license), return as-is + if (serverEntitlements.plan.isSubscriber) { + // Already a subscriber(or has device license), return as-is // never inject trail data in this case. return serverEntitlements; } @@ -608,7 +612,8 @@ define(function (require, exports, module) { ...serverEntitlements, plan: { ...serverEntitlements.plan, - paidSubscriber: true, + isSubscriber: true, + paidSubscriber: serverEntitlements.plan.paidSubscriber || false, name: brackets.config.main_pro_plan, fullName: brackets.config.main_pro_plan, validTill: dateNowFn() + trialDaysRemaining * MS_IN_DAY @@ -632,7 +637,8 @@ define(function (require, exports, module) { // Non-logged-in, non licensed user with trial - return synthetic entitlements return { plan: { - paidSubscriber: true, + isSubscriber: true, + paidSubscriber: false, name: brackets.config.main_pro_plan, fullName: brackets.config.main_pro_plan, validTill: dateNowFn() + trialDaysRemaining * MS_IN_DAY diff --git a/src/services/login-utils.js b/src/services/login-utils.js index cc6d0d02ab..3eec9b39cf 100644 --- a/src/services/login-utils.js +++ b/src/services/login-utils.js @@ -110,7 +110,10 @@ define(function (require, exports, module) { // Check paidSubscriber changes const currentPaidSub = current.plan && current.plan.paidSubscriber; const lastPaidSub = last.plan && last.plan.paidSubscriber; - if (currentPaidSub !== lastPaidSub) { + // Check isSubscriber changes + const currentIsSubscriber = current.plan && current.plan.isSubscriber; + const lastIsSubscriber = last.plan && last.plan.isSubscriber; + if (currentIsSubscriber !== lastIsSubscriber || currentPaidSub !== lastPaidSub) { return true; } diff --git a/src/services/profile-menu.js b/src/services/profile-menu.js index cbc4218327..36b3f50168 100644 --- a/src/services/profile-menu.js +++ b/src/services/profile-menu.js @@ -179,7 +179,7 @@ define(function (require, exports, module) { `; - } else if (effectiveEntitlements.plan && effectiveEntitlements.plan.paidSubscriber) { + } else if (effectiveEntitlements.plan && effectiveEntitlements.plan.isSubscriber) { // Device-licensed user: show Phoenix Pro branding const planName = effectiveEntitlements.plan.fullName || brackets.config.main_pro_plan; proInfoHtml = `
@@ -234,7 +234,7 @@ define(function (require, exports, module) { // Phoenix.pro is only for display purposes and should not be used to gate features. // Use kernal mode apis for trusted check of pro features. Phoenix.pro.plan = { - paidSubscriber: false, + isSubscriber: false, name: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE, fullName: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }; @@ -242,13 +242,13 @@ define(function (require, exports, module) { if (entitlements && entitlements.plan){ Phoenix.pro.plan = { - paidSubscriber: entitlements.plan.paidSubscriber, + isSubscriber: entitlements.plan.isSubscriber, name: entitlements.plan.name, fullName: entitlements.plan.fullName, validTill: entitlements.plan.validTill }; } - if (entitlements && entitlements.plan && entitlements.plan.paidSubscriber) { + if (entitlements && entitlements.plan && entitlements.plan.isSubscriber) { // Pro user (paid subscriber or trial): show short name branding with `name feather icon`(not full name) let displayName = entitlements.plan.name || brackets.config.main_pro_plan; if (entitlements.isInProTrial) { @@ -384,7 +384,7 @@ define(function (require, exports, module) { // Update plan class and content based on paid subscriber status $planName.removeClass('user-plan-free user-plan-paid'); - if (entitlements.plan.paidSubscriber) { + if (entitlements.plan.isSubscriber) { // Use pro styling with feather icon for pro users (paid or trial) if (entitlements.isInProTrial) { // For trial users: separate "Phoenix Pro" with icon from "(X days left)" text @@ -596,7 +596,7 @@ define(function (require, exports, module) { const effectiveEntitlements = await KernalModeTrust.loginService.getEffectiveEntitlements(); return effectiveEntitlements && (effectiveEntitlements.isInProTrial || - (effectiveEntitlements.plan && effectiveEntitlements.plan.paidSubscriber)); + (effectiveEntitlements.plan && effectiveEntitlements.plan.isSubscriber)); } catch (error) { console.error('Failed to check Pro access status:', error); return false; @@ -611,7 +611,7 @@ define(function (require, exports, module) { const effectiveEntitlements = await KernalModeTrust.loginService.getEffectiveEntitlements(); if (effectiveEntitlements && (effectiveEntitlements.isInProTrial || - (effectiveEntitlements.plan && effectiveEntitlements.plan.paidSubscriber))) { + (effectiveEntitlements.plan && effectiveEntitlements.plan.isSubscriber))) { console.log('Profile Menu: Found Pro entitlements (trial or device license), updating branding...'); _updateBranding(effectiveEntitlements); } else { diff --git a/src/services/promotions.js b/src/services/promotions.js index 193536fb18..ff8de4c9ea 100644 --- a/src/services/promotions.js +++ b/src/services/promotions.js @@ -213,7 +213,7 @@ define(function (require, exports, module) { // getEntitlements() returns null if not logged in const entitlements = await LoginService.getEntitlements(); - return entitlements && entitlements.plan && entitlements.plan.paidSubscriber === true; + return entitlements && entitlements.plan && entitlements.plan.isSubscriber === true; } catch (error) { console.error("Error checking pro subscription:", error); return false; diff --git a/test/spec/login-browser-integ-test.js b/test/spec/login-browser-integ-test.js index 1e48af61d7..8a807a2aa0 100644 --- a/test/spec/login-browser-integ-test.js +++ b/test/spec/login-browser-integ-test.js @@ -183,7 +183,7 @@ define(function (require, exports, module) { Date.now() + 30 * 24 * 60 * 60 * 1000; // valid for 30 days entitlementsResponse.plan = { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", fullName: "Phoenix Pro", validTill: validTill @@ -196,7 +196,7 @@ define(function (require, exports, module) { }; } else { entitlementsResponse.plan = { - paidSubscriber: false, + isSubscriber: false, name: "Free Plan", fullName: "Free Plan" }; diff --git a/test/spec/login-desktop-integ-test.js b/test/spec/login-desktop-integ-test.js index 0e46330ddf..fadbcd22c4 100644 --- a/test/spec/login-desktop-integ-test.js +++ b/test/spec/login-desktop-integ-test.js @@ -209,7 +209,7 @@ define(function (require, exports, module) { Date.now() + 30 * 24 * 60 * 60 * 1000; // valid for 30 days entitlementsResponse.plan = { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", fullName: isDeviceIDRequest ? "Phoenix Pro Test Edu" : "Phoenix Pro", validTill: validTill @@ -222,7 +222,7 @@ define(function (require, exports, module) { }; } else { entitlementsResponse.plan = { - paidSubscriber: false, + isSubscriber: false, name: "Free Plan", fullName: "Free Plan" }; diff --git a/test/spec/login-shared.js b/test/spec/login-shared.js index ae67d8f077..a49d2c6ac2 100644 --- a/test/spec/login-shared.js +++ b/test/spec/login-shared.js @@ -188,8 +188,8 @@ define(function (require, exports, module) { } // Check all expected properties match - if (expectedPlan.paidSubscriber !== undefined && - planDetails.paidSubscriber !== expectedPlan.paidSubscriber) { + if (expectedPlan.isSubscriber !== undefined && + planDetails.isSubscriber !== expectedPlan.isSubscriber) { return false; } if (expectedPlan.name && planDetails.name !== expectedPlan.name) { @@ -213,8 +213,8 @@ define(function (require, exports, module) { const finalPlanDetails = await EntitlementsExports.getPlanDetails(); if (expectedPlan) { expect(finalPlanDetails).toBeDefined(); - if (expectedPlan.paidSubscriber !== undefined) { - expect(finalPlanDetails.paidSubscriber).toBe(expectedPlan.paidSubscriber); + if (expectedPlan.isSubscriber !== undefined) { + expect(finalPlanDetails.isSubscriber).toBe(expectedPlan.isSubscriber); } if (expectedPlan.name) { expect(finalPlanDetails.name).toBe(expectedPlan.name); @@ -399,7 +399,7 @@ define(function (require, exports, module) { // Verify entitlements API consistency for logged in pro user await verifyIsInProTrialEntitlement(false, "pro user should not be in trial"); - await verifyPlanEntitlements({ paidSubscriber: true }, "pro user should have paid subscriber plan"); + await verifyPlanEntitlements({ isSubscriber: true }, "pro user should have paid subscriber plan"); // Check profile popup shows pro status (not trial) const $profileButton = testWindow.$("#user-profile-button"); @@ -448,7 +448,7 @@ define(function (require, exports, module) { // Verify entitlements API consistency for logged in trial user await verifyIsInProTrialEntitlement(true, "user should still be in trial after login"); - await verifyPlanEntitlements({ paidSubscriber: true }, "trial user should have paidSubscriber true"); + await verifyPlanEntitlements({ isSubscriber: true }, "trial user should have isSubscriber true"); // Check profile popup shows trial status const $profileButton = testWindow.$("#user-profile-button"); @@ -530,7 +530,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "no pro branding to start with"); // Verify entitlements API consistency for logged out user with expired trial - await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, + await verifyPlanEntitlements({ isSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, "free plan for logged out user with expired trial"); await verifyIsInProTrialEntitlement(false, "no trial for user with expired trial"); await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining for expired trial"); @@ -543,7 +543,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "after trial free user login"); // Verify entitlements API consistency for logged in free user - await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, + await verifyPlanEntitlements({ isSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, "free plan for logged in user with expired trial"); await verifyIsInProTrialEntitlement(false, "still no trial after login"); await verifyLiveEditEntitlement({ activated: false }, "live edit still deactivated after login"); @@ -589,7 +589,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "no pro branding initially due to expired entitlements"); // Verify entitlements API consistency for logged out user with no trial - await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, + await verifyPlanEntitlements({ isSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, "free plan for logged out user with no trial"); await verifyIsInProTrialEntitlement(false, "no trial for logged out user"); await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining"); @@ -603,7 +603,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "no pro branding after login with expired entitlements"); // Verify entitlements API consistency for logged in user with expired entitlements - await verifyPlanEntitlements({ paidSubscriber: false }, + await verifyPlanEntitlements({ isSubscriber: false }, "expired entitlements filtered to free plan after login"); await verifyIsInProTrialEntitlement(false, "no trial for user with expired entitlements"); await verifyTrialRemainingDaysEntitlement(0, "no trial days for expired entitlements user"); @@ -654,7 +654,7 @@ define(function (require, exports, module) { await verifyProBranding(true, "pro branding initially due to active trial"); // Verify entitlements API consistency for logged out user with active trial - await verifyPlanEntitlements({ paidSubscriber: true, name: testWindow.brackets.config.main_pro_plan }, + await verifyPlanEntitlements({ isSubscriber: true, name: testWindow.brackets.config.main_pro_plan }, "trial plan for logged out user overrides expired entitlements"); await verifyIsInProTrialEntitlement(true, "user should be in trial initially"); await verifyTrialRemainingDaysEntitlement(10, "should have 10 trial days remaining"); @@ -668,7 +668,7 @@ define(function (require, exports, module) { await verifyProBranding(true, "pro branding after login - trial overrides expired entitlements"); // Verify entitlements API consistency for logged in user (trial overrides expired server entitlements) - await verifyPlanEntitlements({ paidSubscriber: true }, + await verifyPlanEntitlements({ isSubscriber: true }, "trial overrides expired server entitlements to show paid subscriber"); await verifyIsInProTrialEntitlement(true, "user should still be in trial after login"); await verifyTrialRemainingDaysEntitlement(10, "trial days should remain 10 after login"); @@ -787,7 +787,7 @@ define(function (require, exports, module) { // Verify entitlements API shows Pro access await verifyPlanEntitlements( - { paidSubscriber: true, name: "Phoenix Pro" }, + { isSubscriber: true, name: "Phoenix Pro" }, "device license provides Pro plan" ); await verifyIsInProTrialEntitlement(false, "device license is not a trial"); @@ -797,7 +797,7 @@ define(function (require, exports, module) { const rawEntitlements = await EntitlementsExports.getRawEntitlements(); expect(rawEntitlements).toBeDefined(); expect(rawEntitlements).not.toBeNull(); - expect(rawEntitlements.plan.paidSubscriber).toBe(true); + expect(rawEntitlements.plan.isSubscriber).toBe(true); // Verify login popup shows Pro branding with fullName const $profileButton = testWindow.$("#user-profile-button"); diff --git a/test/spec/login-utils-test.js b/test/spec/login-utils-test.js index 895a9ac168..31b1f5ba4b 100644 --- a/test/spec/login-utils-test.js +++ b/test/spec/login-utils-test.js @@ -51,7 +51,7 @@ define(function (require, exports, module) { it("should return null when no validTill times exist", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true } } @@ -62,7 +62,7 @@ define(function (require, exports, module) { it("should return null when validTill times are in the future", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: futureTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: futureTime }, entitlements: { liveEdit: { activated: true, validTill: futureTime } } @@ -73,11 +73,11 @@ define(function (require, exports, module) { it("should return plan name when plan validTill is newly expired", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: pastTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: pastTime }, entitlements: {} }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: futureTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: futureTime }, entitlements: {} }; const result = LoginUtils.validTillExpired(entitlements, lastRecorded); @@ -86,11 +86,11 @@ define(function (require, exports, module) { it("should return default plan name when plan validTill is newly expired and no name", function () { const entitlements = { - plan: { paidSubscriber: true, validTill: pastTime }, + plan: { isSubscriber: true, validTill: pastTime }, entitlements: {} }; const lastRecorded = { - plan: { paidSubscriber: true, validTill: futureTime }, + plan: { isSubscriber: true, validTill: futureTime }, entitlements: {} }; const result = LoginUtils.validTillExpired(entitlements, lastRecorded); @@ -99,11 +99,11 @@ define(function (require, exports, module) { it("should return null when plan validTill was already expired", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: pastTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: pastTime }, entitlements: {} }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: recentPastTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: recentPastTime }, entitlements: {} }; const result = LoginUtils.validTillExpired(entitlements, lastRecorded); @@ -112,14 +112,14 @@ define(function (require, exports, module) { it("should return entitlement key when entitlement validTill is newly expired", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true, validTill: pastTime }, liveEditAI: { activated: true, validTill: futureTime } } }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true, validTill: futureTime }, liveEditAI: { activated: true, validTill: futureTime } @@ -131,13 +131,13 @@ define(function (require, exports, module) { it("should return null when entitlement validTill was already expired", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true, validTill: pastTime } } }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true, validTill: recentPastTime } } @@ -148,7 +148,7 @@ define(function (require, exports, module) { it("should handle missing entitlements in lastRecorded", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: pastTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: pastTime }, entitlements: { liveEdit: { activated: true, validTill: pastTime } } @@ -159,14 +159,14 @@ define(function (require, exports, module) { it("should skip null entitlements in loop", function () { const entitlements = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: null, liveEditAI: { activated: true, validTill: pastTime } } }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: null, liveEditAI: { activated: true, validTill: futureTime } @@ -182,7 +182,7 @@ define(function (require, exports, module) { lang: "en", plan: { name: "Phoenix Pro", - paidSubscriber: true, + isSubscriber: true, validTill: pastTime }, profileview: { @@ -210,7 +210,7 @@ define(function (require, exports, module) { } }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: futureTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: futureTime }, entitlements: { liveEdit: { activated: false, validTill: futureTime }, liveEditAI: { activated: false, validTill: futureTime } @@ -225,7 +225,7 @@ define(function (require, exports, module) { isSuccess: true, plan: { name: "Phoenix Pro", - paidSubscriber: true, + isSubscriber: true, validTill: pastTime }, isInProTrial: true, @@ -240,7 +240,7 @@ define(function (require, exports, module) { } }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: futureTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: futureTime }, entitlements: { liveEdit: { activated: true, validTill: futureTime } } @@ -252,7 +252,7 @@ define(function (require, exports, module) { it("should test synthetic trial shape", function () { const syntheticTrial = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: pastTime }, @@ -268,7 +268,7 @@ define(function (require, exports, module) { } }; const lastRecorded = { - plan: { name: "Phoenix Pro", paidSubscriber: true, validTill: futureTime }, + plan: { name: "Phoenix Pro", isSubscriber: true, validTill: futureTime }, entitlements: { liveEdit: { activated: true, validTill: futureTime } } @@ -326,13 +326,13 @@ define(function (require, exports, module) { expect(result).toBe(true); }); - it("should return true when paidSubscriber status changes", function () { + it("should return true when isSubscriber status changes", function () { const current = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: {} }; const last = { - plan: { name: "Phoenix Pro", paidSubscriber: false }, + plan: { name: "Phoenix Pro", isSubscriber: false }, entitlements: {} }; const result = LoginUtils.haveEntitlementsChanged(current, last); @@ -341,11 +341,11 @@ define(function (require, exports, module) { it("should return true when plan name changes", function () { const current = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: {} }; const last = { - plan: { name: "Phoenix Basic", paidSubscriber: true }, + plan: { name: "Phoenix Basic", isSubscriber: true }, entitlements: {} }; const result = LoginUtils.haveEntitlementsChanged(current, last); @@ -354,14 +354,14 @@ define(function (require, exports, module) { it("should return true when entitlement activation changes", function () { const current = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true }, liveEditAI: { activated: false } } }; const last = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: false }, liveEditAI: { activated: false } @@ -373,14 +373,14 @@ define(function (require, exports, module) { it("should return false when nothing has changed", function () { const current = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true }, liveEditAI: { activated: false } } }; const last = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true }, liveEditAI: { activated: false } @@ -403,14 +403,14 @@ define(function (require, exports, module) { it("should handle missing entitlement objects", function () { const current = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true }, liveEditAI: null } }; const last = { - plan: { name: "Phoenix Pro", paidSubscriber: true }, + plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: { liveEdit: { activated: true }, liveEditAI: null @@ -426,7 +426,7 @@ define(function (require, exports, module) { lang: "en", plan: { name: "Phoenix Pro", - paidSubscriber: true, + isSubscriber: true, validTill: 1756625665847 }, entitlements: { @@ -439,7 +439,7 @@ define(function (require, exports, module) { lang: "en", plan: { name: "Phoenix Pro", - paidSubscriber: true, + isSubscriber: true, validTill: 1756625665847 }, entitlements: { @@ -455,7 +455,7 @@ define(function (require, exports, module) { const trialCurrent = { plan: { name: "Phoenix Pro", - paidSubscriber: true, + isSubscriber: true, validTill: 1756625665847 }, isInProTrial: true, @@ -467,7 +467,7 @@ define(function (require, exports, module) { const trialLast = { plan: { name: "Phoenix Pro", - paidSubscriber: false, + isSubscriber: false, validTill: 1756625665847 }, entitlements: { @@ -481,7 +481,7 @@ define(function (require, exports, module) { it("should test synthetic trial shape changes", function () { const syntheticCurrent = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: 1756625665847 }, diff --git a/test/spec/promotions-integ-test.js b/test/spec/promotions-integ-test.js index aae8608148..b2dbb6ac86 100644 --- a/test/spec/promotions-integ-test.js +++ b/test/spec/promotions-integ-test.js @@ -808,7 +808,7 @@ define(function (require, exports, module) { // Test expired plan gets reset to free plan const expiredPlanEntitlements = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: mockNow - 86400000 // 1 day ago }, @@ -817,14 +817,14 @@ define(function (require, exports, module) { LoginServiceExports._validateAndFilterEntitlements(expiredPlanEntitlements); - expect(expiredPlanEntitlements.plan.paidSubscriber).toBe(false); + expect(expiredPlanEntitlements.plan.isSubscriber).toBe(false); expect(expiredPlanEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE); expect(expiredPlanEntitlements.plan.validTill).toBeGreaterThan(mockNow); // Test valid plan remains unchanged const validPlanEntitlements = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: mockNow + 86400000 // 1 day from now }, @@ -839,7 +839,7 @@ define(function (require, exports, module) { // Test missing validTill gets reset const noValidTillEntitlements = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro" }, entitlements: {} @@ -847,7 +847,7 @@ define(function (require, exports, module) { LoginServiceExports._validateAndFilterEntitlements(noValidTillEntitlements); - expect(noValidTillEntitlements.plan.paidSubscriber).toBe(false); + expect(noValidTillEntitlements.plan.isSubscriber).toBe(false); expect(noValidTillEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE); }); @@ -855,7 +855,7 @@ define(function (require, exports, module) { // Test expired features get deactivated const entitlementsWithExpiredFeatures = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: mockNow + 86400000 }, @@ -884,7 +884,7 @@ define(function (require, exports, module) { // Test features without validTill get deactivated (treated as expired) const entitlementsNoValidTill = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: mockNow + 86400000 }, @@ -932,7 +932,7 @@ define(function (require, exports, module) { // Test entitlements without entitlements object const noEntitlementsObj = { plan: { - paidSubscriber: true, + isSubscriber: true, name: "Phoenix Pro", validTill: mockNow + 86400000 } From 02a19fe970254ea630843c237cbcdc0b90321526 Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 9 Oct 2025 09:52:54 +0530 Subject: [PATCH 2/3] test: entitlment tests with paidSubscriber --- test/spec/login-browser-integ-test.js | 2 ++ test/spec/login-desktop-integ-test.js | 2 ++ test/spec/login-shared.js | 27 +++++++++++++--------- test/spec/login-utils-test.js | 32 +++++++++++++++++++++++++-- test/spec/promotions-integ-test.js | 2 ++ 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/test/spec/login-browser-integ-test.js b/test/spec/login-browser-integ-test.js index 8a807a2aa0..58faf469de 100644 --- a/test/spec/login-browser-integ-test.js +++ b/test/spec/login-browser-integ-test.js @@ -184,6 +184,7 @@ define(function (require, exports, module) { entitlementsResponse.plan = { isSubscriber: true, + paidSubscriber: true, name: "Phoenix Pro", fullName: "Phoenix Pro", validTill: validTill @@ -197,6 +198,7 @@ define(function (require, exports, module) { } else { entitlementsResponse.plan = { isSubscriber: false, + paidSubscriber: false, name: "Free Plan", fullName: "Free Plan" }; diff --git a/test/spec/login-desktop-integ-test.js b/test/spec/login-desktop-integ-test.js index fadbcd22c4..b578720187 100644 --- a/test/spec/login-desktop-integ-test.js +++ b/test/spec/login-desktop-integ-test.js @@ -210,6 +210,7 @@ define(function (require, exports, module) { entitlementsResponse.plan = { isSubscriber: true, + paidSubscriber: !isDeviceIDRequest, // Educational device licenses are unpaid name: "Phoenix Pro", fullName: isDeviceIDRequest ? "Phoenix Pro Test Edu" : "Phoenix Pro", validTill: validTill @@ -223,6 +224,7 @@ define(function (require, exports, module) { } else { entitlementsResponse.plan = { isSubscriber: false, + paidSubscriber: false, name: "Free Plan", fullName: "Free Plan" }; diff --git a/test/spec/login-shared.js b/test/spec/login-shared.js index a49d2c6ac2..4f246f6122 100644 --- a/test/spec/login-shared.js +++ b/test/spec/login-shared.js @@ -216,6 +216,9 @@ define(function (require, exports, module) { if (expectedPlan.isSubscriber !== undefined) { expect(finalPlanDetails.isSubscriber).toBe(expectedPlan.isSubscriber); } + if (expectedPlan.paidSubscriber !== undefined) { + expect(finalPlanDetails.paidSubscriber).toBe(expectedPlan.paidSubscriber); + } if (expectedPlan.name) { expect(finalPlanDetails.name).toBe(expectedPlan.name); } @@ -399,7 +402,7 @@ define(function (require, exports, module) { // Verify entitlements API consistency for logged in pro user await verifyIsInProTrialEntitlement(false, "pro user should not be in trial"); - await verifyPlanEntitlements({ isSubscriber: true }, "pro user should have paid subscriber plan"); + await verifyPlanEntitlements({ isSubscriber: true, paidSubscriber: true }, "pro user should have paid subscriber plan"); // Check profile popup shows pro status (not trial) const $profileButton = testWindow.$("#user-profile-button"); @@ -448,7 +451,7 @@ define(function (require, exports, module) { // Verify entitlements API consistency for logged in trial user await verifyIsInProTrialEntitlement(true, "user should still be in trial after login"); - await verifyPlanEntitlements({ isSubscriber: true }, "trial user should have isSubscriber true"); + await verifyPlanEntitlements({ isSubscriber: true, paidSubscriber: false }, "trial user should have isSubscriber true but paidSubscriber false"); // Check profile popup shows trial status const $profileButton = testWindow.$("#user-profile-button"); @@ -530,7 +533,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "no pro branding to start with"); // Verify entitlements API consistency for logged out user with expired trial - await verifyPlanEntitlements({ isSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, + await verifyPlanEntitlements({ isSubscriber: false, paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, "free plan for logged out user with expired trial"); await verifyIsInProTrialEntitlement(false, "no trial for user with expired trial"); await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining for expired trial"); @@ -543,7 +546,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "after trial free user login"); // Verify entitlements API consistency for logged in free user - await verifyPlanEntitlements({ isSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, + await verifyPlanEntitlements({ isSubscriber: false, paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, "free plan for logged in user with expired trial"); await verifyIsInProTrialEntitlement(false, "still no trial after login"); await verifyLiveEditEntitlement({ activated: false }, "live edit still deactivated after login"); @@ -589,7 +592,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "no pro branding initially due to expired entitlements"); // Verify entitlements API consistency for logged out user with no trial - await verifyPlanEntitlements({ isSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, + await verifyPlanEntitlements({ isSubscriber: false, paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE }, "free plan for logged out user with no trial"); await verifyIsInProTrialEntitlement(false, "no trial for logged out user"); await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining"); @@ -603,7 +606,7 @@ define(function (require, exports, module) { await verifyProBranding(false, "no pro branding after login with expired entitlements"); // Verify entitlements API consistency for logged in user with expired entitlements - await verifyPlanEntitlements({ isSubscriber: false }, + await verifyPlanEntitlements({ isSubscriber: false, paidSubscriber: false }, "expired entitlements filtered to free plan after login"); await verifyIsInProTrialEntitlement(false, "no trial for user with expired entitlements"); await verifyTrialRemainingDaysEntitlement(0, "no trial days for expired entitlements user"); @@ -654,7 +657,7 @@ define(function (require, exports, module) { await verifyProBranding(true, "pro branding initially due to active trial"); // Verify entitlements API consistency for logged out user with active trial - await verifyPlanEntitlements({ isSubscriber: true, name: testWindow.brackets.config.main_pro_plan }, + await verifyPlanEntitlements({ isSubscriber: true, paidSubscriber: false, name: testWindow.brackets.config.main_pro_plan }, "trial plan for logged out user overrides expired entitlements"); await verifyIsInProTrialEntitlement(true, "user should be in trial initially"); await verifyTrialRemainingDaysEntitlement(10, "should have 10 trial days remaining"); @@ -668,8 +671,8 @@ define(function (require, exports, module) { await verifyProBranding(true, "pro branding after login - trial overrides expired entitlements"); // Verify entitlements API consistency for logged in user (trial overrides expired server entitlements) - await verifyPlanEntitlements({ isSubscriber: true }, - "trial overrides expired server entitlements to show paid subscriber"); + await verifyPlanEntitlements({ isSubscriber: true, paidSubscriber: false }, + "trial overrides expired server entitlements - user is subscriber but not paid"); await verifyIsInProTrialEntitlement(true, "user should still be in trial after login"); await verifyTrialRemainingDaysEntitlement(10, "trial days should remain 10 after login"); await verifyLiveEditEntitlement({ activated: true }, "live edit should be activated via trial override"); @@ -786,9 +789,11 @@ define(function (require, exports, module) { await verifyProBranding(true, "device license shows Phoenix Pro branding in navbar"); // Verify entitlements API shows Pro access + // Note: Device licenses can be paid (paidSubscriber: true) or educational (paidSubscriber: false) + // This test uses educational license (deviceID request) so paidSubscriber should be false await verifyPlanEntitlements( - { isSubscriber: true, name: "Phoenix Pro" }, - "device license provides Pro plan" + { isSubscriber: true, paidSubscriber: false, name: "Phoenix Pro" }, + "device license provides Pro plan (educational license is unpaid)" ); await verifyIsInProTrialEntitlement(false, "device license is not a trial"); await verifyLiveEditEntitlement({ activated: true }, "live edit activated via device license"); diff --git a/test/spec/login-utils-test.js b/test/spec/login-utils-test.js index 31b1f5ba4b..cf7e8cf8f1 100644 --- a/test/spec/login-utils-test.js +++ b/test/spec/login-utils-test.js @@ -327,11 +327,11 @@ define(function (require, exports, module) { }); it("should return true when isSubscriber status changes", function () { - const current = { + const current = { plan: { name: "Phoenix Pro", isSubscriber: true }, entitlements: {} }; - const last = { + const last = { plan: { name: "Phoenix Pro", isSubscriber: false }, entitlements: {} }; @@ -339,6 +339,34 @@ define(function (require, exports, module) { expect(result).toBe(true); }); + it("should return true when paidSubscriber status changes", function () { + const current = { + plan: { name: "Phoenix Pro", isSubscriber: true, paidSubscriber: true }, + entitlements: {} + }; + const last = { + plan: { name: "Phoenix Pro", isSubscriber: true, paidSubscriber: false }, + entitlements: {} + }; + const result = LoginUtils.haveEntitlementsChanged(current, last); + expect(result).toBe(true); + }); + + it("should return true when user goes from trial to paid (paidSubscriber changes)", function () { + // Trial user: isSubscriber true, paidSubscriber false + const trialUser = { + plan: { name: "Phoenix Pro", isSubscriber: true, paidSubscriber: false }, + entitlements: { liveEdit: { activated: true } } + }; + // Paid user: isSubscriber true, paidSubscriber true + const paidUser = { + plan: { name: "Phoenix Pro", isSubscriber: true, paidSubscriber: true }, + entitlements: { liveEdit: { activated: true } } + }; + const result = LoginUtils.haveEntitlementsChanged(paidUser, trialUser); + expect(result).toBe(true); + }); + it("should return true when plan name changes", function () { const current = { plan: { name: "Phoenix Pro", isSubscriber: true }, diff --git a/test/spec/promotions-integ-test.js b/test/spec/promotions-integ-test.js index b2dbb6ac86..38c46dee3a 100644 --- a/test/spec/promotions-integ-test.js +++ b/test/spec/promotions-integ-test.js @@ -818,6 +818,7 @@ define(function (require, exports, module) { LoginServiceExports._validateAndFilterEntitlements(expiredPlanEntitlements); expect(expiredPlanEntitlements.plan.isSubscriber).toBe(false); + expect(expiredPlanEntitlements.plan.paidSubscriber).toBe(false); expect(expiredPlanEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE); expect(expiredPlanEntitlements.plan.validTill).toBeGreaterThan(mockNow); @@ -848,6 +849,7 @@ define(function (require, exports, module) { LoginServiceExports._validateAndFilterEntitlements(noValidTillEntitlements); expect(noValidTillEntitlements.plan.isSubscriber).toBe(false); + expect(noValidTillEntitlements.plan.paidSubscriber).toBe(false); expect(noValidTillEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE); }); From 8e6ef0fce4faf06d179480de0193dbc3d3bf7b3a Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 9 Oct 2025 14:47:56 +0530 Subject: [PATCH 3/3] test: reducing entilement event interval to 100ms for 1 sec in tests --- src/services/login-service.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/services/login-service.js b/src/services/login-service.js index f58757f498..d1980ac8a0 100644 --- a/src/services/login-service.js +++ b/src/services/login-service.js @@ -80,16 +80,20 @@ define(function (require, exports, module) { // Debounced trigger for entitlements changed let entitlementsChangedTimer = null; + const ENTITLEMENT_CHANGED_DEBOUNCE_WINDOW = Phoenix.isTestWindow ? 100 : 1000; + function _debounceEntitlementsChanged() { if (entitlementsChangedTimer) { // already scheduled, skip return; } + // atmost 1 entitlement changed event will be triggered in this window to prevent too many entitlment changed + // events firing. entitlementsChangedTimer = setTimeout(() => { LoginService.trigger(EVENT_ENTITLEMENTS_CHANGED); entitlementsChangedTimer = null; - }, 1000); // atmost 1 entitlement changed event will be triggered in a second + }, ENTITLEMENT_CHANGED_DEBOUNCE_WINDOW); }