import { describe, expect, it } from 'vitest'; import { readFileSync } from 'node:fs'; import path from 'node:path'; import meta from './meta.json' with { type: 'json' }; import { localMeta } from '../_lib/local-meta.js'; import { fixtureDir, listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { parseCardPage, parseSetIndex, parseSetList, parseShortDate, tcgplayerIdFromUrl } from './index.js'; import { usdEur } from '../_g1-cards-eu-jp-lib/index.js'; const connector = createConnector(localMeta(meta)); const snap = (name: string) => readFileSync(path.join(fixtureDir('limitless-tcg'), name), 'utf8'); describe('limitless-tcg', () => { runFixtureSuite(connector, it, expect); it('parses Limitless dates, prices and TCGplayer ids', () => { expect(parseShortDate('26 Sep 25')).toBe('2025-09-26'); expect(parseShortDate('17 Jul 26')).toBe('2026-07-17'); expect(parseShortDate('n/a')).toBeNull(); expect(usdEur('$0.25')).toEqual({ amount: 0.25, currency: 'USD' }); expect(usdEur('1,585.73€')).toEqual({ amount: 1585.73, currency: 'EUR' }); expect(usdEur('')).toBeNull(); expect(tcgplayerIdFromUrl('https://partner.tcgplayer.com/LIMITLESS?u=https%3A%2F%2Fwww.tcgplayer.com%2Fproduct%2F654340%2Fpokemon-me01')).toBe('654340'); }); it('parses the saved set index, set list and card page HTML', () => { const idx = parseSetIndex(snap('sets-index.html'), 'en'); expect(idx.length).toBeGreaterThan(50); expect(idx[0]).toMatchObject({ lang: 'en' }); expect(idx.every((s) => /^[A-Z0-9-]+$/i.test(s.code))).toBe(true); expect(idx.some((s) => s.releaseDate && /^\d{4}-\d{2}-\d{2}$/.test(s.releaseDate))).toBe(true); const list = parseSetList(snap('set-list.html')); expect(list.setName).toBeTruthy(); expect(list.cards.length).toBeGreaterThan(100); const first = list.cards[0]!; expect(first.number).toBe('1'); expect(first.url).toMatch(/^https:\/\/limitlesstcg\.com\/cards\//); expect(first.image).toMatch(/\.png$/); expect(first.usd?.currency).toBe('USD'); expect(first.eur?.currency).toBe('EUR'); expect(first.usd?.tcgplayerId).toMatch(/^\d+$/); const cp = parseCardPage(snap('card-page.html')); expect(cp.cardId).toBeGreaterThan(0); expect(cp.name).toBeTruthy(); expect(cp.number).toBeTruthy(); }); it('emits catalog items with relayed USD/EUR observations for English sets and none for Japanese sets', async () => { const en = loadFixture('limitless-tcg', 'en-set-list'); const out = await connector.normalize(en.raw); const obs = out.filter((r) => r.kind === 'price_observation'); expect(obs.length).toBeGreaterThan(0); for (const o of obs) { if (o.kind !== 'price_observation') continue; expect(['USD', 'EUR']).toContain(o.currency); expect(['tcgplayer', 'cardmarket']).toContain((o.attributes.metadata as { provider: string }).provider); expect(o.confidence).toBeLessThanOrEqual(0.6); } const jp = loadFixture('limitless-tcg', 'jp-set-list-no-prices'); const jout = await connector.normalize(jp.raw); expect(jout.every((r) => r.kind === 'catalog_item')).toBe(true); for (const r of jout) if (r.kind === 'catalog_item') expect(r.attributes.language).toBe('Japanese'); }); it('turns the price-history XHR into dated observations (cents → units, one per day per provider)', async () => { const names = listFixtures('limitless-tcg'); const fx = loadFixture('limitless-tcg', names.find((n) => n === 'card-price-history')!); const out = await connector.normalize(fx.raw); expect(out.length).toBeGreaterThan(0); const dates = new Set(); for (const o of out) { if (o.kind !== 'price_observation') continue; expect(o.observationDate.getUTCHours()).toBe(0); expect(o.observationDate.getTime()).toBeLessThan(fx.raw.fetchedAt.getTime()); expect(o.price).toBeLessThan(10_000); const k = `${(o.attributes.metadata as { provider: string }).provider}|${o.observationDate.toISOString()}`; expect(dates.has(k)).toBe(false); dates.add(k); } }); });