SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
7.9 KB · 128 lines typescript
Raw Blame History
1import { describe, expect, it } from 'vitest';2import { getConnectorMeta } from '@rareindex/connectors';3import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';4import createConnector, { parseProductPage } from './index.js';56const connector = createConnector(getConnectorMeta('pricecharting'));78describe('pricecharting', () => {9  runFixtureSuite(connector, it, expect);1011  it('maps a video game page to catalog + guide values + eBay sales', async () => {12    const fx = loadFixture('pricecharting', 'nintendo-64__zelda-ocarina-of-time');13    const out = await connector.normalize(fx.raw);14    const catalog = out.find((r) => r.kind === 'catalog_item');15    expect(catalog).toBeTruthy();16    if (catalog?.kind !== 'catalog_item') throw new Error();17    expect(catalog.attributes.categorySlug).toBe('nintendo_games');18    expect(catalog.attributes.set).toBe('Nintendo 64');19    expect(catalog.attributes.name).toBe('Zelda Ocarina of Time');20    expect(catalog.attributes.identifiers.pricecharting_id).toBe('3977');21    expect(catalog.attributes.identifiers.upc).toMatch(/^\d{12}$/);22    const guides = out.filter((r) => r.kind === 'price_observation');23    expect(guides.map((g) => g.kind === 'price_observation' && g.condition.completeness)).toEqual(expect.arrayContaining(['loose', 'cib', 'sealed']));24    const sales = out.filter((r) => r.kind === 'sale');25    expect(sales.length).toBeGreaterThan(50);26    const s = sales[0];27    if (s?.kind !== 'sale') throw new Error();28    expect(s.currency).toBe('USD');29    expect(s.externalId).toMatch(/^ebay:\d+$/);30    expect(s.saleDate.getUTCFullYear()).toBeGreaterThanOrEqual(2024);31    expect(['loose', 'cib', 'sealed', 'box_only', 'manual_only', null]).toContain(s.condition.completeness);32  });3334  it('maps LEGO, Funko and comics pages to the right categories', async () => {35    const lego = await connector.normalize(loadFixture('pricecharting', 'lego-star-wars__cloud-city-10123').raw);36    const legoCat = lego.find((r) => r.kind === 'catalog_item');37    if (legoCat?.kind !== 'catalog_item') throw new Error();38    expect(legoCat.attributes.categorySlug).toBe('lego_sets');39    expect(legoCat.attributes.number).toBe('10123');40    expect(legoCat.attributes.identifiers.lego_set_number).toBe('10123');41    expect(legoCat.attributes.set).toBe('Star Wars');4243    const funko = await connector.normalize(loadFixture('pricecharting', 'funko-pop-marvel__spider-man-metallic-3').raw);44    const funkoCat = funko.find((r) => r.kind === 'catalog_item');45    if (funkoCat?.kind !== 'catalog_item') throw new Error();46    expect(funkoCat.attributes.categorySlug).toBe('funko');47    expect(funkoCat.attributes.name).toBe('Spider-Man');48    expect(funkoCat.attributes.variant).toBe('Metallic');49    expect(funkoCat.attributes.number).toBe('3');5051    const comic = await connector.normalize(loadFixture('pricecharting', 'comic-books-amazing-spider-man__amazing-spider-man-300-1988').raw);52    const comicCat = comic.find((r) => r.kind === 'catalog_item');53    if (comicCat?.kind !== 'catalog_item') throw new Error();54    expect(comicCat.attributes.categorySlug).toBe('marvel_comics');55    expect(comicCat.attributes.number).toBe('300');56    expect(comicCat.attributes.year).toBe(1988);57    const graded98 = comic.find((r) => r.kind === 'price_observation' && r.grade.grade === '9.8');58    expect(graded98).toBeTruthy();59  });6061  it('parses the product page HTML deterministically', () => {62    const html = `<html><body><h1 id="product_name">Super Mario 64 [Player's Choice] <a href="/console/nintendo-64">Nintendo 64</a></h1>63      <script>VGPC.product = { id: 3924, is_comic: false, is_lego_set: false, is_funko_pop: false, is_card: false };</script>64      <table id="price_data"><tbody><tr><td id="used_price"><span class="price js-price">$39.99</span></td><td id="complete_price"><span class="price js-price">$134.48</span></td><td id="new_price"><span class="price js-price">$1,628.37</span></td></tr></tbody></table>65      <div class="completed-auctions-used"><table><tbody><tr id="ebay-1"><td class="date">2026-09-05</td><td class="title"><a href="https://www.ebay.com/itm/1">Mario 64 cart</a></td><td class="numeric"><span class="js-price">$37.95</span></td></tr></tbody></table></div>66      <table><tr><td class="title">UPC:</td><td class="details">045496870010</td></tr></table></body></html>`;67    const p = parseProductPage(html, 'https://www.pricecharting.com/game/nintendo-64/super-mario-64');68    expect(p.title).toBe("Super Mario 64 [Player's Choice]");69    expect(p.consoleUri).toBe('nintendo-64');70    expect(p.productId).toBe('3924');71    expect(p.prices.map((x) => x.value)).toEqual([39.99, 134.48, 1628.37]);72    expect(p.sales).toEqual([{ tab: 'used', date: '2026-09-05', title: 'Mario 64 cart', price: 37.95, ebayId: '1', listedPrice: null }]);73    expect(p.details['UPC']).toBe('045496870010');74  });75});7677describe('pricecharting cards', () => {78  it('maps a Pokémon Base Set card to the pokemontcg set code and per-grade sales', async () => {79    const out = await connector.normalize(loadFixture('pricecharting', 'pokemon-base-set__charizard-4').raw);80    const cat = out.find((r) => r.kind === 'catalog_item');81    if (cat?.kind !== 'catalog_item') throw new Error('no catalog item');82    expect(cat.attributes.categorySlug).toBe('pokemon');83    expect(cat.attributes.setCode).toBe('BS');84    expect(cat.attributes.number).toBe('4');85    expect(cat.attributes.name).toBe('Charizard');86    expect(cat.attributes.identifiers.pokemontcg_id).toBe('base1-4');87    expect(cat.attributes.identifiers.tcgplayer_id).toBe('42382');88    const guides = out.filter((r) => r.kind === 'price_observation');89    expect(guides.some((g) => g.kind === 'price_observation' && g.grade.grader === 'psa' && g.grade.grade === '10')).toBe(true);90    expect(guides.some((g) => g.kind === 'price_observation' && g.grade.grader === 'bgs' && g.grade.qualifier === 'Black Label')).toBe(true);91    const sales = out.filter((r) => r.kind === 'sale');92    expect(sales.length).toBeGreaterThan(10);93    expect(sales.some((s) => s.kind === 'sale' && s.grade.grader === 'psa' && s.grade.grade === '10')).toBe(true);94    expect(sales.some((s) => s.kind === 'sale' && s.grade.grader === null && s.grade.grade === null)).toBe(true);95  });9697  it('keeps 1st Edition printings separate from the unlimited pokemontcg id', async () => {98    const out = await connector.normalize(loadFixture('pricecharting', 'pokemon-base-set__charizard-1st-edition-4').raw);99    const cat = out.find((r) => r.kind === 'catalog_item');100    if (cat?.kind !== 'catalog_item') throw new Error('no catalog item');101    expect(cat.attributes.variant).toBe('1st Edition');102    expect(cat.attributes.identifiers.pokemontcg_id).toBeUndefined();103    expect(cat.attributes.setCode).toBe('BS');104  });105106  it('resolves a Magic card to its Scryfall id and collector number', async () => {107    const out = await connector.normalize(loadFixture('pricecharting', 'magic-alpha__black-lotus').raw);108    const cat = out.find((r) => r.kind === 'catalog_item');109    if (cat?.kind !== 'catalog_item') throw new Error('no catalog item');110    expect(cat.attributes.categorySlug).toBe('magic_the_gathering');111    expect(cat.attributes.setCode).toBe('LEA');112    expect(cat.attributes.set).toBe('Limited Edition Alpha');113    expect(cat.attributes.number).toBe('232');114    expect(cat.attributes.identifiers.scryfall_id).toMatch(/^[0-9a-f-]{36}$/);115  });116117  it('parses Yu-Gi-Oh! printed codes into set code + number', async () => {118    const out = await connector.normalize(loadFixture('pricecharting', 'yugioh-lob__blue-eyes-1st-edition').raw);119    const cat = out.find((r) => r.kind === 'catalog_item');120    if (cat?.kind !== 'catalog_item') throw new Error('no catalog item');121    expect(cat.attributes.categorySlug).toBe('yugioh');122    expect(cat.attributes.setCode).toBe('LOB');123    expect(cat.attributes.number).toBe('LOB-001');124    expect(cat.attributes.name).toBe('Blue-Eyes White Dragon');125    expect(cat.attributes.variant).toBe('1st Edition');126  });127});128