@@ -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 ( / 2 4 h o u r s / ) } ) ,
427+ expect . objectContaining ( { status : 'rejected' , error : expect . stringMatching ( / w i n d o w / ) } ) ,
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 ( / w i n d o w / ) ,
520+ } ) ,
521+ ] )
522+ } )
431523} )
0 commit comments