import { describe, expect, it } from 'vitest'; import meta from './meta.json' with { type: 'json' }; import { localMeta } from '../_lib/local-meta.js'; import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import { createCrawlContext, createRouter, missingRequirements } from '@rareindex/connectors'; import createConnector, { offerProperties, slugForGame } from './index.js'; const connector = createConnector(localMeta(meta)); describe('cardtrader (gated)', () => { runFixtureSuite(connector, it, expect); it('is reported as gated until CARDTRADER_API_TOKEN exists and never crashes without it', async () => { expect(meta.requires).toEqual(['CARDTRADER_API_TOKEN']); const saved = process.env.CARDTRADER_API_TOKEN; delete process.env.CARDTRADER_API_TOKEN; try { expect(missingRequirements(connector.meta)).toEqual(['CARDTRADER_API_TOKEN']); const ctx = createCrawlContext({ router: createRouter({}), meta: connector.meta, options: { mode: 'probe', limit: 1 } }); const seen: unknown[] = []; for await (const r of connector.crawl(ctx)) seen.push(r); expect(seen).toEqual([]); expect(ctx.engineStats).toEqual({}); } finally { if (saved !== undefined) process.env.CARDTRADER_API_TOKEN = saved; } }); it('maps games and offer properties', () => { const map = meta.config.gameSlugs as Record; expect(slugForGame('Magic Magic: the Gathering', map)).toBe('magic_the_gathering'); expect(slugForGame('Pokemon', map)).toBe('pokemon'); expect(slugForGame('Warhammer 40k', map)).toBeNull(); const p = offerProperties({ condition: 'Moderately Played', signed: false, mtg_foil: true, mtg_language: 'en', altered: false }); expect(p).toMatchObject({ conditionRaw: 'Moderately Played', condition: 'very_good', language: 'English', foil: true, signed: false }); expect(offerProperties({ condition: 'Near Mint', pokemon_language: 'jp', pokemon_reverse: true }).reverse).toBe(true); }); it('emits a catalog item with cross-marketplace ids and one listing per public offer', async () => { for (const name of listFixtures('cardtrader')) { const fx = loadFixture('cardtrader', name); const out = await connector.normalize(fx.raw); const cats = out.filter((r) => r.kind === 'catalog_item'); expect(cats.length).toBe(1); const c = cats[0]!; if (c.kind === 'catalog_item') { expect(c.attributes.identifiers.cardtrader_blueprint_id).toMatch(/^\d+$/); expect(c.attributes.set).toBeTruthy(); } for (const r of out) { if (r.kind !== 'listing') continue; expect(['EUR', 'USD']).toContain(r.currency); expect(r.price).toBeGreaterThan(0); expect(r.listingType).toBe('fixed_price'); // private sellers' usernames are never stored if ((r.attributes.metadata as { seller_type: string }).seller_type !== 'professional') expect(r.seller).toBeNull(); } } }); });