Skip to content

Commit 10b9362

Browse files
committed
fix(billing): re-arm limit dedup at zero usage and zero prior usage (full clear / wipe-rebuild)
1 parent 4a689d8 commit 10b9362

2 files changed

Lines changed: 25 additions & 4 deletions

File tree

apps/sim/lib/billing/core/limit-notifications.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,28 @@ describe('maybeSendLimitThresholdEmail', () => {
161161
expect(sendEmailSpy).not.toHaveBeenCalled()
162162
})
163163

164-
it('skips when limit or usage is non-positive', async () => {
164+
it('re-arms but does not send when usage is fully cleared (zero usage)', async () => {
165165
await maybeSendLimitThresholdEmail({ ...baseUserParams, currentUsage: 0, limit: 5 })
166+
expect(dbUpdateSpy).toHaveBeenCalledTimes(1) // re-arm only
167+
expect(mockClaim).not.toHaveBeenCalled()
168+
expect(sendEmailSpy).not.toHaveBeenCalled()
169+
})
170+
171+
it('re-arms then sends on wipe-then-rebuild (priorUsage 0)', async () => {
172+
// prior 0% (empty table) → current 90%: re-arm + claim + send.
173+
await maybeSendLimitThresholdEmail({
174+
...baseUserParams,
175+
currentUsage: 4.5,
176+
limit: 5,
177+
priorUsage: 0,
178+
})
179+
expect(dbUpdateSpy).toHaveBeenCalledTimes(2)
180+
expect(sendEmailSpy).toHaveBeenCalledTimes(1)
181+
})
182+
183+
it('skips when the limit is non-positive', async () => {
166184
await maybeSendLimitThresholdEmail({ ...baseUserParams, currentUsage: 4, limit: 0 })
185+
expect(dbUpdateSpy).not.toHaveBeenCalled()
167186
expect(sendEmailSpy).not.toHaveBeenCalled()
168187
})
169188
})

apps/sim/lib/billing/core/limit-notifications.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,14 @@ export async function maybeSendLimitThresholdEmail(params: {
129129
}): Promise<void> {
130130
try {
131131
if (!isBillingEnabled) return
132-
if (params.limit <= 0 || params.currentUsage <= 0) return
132+
// A non-positive limit can't yield a percentage; a zero/negative `currentUsage`
133+
// still needs to re-arm below, so it is handled by the `desired === 0` return.
134+
if (params.limit <= 0) return
133135

134136
const { category, scope } = params
135-
const percent = (params.currentUsage / params.limit) * 100
137+
const percent = Math.max(0, (params.currentUsage / params.limit) * 100)
136138
const priorPercent =
137-
params.priorUsage != null && params.priorUsage > 0
139+
params.priorUsage != null && params.priorUsage >= 0
138140
? (params.priorUsage / params.limit) * 100
139141
: percent
140142
const desired = thresholdFor(percent)

0 commit comments

Comments
 (0)