TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import meta from './meta.json' with { type: 'json' };3import { localMeta } from '../_lib/local-meta.js';4import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';5import { createCrawlContext, createRouter, missingRequirements } from '@rareindex/connectors';6import createConnector, { offerProperties, slugForGame } from './index.js';78const connector = createConnector(localMeta(meta));910describe('cardtrader (gated)', () => {11 runFixtureSuite(connector, it, expect);1213 it('is reported as gated until CARDTRADER_API_TOKEN exists and never crashes without it', async () => {14 expect(meta.requires).toEqual(['CARDTRADER_API_TOKEN']);15 const saved = process.env.CARDTRADER_API_TOKEN;16 delete process.env.CARDTRADER_API_TOKEN;17 try {18 expect(missingRequirements(connector.meta)).toEqual(['CARDTRADER_API_TOKEN']);19 const ctx = createCrawlContext({ router: createRouter({}), meta: connector.meta, options: { mode: 'probe', limit: 1 } });20 const seen: unknown[] = [];21 for await (const r of connector.crawl(ctx)) seen.push(r);22 expect(seen).toEqual([]);23 expect(ctx.engineStats).toEqual({});24 } finally {25 if (saved !== undefined) process.env.CARDTRADER_API_TOKEN = saved;26 }27 });2829 it('maps games and offer properties', () => {30 const map = meta.config.gameSlugs as Record<string, string>;31 expect(slugForGame('Magic Magic: the Gathering', map)).toBe('magic_the_gathering');32 expect(slugForGame('Pokemon', map)).toBe('pokemon');33 expect(slugForGame('Warhammer 40k', map)).toBeNull();34 const p = offerProperties({ condition: 'Moderately Played', signed: false, mtg_foil: true, mtg_language: 'en', altered: false });35 expect(p).toMatchObject({ conditionRaw: 'Moderately Played', condition: 'very_good', language: 'English', foil: true, signed: false });36 expect(offerProperties({ condition: 'Near Mint', pokemon_language: 'jp', pokemon_reverse: true }).reverse).toBe(true);37 });3839 it('emits a catalog item with cross-marketplace ids and one listing per public offer', async () => {40 for (const name of listFixtures('cardtrader')) {41 const fx = loadFixture('cardtrader', name);42 const out = await connector.normalize(fx.raw);43 const cats = out.filter((r) => r.kind === 'catalog_item');44 expect(cats.length).toBe(1);45 const c = cats[0]!;46 if (c.kind === 'catalog_item') {47 expect(c.attributes.identifiers.cardtrader_blueprint_id).toMatch(/^\d+$/);48 expect(c.attributes.set).toBeTruthy();49 }50 for (const r of out) {51 if (r.kind !== 'listing') continue;52 expect(['EUR', 'USD']).toContain(r.currency);53 expect(r.price).toBeGreaterThan(0);54 expect(r.listingType).toBe('fixed_price');55 // private sellers' usernames are never stored56 if ((r.attributes.metadata as { seller_type: string }).seller_type !== 'professional') expect(r.seller).toBeNull();57 }58 }59 });60});61