@@ -49,6 +49,10 @@ describe('Billing usage endpoint unit tests:', () => {
4949 default : { getOrgBalanceContext : jest . fn ( ) . mockResolvedValue ( 0 ) } ,
5050 } ) ) ;
5151
52+ jest . unstable_mockModule ( '../services/billing.plan.service.js' , ( ) => ( {
53+ default : { getActivePlan : jest . fn ( ) . mockReturnValue ( null ) } ,
54+ } ) ) ;
55+
5256 jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
5357 default : mockConfig ,
5458 } ) ) ;
@@ -232,4 +236,125 @@ describe('Billing usage endpoint unit tests:', () => {
232236 message : 'Internal Server Error' ,
233237 } ) ) ;
234238 } ) ;
239+
240+ describe ( 'meterMode — meterQuota live override' , ( ) => {
241+ let mockBillingPlanService ;
242+ let mockMeterUsageService ;
243+
244+ beforeEach ( async ( ) => {
245+ jest . resetModules ( ) ;
246+
247+ mockBillingService = {
248+ getLocalSubscription : jest . fn ( ) ,
249+ getSubscription : jest . fn ( ) ,
250+ } ;
251+
252+ mockMeterUsageService = {
253+ getMeter : jest . fn ( ) ,
254+ currentWeekKey : jest . fn ( ) . mockReturnValue ( '2026-W20' ) ,
255+ } ;
256+
257+ mockBillingPlanService = {
258+ getActivePlan : jest . fn ( ) ,
259+ } ;
260+
261+ jest . unstable_mockModule ( '../services/billing.service.js' , ( ) => ( {
262+ default : mockBillingService ,
263+ } ) ) ;
264+
265+ jest . unstable_mockModule ( '../services/billing.usage.service.js' , ( ) => ( {
266+ default : mockMeterUsageService ,
267+ } ) ) ;
268+
269+ jest . unstable_mockModule ( '../services/billing.extra.service.js' , ( ) => ( {
270+ default : { getOrgBalanceContext : jest . fn ( ) . mockResolvedValue ( 0 ) } ,
271+ } ) ) ;
272+
273+ jest . unstable_mockModule ( '../services/billing.plan.service.js' , ( ) => ( {
274+ default : mockBillingPlanService ,
275+ } ) ) ;
276+
277+ jest . unstable_mockModule ( '../../../lib/services/logger.js' , ( ) => ( {
278+ default : { info : jest . fn ( ) , error : jest . fn ( ) , warn : jest . fn ( ) } ,
279+ } ) ) ;
280+ jest . unstable_mockModule ( '../lib/events.js' , ( ) => ( {
281+ default : { emit : jest . fn ( ) } ,
282+ } ) ) ;
283+
284+ jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
285+ default : {
286+ billing : {
287+ meterMode : true ,
288+ packs : [ ] ,
289+ } ,
290+ } ,
291+ } ) ) ;
292+
293+ const mod = await import ( '../controllers/billing.controller.js' ) ;
294+ billingController = mod . default ;
295+
296+ res = {
297+ status : jest . fn ( ) . mockReturnThis ( ) ,
298+ json : jest . fn ( ) . mockReturnThis ( ) ,
299+ } ;
300+ } ) ;
301+
302+ test ( 'returns growth plan quota (1600) from live config when DB snapshot shows old free quota (10)' , async ( ) => {
303+ // DB snapshot baked when user was on free (meterQuota = 10)
304+ mockBillingService . getLocalSubscription . mockResolvedValue ( { plan : 'growth' , status : 'active' } ) ;
305+ mockMeterUsageService . getMeter . mockResolvedValue ( {
306+ meterUsed : 46 ,
307+ meterQuota : 10 ,
308+ meterBreakdown : { } ,
309+ planVersion : 'v1' ,
310+ weekKey : '2026-W20' ,
311+ resetAt : null ,
312+ } ) ;
313+ // Live config knows growth = 1600
314+ mockBillingPlanService . getActivePlan . mockReturnValue ( { meterQuota : 1600 } ) ;
315+
316+ const req = { organization : { _id : orgId } } ;
317+ await billingController . getUsage ( req , res ) ;
318+
319+ expect ( res . status ) . toHaveBeenCalledWith ( 200 ) ;
320+ const payload = res . json . mock . calls [ 0 ] [ 0 ] . data ;
321+ expect ( payload . meterQuota ) . toBe ( 1600 ) ; // live plan config, not stale DB snapshot
322+ expect ( payload . meterUsed ) . toBe ( 46 ) ;
323+ expect ( payload . plan ) . toBe ( 'growth' ) ;
324+ } ) ;
325+
326+ test ( 'falls back to DB snapshot quota when live plan config returns null (unknown plan)' , async ( ) => {
327+ mockBillingService . getLocalSubscription . mockResolvedValue ( { plan : 'legacy' , status : 'active' } ) ;
328+ mockMeterUsageService . getMeter . mockResolvedValue ( {
329+ meterUsed : 5 ,
330+ meterQuota : 50 ,
331+ meterBreakdown : { } ,
332+ planVersion : 'v1' ,
333+ weekKey : '2026-W20' ,
334+ resetAt : null ,
335+ } ) ;
336+ mockBillingPlanService . getActivePlan . mockReturnValue ( null ) ;
337+
338+ const req = { organization : { _id : orgId } } ;
339+ await billingController . getUsage ( req , res ) ;
340+
341+ expect ( res . status ) . toHaveBeenCalledWith ( 200 ) ;
342+ const payload = res . json . mock . calls [ 0 ] [ 0 ] . data ;
343+ expect ( payload . meterQuota ) . toBe ( 50 ) ; // falls back to DB snapshot
344+ } ) ;
345+
346+ test ( 'returns 0 meterQuota when no DB snapshot and no live config plan' , async ( ) => {
347+ mockBillingService . getLocalSubscription . mockResolvedValue ( null ) ;
348+ mockMeterUsageService . getMeter . mockResolvedValue ( null ) ;
349+ mockBillingPlanService . getActivePlan . mockReturnValue ( null ) ;
350+
351+ const req = { organization : { _id : orgId } } ;
352+ await billingController . getUsage ( req , res ) ;
353+
354+ expect ( res . status ) . toHaveBeenCalledWith ( 200 ) ;
355+ const payload = res . json . mock . calls [ 0 ] [ 0 ] . data ;
356+ expect ( payload . meterQuota ) . toBe ( 0 ) ;
357+ expect ( payload . meterUsed ) . toBe ( 0 ) ;
358+ } ) ;
359+ } ) ;
235360} ) ;
0 commit comments