mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-23 22:47:19 +00:00
fix: metering gui maths (#3592)
This commit is contained in:
@@ -1432,6 +1432,27 @@ describe('MeteringService', () => {
|
||||
expect(allowed.remaining).toBe(0);
|
||||
});
|
||||
|
||||
it('never trusts a stored allowanceUsed past the month total', async () => {
|
||||
const sub = await target.getActorSubscription(actor);
|
||||
const month = `${new Date().getUTCFullYear()}-${String(new Date().getUTCMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
// A corrupt record: allowanceUsed grew past the total (a raced
|
||||
// or repeated write). The split is bookkeeping over the total,
|
||||
// so the total is the most the allowance can have been charged.
|
||||
await server.stores.meteringBuffer.incr({
|
||||
key: `${METRICS_PREFIX}:actor:${actor.user.uuid}:${month}`,
|
||||
pathAndAmountMap: {
|
||||
total: 1_000_000,
|
||||
allowanceUsed: sub.monthUsageAllowance + 99_000_000,
|
||||
},
|
||||
});
|
||||
|
||||
const allowed = await target.getAllowedUsage(actor);
|
||||
expect(allowed.remaining).toBe(
|
||||
sub.monthUsageAllowance - 1_000_000,
|
||||
);
|
||||
});
|
||||
|
||||
it('folds the legacy baseline in exactly once under concurrent increments', async () => {
|
||||
const sub = await target.getActorSubscription(actor);
|
||||
const month = `${new Date().getUTCFullYear()}-${String(new Date().getUTCMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
@@ -1144,15 +1144,23 @@ export class MeteringService extends PuterService {
|
||||
* pre-split reading — everything counted against the allowance, capped at
|
||||
* it — which is also what keeps balances unchanged across the deploy that
|
||||
* introduced the field.
|
||||
*
|
||||
* Allowance-charged spend is a subset of spend, so the stored value is
|
||||
* never trusted past the month's total: the split is bookkeeping over the
|
||||
* total, and a stored value exceeding it is corrupt (a write raced or
|
||||
* repeated). Clamping here makes such a record cost the user at most their
|
||||
* real spend rather than however large the corrupt value grew.
|
||||
*/
|
||||
private static allowanceUsedFrom(
|
||||
usage: UsageByType | null | undefined,
|
||||
monthUsageAllowance: number,
|
||||
): number {
|
||||
if (!usage) return 0;
|
||||
return (
|
||||
const total = usage.total || 0;
|
||||
return Math.min(
|
||||
usage.allowanceUsed ??
|
||||
Math.min(usage.total || 0, Math.max(0, monthUsageAllowance || 0))
|
||||
Math.min(total, Math.max(0, monthUsageAllowance || 0)),
|
||||
total,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1774,11 +1782,13 @@ export class MeteringService extends PuterService {
|
||||
0,
|
||||
(usageRecord.total || 0) - incrementCost,
|
||||
);
|
||||
// Same fallback as `allowanceUsedFrom`, measured before this
|
||||
// increment's own cost.
|
||||
const usedBefore =
|
||||
// Same fallback and corrupt-value clamp as `allowanceUsedFrom`,
|
||||
// measured before this increment's own cost.
|
||||
const usedBefore = Math.min(
|
||||
usageRecord.allowanceUsed ??
|
||||
Math.min(totalBefore, Math.max(0, monthUsageAllowance || 0));
|
||||
Math.min(totalBefore, Math.max(0, monthUsageAllowance || 0)),
|
||||
totalBefore,
|
||||
);
|
||||
|
||||
let baseline = 0;
|
||||
if (usageRecord.allowanceUsed === undefined && usedBefore > 0) {
|
||||
|
||||
Reference in New Issue
Block a user