[ {"name": "99.9% of a million requests allows 1000 bad; 250 spent is a quarter of the budget", "args": [9990, 1000000, 250], "expect": {"allowedBad": 1000, "remainingBad": 750, "consumedBasisPoints": 2500, "remainingBasisPoints": 7500, "exhausted": false}}, {"name": "a time budget: 99.9% over 30 days allows 2592 down seconds (43m 12s)", "args": [9990, 2592000, 0], "expect": {"allowedBad": 2592, "remainingBad": 2592, "consumedBasisPoints": 0, "remainingBasisPoints": 10000, "exhausted": false}}, {"name": "half the 30-day time budget spent after 1296 down seconds", "args": [9990, 2592000, 1296], "expect": {"allowedBad": 2592, "remainingBad": 1296, "consumedBasisPoints": 5000, "remainingBasisPoints": 5000, "exhausted": false}}, {"name": "exactly at budget is fully spent but not exhausted", "args": [9990, 1000000, 1000], "expect": {"allowedBad": 1000, "remainingBad": 0, "consumedBasisPoints": 10000, "remainingBasisPoints": 0, "exhausted": false}}, {"name": "one bad event over budget is exhausted, and remaining goes negative", "args": [9990, 1000000, 1001], "expect": {"allowedBad": 1000, "remainingBad": -1, "consumedBasisPoints": 10010, "remainingBasisPoints": -10, "exhausted": true}}, {"name": "no events yet: nothing allowed, nothing spent", "args": [9990, 0, 0], "expect": {"allowedBad": 0, "remainingBad": 0, "consumedBasisPoints": 0, "remainingBasisPoints": 10000, "exhausted": false}}, {"name": "a budget of 1.5 events: allowedBad floors to 1, but 1 bad is only two thirds spent and not exhausted", "args": [9990, 1500, 1], "expect": {"allowedBad": 1, "remainingBad": 0, "consumedBasisPoints": 6667, "remainingBasisPoints": 3333, "exhausted": false}}, {"name": "a budget of 1.5 events with 2 bad is exhausted", "args": [9990, 1500, 2], "expect": {"allowedBad": 1, "remainingBad": -1, "consumedBasisPoints": 13333, "remainingBasisPoints": -3333, "exhausted": true}}, {"name": "consumed rounds half up: 2.5 basis points becomes 3", "args": [9000, 40000, 1], "expect": {"allowedBad": 4000, "remainingBad": 3999, "consumedBasisPoints": 3, "remainingBasisPoints": 9997, "exhausted": false}}, {"name": "consumed rounds half up: 62.5 basis points becomes 63", "args": [9990, 160000, 1], "expect": {"allowedBad": 160, "remainingBad": 159, "consumedBasisPoints": 63, "remainingBasisPoints": 9937, "exhausted": false}}, {"name": "every request failing at 99.9% burns the budget a thousand times over", "args": [9990, 1000, 1000], "expect": {"allowedBad": 1, "remainingBad": -999, "consumedBasisPoints": 10000000, "remainingBasisPoints": -9990000, "exhausted": true}}, {"name": "the loosest target, 0.01%, allows 9999 of 10000 bad", "args": [1, 10000, 10000], "expect": {"allowedBad": 9999, "remainingBad": -1, "consumedBasisPoints": 10001, "remainingBasisPoints": -1, "exhausted": true}}, {"name": "a trillion requests: products pass 2^53 and stay exact", "args": [9990, 1000000000000, 400049999], "expect": {"allowedBad": 1000000000, "remainingBad": 599950001, "consumedBasisPoints": 4000, "remainingBasisPoints": 6000, "exhausted": false}}, {"name": "a 100% target has no budget and is an error", "args": [10000, 100, 0], "expectError": "targetBasisPoints must be a whole number from 1 to 9999 (10000 leaves no error budget), received 10000"}, {"name": "a 0% target is an error", "args": [0, 100, 0], "expectError": "targetBasisPoints must be a whole number from 1 to 9999"}, {"name": "negative total is an error", "args": [9990, -1, 0], "expectError": "totalEvents must be a whole number of at least 0, received -1"}, {"name": "negative bad is an error", "args": [9990, 10, -1], "expectError": "badEvents must be a whole number of at least 0, received -1"}, {"name": "more bad events than events is an error", "args": [9990, 4, 5], "expectError": "badEvents must not exceed totalEvents: 5 > 4"}, {"name": "a fractional count is an error", "args": [9990, 2.5, 0], "expectError": "totalEvents must be a whole number of at least 0, received 2.5"} ]