Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/__tests__/billing-index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
* Confirms the hot /api/billing filter on `developer_id` creates and uses
* `idx_billing_requests_lookup_hot`, and that the rollback migration drops it.
*/
import fc from 'fast-check';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { newDb } from 'pg-mem';
import { billingInternals } from '../services/billing.js';

const migrationsDir = path.join(process.cwd(), 'migrations');

Expand All @@ -31,6 +33,13 @@ INSERT INTO billing_requests (id, request_id, developer_id, api_id, endpoint_id,
VALUES ('req_1', 'req_id_1', 'dev_a', 'api_1', 'ep_1', 'key_1', '5.00');
`;

// --- USDC unit conversion properties (issue: property-test round trips) ---
// Use the real exported helpers from src/services/billing.ts so these
// round-trip invariants exercise production code instead of a local copy.
const { parseUsdcToContractUnits, formatContractUnitsToUsdc } = billingInternals;
const USDC_DECIMALS = 7;
const USDC_SCALE = 10n ** BigInt(USDC_DECIMALS);

const HOT_PATH_QUERY = `
SELECT id, request_id, developer_id, api_id, endpoint_id, api_key_id, amount_usdc, created_at
FROM billing_requests
Expand All @@ -48,6 +57,101 @@ function isSqliteAvailable(): boolean {
}
}

describe('USDC unit conversion — fast-check properties', () => {
it('round-trips format(parse(x)) for 10k generated amounts', () => {
const amountArb = fc
.tuple(
fc.bigInt({ min: 0n, max: 10n ** 18n }),
fc.integer({ min: 0, max: USDC_DECIMALS }),
fc.array(fc.integer({ min: 0, max: 9 }), {
minLength: USDC_DECIMALS,
maxLength: USDC_DECIMALS,
}),
)
.map(([whole, fracLen, digits]) => {
const frac = digits.slice(0, fracLen).join('');
const raw = frac.length > 0 ? `${whole}.${frac}` : `${whole}`;
return raw;
})
.filter((raw) => {
try {
parseUsdcToContractUnits(raw);
return true;
} catch {
return false;
}
});

fc.assert(
fc.property(amountArb, (raw) => {
const units = parseUsdcToContractUnits(raw);
const formatted = formatContractUnitsToUsdc(units);
const reparsed = parseUsdcToContractUnits(formatted);
expect(reparsed).toBe(units);
}),
{ numRuns: 10_000 },
);
});

it('rejects inputs with more than 7 fractional digits', () => {
fc.assert(
fc.property(
fc.bigInt({ min: 0n, max: 10n ** 12n }),
fc.array(fc.integer({ min: 0, max: 9 }), { minLength: 8, maxLength: 12 }),
(whole, digits) => {
const raw = `${whole}.${digits.join('')}`;
expect(() => parseUsdcToContractUnits(raw)).toThrow();
},
),
{ numRuns: 1_000 },
);
});

it('rejects negative amounts', () => {
fc.assert(
fc.property(fc.bigInt({ min: 1n, max: 10n ** 12n }), (whole) => {
expect(() => parseUsdcToContractUnits(`-${whole}`)).toThrow();
}),
{ numRuns: 1_000 },
);
});

it("throws 'must be greater than zero' for zero", () => {
expect(() => parseUsdcToContractUnits('0')).toThrow('must be greater than zero');
expect(() => parseUsdcToContractUnits('0.0')).toThrow('must be greater than zero');
expect(() => parseUsdcToContractUnits('0.0000000')).toThrow('must be greater than zero');
});

it('never emits trailing zeros in formatted output', () => {
fc.assert(
fc.property(
fc.bigInt({ min: 1n, max: 10n ** 24n }),
(units) => {
const formatted = formatContractUnitsToUsdc(units);
expect(formatted).not.toMatch(/\.\d*0$/);
expect(formatted).not.toMatch(/\.$/);
},
),
{ numRuns: 10_000 },
);
});

it('preserves precision for very large whole numbers', () => {
fc.assert(
fc.property(
fc.bigInt({ min: 10n ** 18n, max: 10n ** 30n }),
(whole) => {
const raw = `${whole}`;
const units = parseUsdcToContractUnits(raw);
expect(units).toBe(whole * USDC_SCALE);
expect(formatContractUnitsToUsdc(units)).toBe(raw);
},
),
{ numRuns: 1_000 },
);
});
});

describe('migrations/billing_index.sql — EXPLAIN-verified hot path [b#057]', () => {
let db: ReturnType<typeof newDb>;

Expand All @@ -61,6 +165,7 @@ describe('migrations/billing_index.sql — EXPLAIN-verified hot path [b#057]', (
expect(() => db.public.none(upSql)).not.toThrow();
});


it('uses idx_billing_requests_lookup_hot for the hot developer_id filter', () => {
const upSql = readFileSync(path.join(migrationsDir, 'billing_index.sql'), 'utf8');
db.public.none(upSql);
Expand Down
Loading