Skip to content

Commit 1cc1c6d

Browse files
authored
fix(entitlement): reject duplicate reset time (#4511)
1 parent 779ca4f commit 1cc1c6d

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

openmeter/entitlement/metered/reset.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ func (e *connector) ResetEntitlementUsage(ctx context.Context, entitlementID mod
3838
return nil, fmt.Errorf("failed to parse entitlement: %w", err)
3939
}
4040

41+
resetTime := params.At.Truncate(time.Minute)
42+
lastReset := mEnt.LastReset.Truncate(time.Minute)
43+
if resetTime.Equal(lastReset) {
44+
return nil, models.NewGenericValidationError(fmt.Errorf("reset at %s must be after last reset at %s", resetTime, lastReset))
45+
}
46+
4147
if err := e.hooks.PreUpdate(ctx, mEnt); err != nil {
4248
return nil, err
4349
}

openmeter/entitlement/metered/reset_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,47 @@ func TestResetEntitlementUsage(t *testing.T) {
204204
assert.ErrorContains(t, err, "in the future")
205205
},
206206
},
207+
{
208+
name: "Should error if requested reset time is the same minute as the last reset",
209+
run: func(t *testing.T, connector meteredentitlement.Connector, deps *dependencies) {
210+
ctx := t.Context()
211+
startTime := getAnchor(t)
212+
randName := testutils.NameGenerator.Generate()
213+
214+
// given:
215+
// - a metered entitlement that has already been reset in a specific minute
216+
// when:
217+
// - reset is requested again inside the same minute
218+
// then:
219+
// - the second reset is rejected as a duplicate reset time
220+
221+
// create customer and subject
222+
cust := createCustomerAndSubject(t, deps.subjectService, deps.customerService, namespace, randName.Key, randName.Name)
223+
224+
// create entitlement in db
225+
inp := getEntitlement(t, feat, cust.GetUsageAttribution())
226+
inp.MeasureUsageFrom = &startTime
227+
ent, err := deps.entitlementRepo.CreateEntitlement(ctx, inp)
228+
require.NoError(t, err)
229+
230+
deps.streamingConnector.AddSimpleEvent(meterSlug, 100, startTime.Add(time.Minute))
231+
232+
resetTime := startTime.Add(3*time.Hour + 10*time.Second)
233+
_, err = connector.ResetEntitlementUsage(ctx,
234+
models.NamespacedID{Namespace: namespace, ID: ent.ID},
235+
meteredentitlement.ResetEntitlementUsageParams{
236+
At: resetTime,
237+
})
238+
require.NoError(t, err)
239+
240+
_, err = connector.ResetEntitlementUsage(ctx,
241+
models.NamespacedID{Namespace: namespace, ID: ent.ID},
242+
meteredentitlement.ResetEntitlementUsageParams{
243+
At: resetTime.Truncate(time.Minute).Add(30 * time.Second),
244+
})
245+
assert.ErrorContains(t, err, "must be after last reset")
246+
},
247+
},
207248
{
208249
name: "Should invalidate snapshots after the reset time",
209250
run: func(t *testing.T, connector meteredentitlement.Connector, deps *dependencies) {

0 commit comments

Comments
 (0)