feat: add exact signed money parsing and formatting
This commit is contained in:
@@ -123,6 +123,14 @@ describe("central ESI client", () => {
|
||||
});
|
||||
|
||||
describe("ESI market estimates", () => {
|
||||
test("keeps the inherited non-negative market-price policy", async () => {
|
||||
const queued = queueFetch([
|
||||
response([{ type_id: 34, average_price: -1 }], { ETag: '"negative"', "Cache-Control": "max-age=60" }),
|
||||
]);
|
||||
const client = new EsiClient({ userAgent: "test", fetch: queued.fetch, now: () => 1234 });
|
||||
await expect(new EsiMarketEstimates(client).estimates([34])).rejects.toMatchObject({ code: "invalid_price" });
|
||||
});
|
||||
|
||||
test("uses average prices only and returns cents with retrieval provenance", async () => {
|
||||
const queued = queueFetch([
|
||||
response([
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import {
|
||||
decimalIskToCents,
|
||||
validateQuantity,
|
||||
} from '../src/lib/domain/money';
|
||||
import { formatIskAmount, formatQuantity } from '../src/lib/domain/formatting';
|
||||
|
||||
describe('exact decimal ISK conversion', () => {
|
||||
test('preserves the existing unsigned ESI syntax and positive half-up rounding', () => {
|
||||
expect(decimalIskToCents('0')).toBe('0');
|
||||
expect(decimalIskToCents('12.34')).toBe('1234');
|
||||
expect(decimalIskToCents('12.345')).toBe('1235');
|
||||
expect(decimalIskToCents('12.344')).toBe('1234');
|
||||
expect(decimalIskToCents('1e2')).toBe('10000');
|
||||
expect(decimalIskToCents('1.2e-1')).toBe('12');
|
||||
expect(decimalIskToCents('0.000e1000')).toBe('0');
|
||||
});
|
||||
|
||||
test('rounds signed values half away from zero and normalizes negative zero', () => {
|
||||
expect(decimalIskToCents('1.005')).toBe('101');
|
||||
expect(decimalIskToCents('-1.005')).toBe('-101');
|
||||
expect(decimalIskToCents('1.004')).toBe('100');
|
||||
expect(decimalIskToCents('-1.004')).toBe('-100');
|
||||
expect(decimalIskToCents('-0')).toBe('0');
|
||||
expect(decimalIskToCents('-0.004')).toBe('0');
|
||||
expect(decimalIskToCents('-0.005')).toBe('-1');
|
||||
});
|
||||
|
||||
test('rejects malformed syntax, strict trailing-newline bypasses, and plus signs', () => {
|
||||
for (const value of ['+1', '01', '1.', '.1', 'NaN', 'Infinity', '1_000', ' 1', '1 ', '١', '1\n', '1\r', '1e2\n']) {
|
||||
expect(() => decimalIskToCents(value)).toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
test('enforces exponent, shift, coefficient, and raw-input limits without truncation', () => {
|
||||
expect(decimalIskToCents(`1e${'0'.repeat(8190)}`)).toBe('100');
|
||||
expect(() => decimalIskToCents(`1e${'0'.repeat(8191)}`)).toThrow('input exceeds resource bounds');
|
||||
expect(() => decimalIskToCents('1e1001')).toThrow('exponent is outside bounds');
|
||||
expect(() => decimalIskToCents('1e-1001')).toThrow('exponent is outside bounds');
|
||||
expect(decimalIskToCents('1e1000')).toBe(`1${'0'.repeat(1002)}`);
|
||||
expect(decimalIskToCents(`0.${'0'.repeat(999)}1`)).toBe('0');
|
||||
expect(() => decimalIskToCents(`0.${'0'.repeat(1000)}1`)).toThrow('shift exceeds resource bounds');
|
||||
expect(decimalIskToCents('1'.repeat(4096))).toBe(`${BigInt('1'.repeat(4096)) * 100n}`);
|
||||
expect(() => decimalIskToCents('1'.repeat(4097))).toThrow('coefficient exceeds resource bounds');
|
||||
});
|
||||
});
|
||||
|
||||
describe('shared exact money formatting', () => {
|
||||
test('formats signed cents with correct fractional sign and grouping', () => {
|
||||
expect(formatIskAmount('0')).toBe('0.00');
|
||||
expect(formatIskAmount('123456789')).toBe('1,234,567.89');
|
||||
expect(formatIskAmount('-1')).toBe('-0.01');
|
||||
expect(formatIskAmount('-99')).toBe('-0.99');
|
||||
expect(formatIskAmount('-100')).toBe('-1.00');
|
||||
expect(formatIskAmount('-101')).toBe('-1.01');
|
||||
expect(formatIskAmount('-123456789')).toBe('-1,234,567.89');
|
||||
expect(formatIskAmount(-1n)).toBe('-0.01');
|
||||
});
|
||||
|
||||
test('formats quantities without coercing bigint or changing quantity policy', () => {
|
||||
const large = 2n ** 100n;
|
||||
expect(validateQuantity(Number.MAX_SAFE_INTEGER)).toBe(BigInt(Number.MAX_SAFE_INTEGER));
|
||||
expect(validateQuantity(large)).toBe(large);
|
||||
expect(formatQuantity(large)).toBe('1,267,650,600,228,229,401,496,703,205,376');
|
||||
expect(() => formatQuantity(-1)).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user