Skip to content

Commit 87f5256

Browse files
committed
fix(aws-marketplace): fix a record's quantity once it is first sent, and keep out-of-window records from failing a batch
1 parent 7a7f562 commit 87f5256

7 files changed

Lines changed: 249 additions & 74 deletions

File tree

‎apps/sim/lib/billing/aws-marketplace/metering.integration.ts‎

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,100 @@ describe('AWS Marketplace usage metering', () => {
424424
await meterAwsMarketplaceUsage(NOW + 25 * HOUR_MS)
425425

426426
expect(await reportsFor(owner.organizationId)).toEqual([
427-
expect.objectContaining({ status: 'rejected', error: expect.stringMatching(/24 hours/) }),
427+
expect.objectContaining({ status: 'rejected', error: expect.stringMatching(/window/) }),
428428
])
429429
expect(sentRecords(licenseArn)).toEqual([])
430430
})
431+
432+
it('resends a record at the quantity first sent, keeping later usage off it', async () => {
433+
const owner = await createOrganization()
434+
const { licenseArn } = await insertLicense(owner.organizationId, {
435+
status: 'inactive',
436+
expiresAt: null,
437+
endedAt: minutesAgo(10),
438+
})
439+
await logUsage(owner, { at: minutesAgo(15), cost: 0.02 })
440+
meteringSend.mockRejectedValueOnce(
441+
Object.assign(new Error('timed out'), { name: 'TimeoutError' })
442+
)
443+
444+
await meterAwsMarketplaceUsage(NOW)
445+
await logUsage(owner, { at: minutesAgo(1), cost: 3 })
446+
await meterAwsMarketplaceUsage(NOW + 5 * 60_000)
447+
448+
expect(sentRecords(licenseArn).map((record) => record.Quantity)).toEqual([2, 2])
449+
expect(await reportsFor(owner.organizationId)).toEqual([
450+
expect.objectContaining({ quantity: 2, status: 'reported' }),
451+
])
452+
})
453+
454+
it('rejects only the record whose timestamp AWS finds out of range', async () => {
455+
const owner = await createOrganization()
456+
const { licenseArn } = await insertLicense(owner.organizationId)
457+
const earlierHour = floorHour(NOW - 2 * HOUR_MS)
458+
await logUsage(owner, { at: minutesAgo(130), cost: 0.01 })
459+
await meterAwsMarketplaceUsage(NOW - 2 * HOUR_MS)
460+
await logUsage(owner, { at: minutesAgo(10), cost: 0.02 })
461+
meteringSend.mockResolvedValue({ Results: [], UnprocessedRecords: [] })
462+
await meterAwsMarketplaceUsage(NOW)
463+
acceptEverything()
464+
const accept = meteringSend.getMockImplementation()
465+
meteringSend.mockImplementation(async (command: { input: { UsageRecords: SentRecord[] } }) => {
466+
if (
467+
command.input.UsageRecords.some(
468+
(record) => record.Timestamp?.getTime() === earlierHour.getTime()
469+
)
470+
) {
471+
throw Object.assign(new Error('out of range'), { name: 'TimestampOutOfBoundsException' })
472+
}
473+
return accept?.(command)
474+
})
475+
476+
await meterAwsMarketplaceUsage(NEXT_HOUR)
477+
478+
expect(await reportsFor(owner.organizationId)).toEqual([
479+
expect.objectContaining({
480+
hourStart: earlierHour,
481+
status: 'rejected',
482+
error: 'TimestampOutOfBoundsException',
483+
}),
484+
expect.objectContaining({ hourStart: HOUR, quantity: 2, status: 'reported' }),
485+
])
486+
expect(sentRecords(licenseArn).at(-1)).toEqual(expect.objectContaining({ Timestamp: HOUR }))
487+
})
488+
489+
it("sends last month's records until the month-end grace passes, then rejects them", async () => {
490+
const owner = await createOrganization()
491+
const today = new Date(NOW)
492+
const monthStart = Date.UTC(today.getUTCFullYear(), today.getUTCMonth() + 1, 1)
493+
const period = {
494+
start: new Date(monthStart - 10 * DAY_MS),
495+
end: new Date(monthStart + 20 * DAY_MS),
496+
}
497+
const { licenseArn } = await insertLicense(owner.organizationId, {
498+
organizationLinkedAt: new Date(monthStart - 5 * HOUR_MS),
499+
})
500+
const lastHourOfMonth = new Date(monthStart - HOUR_MS)
501+
await logUsage(owner, { at: new Date(monthStart - 50 * 60_000), cost: 0.01, period })
502+
await meterAwsMarketplaceUsage(monthStart - 30 * 60_000)
503+
meteringSend.mockResolvedValue({ Results: [], UnprocessedRecords: [] })
504+
505+
await meterAwsMarketplaceUsage(monthStart + 5 * HOUR_MS)
506+
507+
expect(sentRecords(licenseArn)).toEqual([
508+
expect.objectContaining({ Timestamp: lastHourOfMonth }),
509+
])
510+
511+
meteringSend.mockClear()
512+
await meterAwsMarketplaceUsage(monthStart + 7 * HOUR_MS)
513+
514+
expect(sentRecords(licenseArn)).toEqual([])
515+
expect(await reportsFor(owner.organizationId)).toEqual([
516+
expect.objectContaining({
517+
hourStart: lastHourOfMonth,
518+
status: 'rejected',
519+
error: expect.stringMatching(/window/),
520+
}),
521+
])
522+
})
431523
})

0 commit comments

Comments
 (0)