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%
8.9 KB · 138 lines typescript
Raw Blame History
1import { describe, expect, it } from 'vitest';2import { readFileSync } from 'node:fs';3import path from 'node:path';4import { fileURLToPath } from 'node:url';5import { runFixtureSuite } from '@rareindex/connectors/testing';6import { localMeta } from '../_lib/local-meta.js';7import createConnector, { parseItemPage, salesHistorySitemaps } from './index.js';89const dir = path.dirname(fileURLToPath(import.meta.url));10const meta = localMeta(JSON.parse(readFileSync(path.join(dir, 'meta.json'), 'utf8')));11const connector = createConnector(meta);1213/** Real prefetchedItemData captured live from https://www.fanaticscollect.com/weekly/1e389fce-6f23-11ed-a610-0a58a9feac02 (2026-09-08), trimmed. */14const SOLD_LISTING = {15  __typename: 'CollectListing',16  id: '1e389fce-6f23-11ed-a610-0a58a9feac02',17  title: '2003 Fleer Tradition LeBron James ROOKIE #261 PSA 9 MINT',18  listingType: 'WEEKLY',19  isManageable: false,20  certifiedSeller: 'Fanatics Collect',21  currentBid: { __typename: 'Money', amountInCents: 23000, currency: 'USD' },22  auction: { __typename: 'CollectWeeklyAuction', id: '8d27de96-6f1d-11ed-ab20-0a58a9feac02', auctionIntegerId: 30, integerId: 172, name: '2022 Weekly Auction #1', shortName: 'WA1', payoutDate: '2022-02-16', startsAt: '2022-01-14T03:00:00Z', endsAt: '2022-01-24T03:00:00Z', status: 'CLOSED' },23  integerId: 3093498,24  insertedAt: '2021-12-22T13:41:22Z',25  updatedAt: '2023-01-09T18:38:25Z',26  soldDate: null,27  lotString: 'WA1 Lot: 1100',28  imageSets: [{ __typename: 'CollectImageSet', large: 'https://dilxwvfkfup17.cloudfront.net/abc-large', medium: 'https://dilxwvfkfup17.cloudfront.net/abc-medium' }],29  slug: null,30  soldFor: null,31  startingPrice: { __typename: 'Money', amountInCents: 500, currency: 'USD' },32  description: 'Welcome to our first Weekly Sunday Auction! This is one of over 3,600 items spanning all sports and genres, closing on the evening of Sunday, January 23rd.',33  status: 'STATUS_SOLD',34  bidCount: 27,35  collectSales: [{ __typename: 'CollectSale', soldDate: '2022-01-24T04:10:06Z', soldFor: { __typename: 'Money', amountInCents: 27600, currency: 'USD' } }],36  vaultItem: { __typename: 'CollectVaultItem', id: '0bf62e5c-6f1f-11ed-a6c4-0a58a9feac02', integerId: 732786 },37};3839/** Real fixed-price (best offer) listing from /fixed/eb86cdda-a912-11f0-8fa5-0a58a9feac02/… — sold via offer, but no CollectSale exposed. */40const FIXED_LISTING = {41  __typename: 'CollectListing',42  id: '178984d1-fd50-473c-8e6d-3e96d63b5ba3',43  title: '2000 Pokemon Insert Card Ancient Mew CGC 5.5 EX+',44  listingType: 'BO',45  status: 'BO_STATUS_SOLD',46  soldDate: null,47  soldFor: null,48  startingPrice: { __typename: 'Money', amountInCents: 0, currency: 'USD' },49  collectSales: [],50  vaultItem: { __typename: 'CollectVaultItem', id: '60ecaa72-89d8-11f0-85e1-02f5d6e69f97' },51  imageSets: [{ __typename: 'CollectImageSet', large: 'https://cdn-vault.fanaticscollect.com/2025/9/4/rm2/large/v1503807_2020090306325159R_23.jpg' }],52  slug: '2000-pokemon-insert-card-ancient-mew-cgc-55-ex',53  description: '',54};5556/** Wrap an object in a Next.js flight chunk the way the live page does (JS-string escaped JSON). */57function flightHtml(listing: unknown): string {58  const line = `2f:["$","$L42",null,{"prefetchedItemData":${JSON.stringify(listing)},"foo":1}]\n`;59  return `<html><head><title>x</title></head><body><script>self.__next_f.push([1,"1:\\"$Sreact.fragment\\"\\n"])</script><script>self.__next_f.push([1,${JSON.stringify(line)}])</script></body></html>`;60}6162const SITEMAP_INDEX = `<?xml version="1.0" encoding="UTF-8"?><sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">63<sitemap><loc>https://www.fanaticscollect.com/sitemap/pages.xml.gz</loc></sitemap>64<sitemap><loc>https://www.fanaticscollect.com/sitemap/weekly-auction-1.xml.gz</loc></sitemap>65<sitemap><loc>https://www.fanaticscollect.com/sitemap/sales-history-weekly-auction-1.xml.gz</loc></sitemap>66<sitemap><loc>https://www.fanaticscollect.com/sitemap/sales-history-weekly-auction-2.xml.gz</loc></sitemap>67<sitemap><loc>https://www.fanaticscollect.com/sitemap/sales-history-fixed-price-1.xml.gz</loc></sitemap>68<sitemap><loc>https://www.fanaticscollect.com/sitemap/sales-history-fixed-price-7.xml.gz</loc></sitemap>69</sitemapindex>`;7071describe('fanatics-collect', () => {72  runFixtureSuite(connector, it, expect);7374  it('parses the RSC flight payload of a sold weekly lot', () => {75    const url = 'https://www.fanaticscollect.com/weekly/1e389fce-6f23-11ed-a610-0a58a9feac02';76    const p = parseItemPage(flightHtml(SOLD_LISTING), url, '2023-01-09')!;77    expect(p.kind).toBe('fc_item');78    expect(p.listing).toMatchObject({ id: '1e389fce-6f23-11ed-a610-0a58a9feac02', listingType: 'WEEKLY', status: 'STATUS_SOLD', bidCount: 27, lotString: 'WA1 Lot: 1100' });79    expect(p.listing.collectSales[0]).toEqual({ soldDate: '2022-01-24T04:10:06Z', soldFor: { amountInCents: 27600, currency: 'USD' } });80    expect(p.listing.images).toEqual(['https://dilxwvfkfup17.cloudfront.net/abc-large']);81    expect(p.listing.auction).toMatchObject({ shortName: 'WA1', endsAt: '2022-01-24T03:00:00Z' });82    expect((p.listing as Record<string, unknown>).__typename).toBeUndefined();83  });8485  it('normalises a sale with buyer premium included, hammer in metadata, PSA grade and lot number', async () => {86    const url = 'https://www.fanaticscollect.com/weekly/1e389fce-6f23-11ed-a610-0a58a9feac02';87    const p = parseItemPage(flightHtml(SOLD_LISTING), url, '2023-01-09')!;88    const out = await connector.normalize({ url, externalId: p.listing.id, kind: 'sale', engine: 'api', fetchedAt: new Date('2026-09-08T10:00:00Z'), payload: p });89    expect(out.length).toBe(1);90    const s = out[0]!;91    if (s.kind !== 'sale') throw new Error('expected sale');92    expect(s).toMatchObject({ price: 276, currency: 'USD', buyerPremiumIncluded: true, auctionHouse: 'Fanatics Collect', lotNumber: '1100', saleType: 'auction', location: 'US', externalId: '1e389fce-6f23-11ed-a610-0a58a9feac02' });93    expect(s.saleDate.toISOString()).toBe('2022-01-24T04:10:06.000Z');94    expect(s.attributes.categorySlug).toBe('basketball_cards');95    expect(s.attributes.year).toBe(2003);96    expect(s.attributes.metadata).toMatchObject({ hammer_price: 230, buyer_premium_pct: 20, auction_short_name: 'WA1', listing_type: 'WEEKLY' });97    expect(s.attributes.identifiers).toEqual({ fanatics_listing_id: '1e389fce-6f23-11ed-a610-0a58a9feac02', fanatics_vault_item_id: '0bf62e5c-6f1f-11ed-a6c4-0a58a9feac02' });98    expect(s.grade).toMatchObject({ grader: 'psa', grade: '9' });99    expect(s.confidence).toBe(0.9);100  });101102  it('skips fixed-price pages that expose no CollectSale', async () => {103    const url = 'https://www.fanaticscollect.com/fixed/eb86cdda-a912-11f0-8fa5-0a58a9feac02/2000-pokemon-insert-card-ancient-mew-cgc-55-ex';104    const p = parseItemPage(flightHtml(FIXED_LISTING), url)!;105    expect(p.listing.listingType).toBe('BO');106    expect(p.listing.collectSales).toEqual([]);107    expect(await connector.normalize({ url, externalId: p.listing.id, kind: 'sale', engine: 'api', fetchedAt: new Date(), payload: p })).toEqual([]);108  });109110  it('records fixed-price sales without a premium flag when a CollectSale exists', async () => {111    const url = 'https://www.fanaticscollect.com/fixed/178984d1-fd50-473c-8e6d-3e96d63b5ba3/x';112    const p = parseItemPage(flightHtml({ ...FIXED_LISTING, collectSales: [{ __typename: 'CollectSale', soldDate: '2025-10-22T09:59:16.000Z', soldFor: { __typename: 'Money', amountInCents: 500, currency: 'USD' } }] }), url)!;113    const out = await connector.normalize({ url, externalId: p.listing.id, kind: 'sale', engine: 'api', fetchedAt: new Date(), payload: p });114    expect(out.length).toBe(1);115    const s = out[0]!;116    if (s.kind !== 'sale') throw new Error('expected sale');117    expect(s).toMatchObject({ price: 5, saleType: 'fixed_price', buyerPremiumIncluded: null, lotNumber: null });118    expect(s.attributes.categorySlug).toBe('pokemon');119    expect(s.grade).toMatchObject({ grader: 'cgc', grade: '5.5' });120  });121122  it('returns null for pages without listing JSON and orders sales-history sitemaps newest first', () => {123    expect(parseItemPage('<html><body>nothing</body></html>', 'https://www.fanaticscollect.com/weekly/x')).toBeNull();124    expect(salesHistorySitemaps(SITEMAP_INDEX)).toEqual([125      'https://www.fanaticscollect.com/sitemap/sales-history-fixed-price-7.xml.gz',126      'https://www.fanaticscollect.com/sitemap/sales-history-weekly-auction-2.xml.gz',127      'https://www.fanaticscollect.com/sitemap/sales-history-fixed-price-1.xml.gz',128      'https://www.fanaticscollect.com/sitemap/sales-history-weekly-auction-1.xml.gz',129    ]);130  });131132  it('recognises item URLs for lookup', () => {133    expect(connector.urlPatterns![0]!.test('https://www.fanaticscollect.com/weekly/1e389fce-6f23-11ed-a610-0a58a9feac02')).toBe(true);134    expect(connector.urlPatterns![0]!.test('https://www.fanaticscollect.com/fixed/eb86cdda-a912-11f0-8fa5-0a58a9feac02/slug')).toBe(true);135    expect(connector.urlPatterns![0]!.test('https://www.fanaticscollect.com/marketplace?type=WEEKLY')).toBe(false);136  });137});138