deckhearth/test/lib/pricing/tcgdex-client.test.js
Randall Stillwell d0468015eb
Some checks failed
CI / Lint (pull_request) Has been cancelled
CI / Schema map up to date (pull_request) Has been cancelled
CI / Forbidden patterns (9 checks) (pull_request) Has been cancelled
CI / Migrations apply (node-pg-migrate) (pull_request) Has been cancelled
Visual diff / Should run? (pull_request) Has been cancelled
CI / Unit tests (vitest) (pull_request) Has been cancelled
Convoy metrics gate / Require role-event telemetry on convoy PRs (pull_request) Has been cancelled
PR Health rollup / Aggregate gate status (pull_request) Has been cancelled
Preview smoke / Should run? (pull_request) Has been cancelled
Preview smoke / Playwright smoke (pull_request) Has been cancelled
Visual diff / Screenshot diff (pull_request) Has been cancelled
feat(pricing): convoy for online prices and local stores
Add pricing schema, free API integrations (MTG bulk, TCGdex, Foolio),
valuation and sort-by-price, card detail pricing UI, and nearby store
discovery via home zip and OSM geo clients.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 22:47:46 -05:00

120 lines
2.9 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
buildTcgdexPriceIndex,
getTcgdexBaseUrl,
matchTcgdexCard,
parseTcgdexPricing,
} from '../../../lib/pricing/tcgdex-client.js';
describe('tcgdex-client', () => {
beforeEach(() => {
delete process.env.TCGDEX_BASE_URL;
vi.restoreAllMocks();
});
it('uses default TCGdex base URL', () => {
expect(getTcgdexBaseUrl()).toBe('https://api.tcgdex.net/v2/en');
});
it('parses TCGPlayer USD pricing into current and market prices', () => {
const parsed = parseTcgdexPricing({
pricing: {
tcgplayer: {
normal: {
lowPrice: 0.5,
marketPrice: 1.25,
},
},
},
});
expect(parsed).toEqual({
currentPrice: 0.5,
marketPrice: 1.25,
currency: 'USD',
});
});
it('falls back to Cardmarket EUR for market_price when USD is missing', () => {
const parsed = parseTcgdexPricing({
pricing: {
cardmarket: {
trend: 2.5,
unit: 'EUR',
},
},
});
expect(parsed.currentPrice).toBeNull();
expect(parsed.marketPrice).toBe(2.5);
expect(parsed.currency).toBe('EUR');
});
it('matches cards by set card number with leading zero normalization', () => {
const index = buildTcgdexPriceIndex([
{
id: 'me3-18',
localId: '18',
name: 'Seel',
set: { cardCount: { official: 88 } },
},
]);
const match = matchTcgdexCard(index, {
name: 'Seel',
cardNumber: '018/88',
});
expect(match?.id).toBe('me3-18');
});
it('falls back to unique name match within a set index', () => {
const index = buildTcgdexPriceIndex([
{ id: 'sv1-001', localId: '1', name: 'Sprigatito' },
]);
const match = matchTcgdexCard(index, {
name: 'Sprigatito',
cardNumber: null,
});
expect(match?.id).toBe('sv1-001');
});
it('fetchSetCards loads set summaries and full card details', async () => {
const fetchMock = vi.fn(async (url) => {
if (url.endsWith('/sets/me3')) {
return {
ok: true,
json: async () => ({
cards: [{ id: 'me3-18', localId: '18', name: 'Seel' }],
}),
};
}
if (url.endsWith('/cards/me3-18')) {
return {
ok: true,
json: async () => ({
id: 'me3-18',
localId: '18',
name: 'Seel',
pricing: {
tcgplayer: { normal: { lowPrice: 0.1, marketPrice: 0.2 } },
},
}),
};
}
throw new Error(`unexpected url ${url}`);
});
vi.stubGlobal('fetch', fetchMock);
const { fetchSetCards } = await import('../../../lib/pricing/tcgdex-client.js');
const cards = await fetchSetCards('me3');
expect(cards).toHaveLength(1);
expect(cards[0].pricing.tcgplayer.normal.marketPrice).toBe(0.2);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
});