@@ -37,11 +37,12 @@ import (
3737var m sync.Mutex
3838
3939type deps struct {
40- entRepo entitlement.EntitlementRepo
41- featureRepo feature.FeatureRepo
42- subjectRepo subject.Service
43- customerRepo customer.Adapter
44- meterRepo * meteradapter.Adapter
40+ entRepo entitlement.EntitlementRepo
41+ featureRepo feature.FeatureRepo
42+ subjectRepo subject.Service
43+ customerRepo customer.Adapter
44+ meterRepo * meteradapter.Adapter
45+ usageResetRepo meteredentitlement.UsageResetRepo
4546}
4647
4748func setup (t * testing.T ) (deps deps , cleanup func ()) {
@@ -74,6 +75,7 @@ func setup(t *testing.T) (deps deps, cleanup func()) {
7475 require .NotNilf (t , deps .meterRepo , "meter adapter must not be nil" )
7576
7677 deps .entRepo = adapter .NewPostgresEntitlementRepo (dbClient )
78+ deps .usageResetRepo = adapter .NewPostgresUsageResetRepo (dbClient )
7779 deps .featureRepo = featureadapter .NewPostgresFeatureRepo (dbClient , logger )
7880
7981 // customer adapter for creating customers in tests
@@ -329,79 +331,139 @@ func TestListActiveEntitlementsWithExpiredUsagePeriod(t *testing.T) {
329331 ns := "ns1"
330332 featureKey := "feature1"
331333
332- t .Run ("Should return entitlements with expired usage period" , func (t * testing.T ) {
333- ctx := context . Background ()
334+ t .Run ("Should return only resettable metered entitlements with expired usage period" , func (t * testing.T ) {
335+ ctx := t . Context ()
334336 repo , cleanup := setup (t )
335337 defer cleanup ()
336338
337- // Let's create an example feature
338339 feature , err := repo .featureRepo .CreateFeature (ctx , feature.CreateFeatureInputs {
339340 Namespace : ns ,
340341 Key : featureKey ,
341342 Name : "Feature 1" ,
342343 })
343344 require .NoError (t , err )
344345
345- // Let's set the current time
346346 now := time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )
347347 clock .SetTime (now )
348348 defer clock .ResetTime ()
349349
350- // First, create the subjects
350+ expiredPeriod := & timeutil.ClosedPeriod {
351+ From : time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
352+ To : time .Date (2025 , 2 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
353+ }
354+ activePeriod := & timeutil.ClosedPeriod {
355+ From : time .Date (2025 , 3 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
356+ To : time .Date (2025 , 4 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
357+ }
358+ usagePeriod := lo .ToPtr (entitlement .NewUsagePeriodInputFromRecurrence (timeutil.Recurrence {
359+ Interval : timeutil .RecurrencePeriodMonth ,
360+ Anchor : time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
361+ }))
362+
351363 cust1 := createCustomerWithSubject (t , repo .subjectRepo , repo .customerRepo , ns , "subject1" )
352364 cust2 := createCustomerWithSubject (t , repo .subjectRepo , repo .customerRepo , ns , "subject2" )
365+ custStatic := createCustomerWithSubject (t , repo .subjectRepo , repo .customerRepo , ns , "subject-static" )
366+ custBoolean := createCustomerWithSubject (t , repo .subjectRepo , repo .customerRepo , ns , "subject-boolean" )
367+ custMalformed := createCustomerWithSubject (t , repo .subjectRepo , repo .customerRepo , ns , "subject-malformed" )
368+ custResetAnchor := createCustomerWithSubject (t , repo .subjectRepo , repo .customerRepo , ns , "subject-reset-anchor" )
353369
354- // Then create two entitlements, one with expired usage period and one with no expired usage period
355370 ent1 , err := repo .entRepo .CreateEntitlement (ctx , entitlement.CreateEntitlementRepoInputs {
356- Namespace : ns ,
357- FeatureID : feature .ID ,
358- FeatureKey : featureKey ,
359- UsageAttribution : cust1 .GetUsageAttribution (),
360- EntitlementType : entitlement .EntitlementTypeMetered ,
361- MeasureUsageFrom : lo .ToPtr (time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )),
362- UsagePeriod : lo .ToPtr (entitlement .NewUsagePeriodInputFromRecurrence (timeutil.Recurrence {
363- Interval : timeutil .RecurrencePeriodMonth ,
364- Anchor : time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
365- })),
366- CurrentUsagePeriod : & timeutil.ClosedPeriod {
367- From : time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
368- To : time .Date (2025 , 2 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
369- },
371+ Namespace : ns ,
372+ FeatureID : feature .ID ,
373+ FeatureKey : featureKey ,
374+ UsageAttribution : cust1 .GetUsageAttribution (),
375+ EntitlementType : entitlement .EntitlementTypeMetered ,
376+ MeasureUsageFrom : lo .ToPtr (time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )),
377+ UsagePeriod : usagePeriod ,
378+ CurrentUsagePeriod : expiredPeriod ,
370379 })
371380 require .NoError (t , err )
372381 require .NotNil (t , ent1 )
373382
374383 ent2 , err := repo .entRepo .CreateEntitlement (ctx , entitlement.CreateEntitlementRepoInputs {
375- Namespace : ns ,
376- FeatureID : feature .ID ,
377- FeatureKey : featureKey ,
378- UsageAttribution : cust2 .GetUsageAttribution (),
379- EntitlementType : entitlement .EntitlementTypeMetered ,
380- MeasureUsageFrom : lo .ToPtr (time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )),
381- UsagePeriod : lo .ToPtr (entitlement .NewUsagePeriodInputFromRecurrence (timeutil.Recurrence {
382- Interval : timeutil .RecurrencePeriodMonth ,
383- Anchor : time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
384- })),
385- CurrentUsagePeriod : & timeutil.ClosedPeriod {
386- From : time .Date (2025 , 3 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
387- To : time .Date (2025 , 4 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
388- },
384+ Namespace : ns ,
385+ FeatureID : feature .ID ,
386+ FeatureKey : featureKey ,
387+ UsageAttribution : cust2 .GetUsageAttribution (),
388+ EntitlementType : entitlement .EntitlementTypeMetered ,
389+ MeasureUsageFrom : lo .ToPtr (time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )),
390+ UsagePeriod : usagePeriod ,
391+ CurrentUsagePeriod : activePeriod ,
389392 })
390393 require .NoError (t , err )
391394 require .NotNil (t , ent2 )
392395
396+ staticEnt , err := repo .entRepo .CreateEntitlement (ctx , entitlement.CreateEntitlementRepoInputs {
397+ Namespace : ns ,
398+ FeatureID : feature .ID ,
399+ FeatureKey : featureKey ,
400+ UsageAttribution : custStatic .GetUsageAttribution (),
401+ EntitlementType : entitlement .EntitlementTypeStatic ,
402+ Config : lo .ToPtr (`{"on":true}` ),
403+ UsagePeriod : usagePeriod ,
404+ CurrentUsagePeriod : expiredPeriod ,
405+ })
406+ require .NoError (t , err )
407+ require .NotNil (t , staticEnt )
408+
409+ booleanEnt , err := repo .entRepo .CreateEntitlement (ctx , entitlement.CreateEntitlementRepoInputs {
410+ Namespace : ns ,
411+ FeatureID : feature .ID ,
412+ FeatureKey : featureKey ,
413+ UsageAttribution : custBoolean .GetUsageAttribution (),
414+ EntitlementType : entitlement .EntitlementTypeBoolean ,
415+ UsagePeriod : usagePeriod ,
416+ CurrentUsagePeriod : expiredPeriod ,
417+ })
418+ require .NoError (t , err )
419+ require .NotNil (t , booleanEnt )
420+
421+ malformedEnt , err := repo .entRepo .CreateEntitlement (ctx , entitlement.CreateEntitlementRepoInputs {
422+ Namespace : ns ,
423+ FeatureID : feature .ID ,
424+ FeatureKey : featureKey ,
425+ UsageAttribution : custMalformed .GetUsageAttribution (),
426+ EntitlementType : entitlement .EntitlementTypeMetered ,
427+ UsagePeriod : usagePeriod ,
428+ CurrentUsagePeriod : expiredPeriod ,
429+ })
430+ require .NoError (t , err )
431+ require .NotNil (t , malformedEnt )
432+
433+ resetAnchorEnt , err := repo .entRepo .CreateEntitlement (ctx , entitlement.CreateEntitlementRepoInputs {
434+ Namespace : ns ,
435+ FeatureID : feature .ID ,
436+ FeatureKey : featureKey ,
437+ UsageAttribution : custResetAnchor .GetUsageAttribution (),
438+ EntitlementType : entitlement .EntitlementTypeMetered ,
439+ UsagePeriod : usagePeriod ,
440+ CurrentUsagePeriod : expiredPeriod ,
441+ })
442+ require .NoError (t , err )
443+ require .NotNil (t , resetAnchorEnt )
444+
445+ err = repo .usageResetRepo .Save (ctx , meteredentitlement.UsageResetUpdate {
446+ NamespacedModel : models.NamespacedModel {
447+ Namespace : ns ,
448+ },
449+ EntitlementID : resetAnchorEnt .ID ,
450+ ResetTime : time .Date (2025 , 1 , 15 , 0 , 0 , 0 , 0 , time .UTC ),
451+ Anchor : time .Date (2025 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
452+ UsagePeriodInterval : timeutil .RecurrencePeriodMonth .ISOString (),
453+ })
454+ require .NoError (t , err )
455+
393456 now = time .Date (2025 , 3 , 1 , 0 , 0 , 0 , 0 , time .UTC )
394457 clock .SetTime (now )
395- defer clock .ResetTime ()
396458
397- // Let's check that the entitlement with expired usage period is returned
398459 ents , err := repo .entRepo .ListActiveEntitlementsWithExpiredUsagePeriod (ctx , entitlement.ListExpiredEntitlementsParams {
399460 Namespaces : []string {ns },
400461 Highwatermark : now ,
401462 })
402463 require .NoError (t , err )
403- require .Equal (t , 1 , len ( ents ) )
464+ require .Len (t , ents , 2 )
404465 require .Equal (t , ent1 .ID , ents [0 ].ID )
466+ require .Equal (t , resetAnchorEnt .ID , ents [1 ].ID )
405467 })
406468
407469 t .Run ("Should return entitlements with cursor and limit" , func (t * testing.T ) {
0 commit comments