import { describe, expect, it } from 'vitest'; import { getConnectorMeta } from '@rareindex/connectors'; import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { categoryPageAuctionIds, classifyLot, parseAuctionPage, parseLotPage, type AuctionLotsPayload, type LotPayload } from './index.js'; const connector = createConnector(getConnectorMeta('catawiki')); describe('catawiki connector', () => { runFixtureSuite(connector, it, expect); it('turns a closed, sold lot into a hammer-price sale in EUR with the source end time', async () => { const fx = loadFixture('catawiki', 'seeded-1'); const lot = fx.raw.payload as LotPayload; const out = await connector.normalize(fx.raw); expect(out).toHaveLength(1); const r = out[0]!; if (r.kind !== 'sale') throw new Error('expected sale'); expect(r.price).toBe(lot.bidding.finalBidEur); expect(r.currency).toBe('EUR'); expect(r.buyerPremiumIncluded).toBe(false); expect(r.saleDate.getTime()).toBe(lot.bidding.biddingEndTime); expect(r.auctionHouse).toBe('Catawiki'); expect(r.location).toBe(lot.sellerCountry); expect(r.attributes.identifiers.catawiki_lot_id).toBe(String(lot.id)); expect(r.attributes.metadata.estimate_min_eur).toBe(lot.estimateMinEur); expect(r.attributes.material).toBe('Wood'); expect(r.attributes.categorySlug).toBe('antiques'); }); it('drops closed-unsold lots and emits live lots as auction_lot', async () => { const fx = loadFixture('catawiki', 'seeded-1'); const lot = structuredClone(fx.raw.payload) as LotPayload; const unsold = { ...lot, bidding: { ...lot.bidding, sold: false } }; expect(await connector.normalize({ ...fx.raw, payload: unsold })).toHaveLength(0); const live = { ...lot, bidding: { ...lot.bidding, closed: false, sold: null, biddingEndTime: Date.now() + 3 * 86_400_000 } }; const out = await connector.normalize({ ...fx.raw, payload: live }); expect(out).toHaveLength(1); expect(out[0]!.kind).toBe('auction_lot'); if (out[0]!.kind === 'auction_lot') { expect(out[0]!.status).toBe('live'); expect(out[0]!.currentBid).toBe(lot.bidding.finalBidEur); expect(out[0]!.estimateLow).toBe(lot.estimateMinEur); } }); it('emits auction_lot records for a live themed auction and none for a stale one', async () => { const fx = loadFixture('catawiki', 'seeded-2'); const payload = structuredClone(fx.raw.payload) as AuctionLotsPayload; const future = new Date(Date.now() + 2 * 86_400_000).toISOString(); payload.auction = { ...payload.auction, closeAt: future, startAt: new Date(Date.now() - 86_400_000).toISOString() }; const out = await connector.normalize({ ...fx.raw, payload }); expect(out.length).toBeGreaterThan(payload.lots.length * 0.8); for (const r of out) { expect(r.kind).toBe('auction_lot'); if (r.kind !== 'auction_lot') continue; expect(r.endsAt?.toISOString()).toBe(new Date(future).toISOString()); expect(r.currency).toBe('EUR'); expect(['rolex', 'patek_philippe', 'audemars_piguet', 'omega', 'other_watches', 'pens', 'lighters', 'clocks', 'jewelry']).toContain(r.attributes.categorySlug); } const stale = { ...payload, auction: { ...payload.auction, closeAt: '2020-01-01T00:00:00Z' } }; expect(await connector.normalize({ ...fx.raw, payload: stale })).toHaveLength(0); }); it('classifies lots from category path, specs and titles', () => { const base: LotPayload = { kind: 'lot', seedHint: 'toys', id: 1, url: 'https://www.catawiki.com/en/l/1', title: 'LEGO - Star Wars - 10179 - Millennium Falcon UCS', subtitle: 'Sealed', description: null, images: [], categoryId: null, categoryUrl: null, auction: { id: 1, title: 'LEGO Auction', url: 'https://www.catawiki.com/en/a/1', status: 'closed', startAt: null, closeAt: null, closedAt: null, categories: ['Toys & Models', 'LEGO'], lotCount: null }, specs: [{ name: 'Set number', value: '10179' }], estimateMinEur: null, estimateMaxEur: null, sellerCountry: 'NL', sellerName: null, sellerIsPro: null, bidding: { closed: true, sold: true, finalBidEur: 2500, biddingStartTime: null, biddingEndTime: 1, bidCount: null, reservePriceMet: null } }; expect(classifyLot(base).slug).toBe('lego_sets'); expect(classifyLot({ ...base, seedHint: 'cards', title: 'Pokémon - Charizard Base Set 4/102 - PSA 9', auction: { ...base.auction!, categories: ['Trading Cards', 'Pokémon'] } }).slug).toBe('pokemon'); expect(classifyLot({ ...base, seedHint: 'watches', title: 'Rolex - Submariner 16610 - Men - 2005', auction: { ...base.auction!, categories: ['Watches, Pens & Lighters', 'Watches'] } }).slug).toBe('rolex'); expect(classifyLot({ ...base, seedHint: 'wine', title: 'Macallan 18 Sherry Oak 2020 release', auction: { ...base.auction!, categories: ['Wine, Whisky & Spirits', 'Whisky'] } }).slug).toBe('whisky'); expect(classifyLot({ ...base, seedHint: 'coins', title: 'Netherlands 10 Gulden 1875 gold', auction: { ...base.auction!, categories: ['Coins & Stamps', 'Coins'] } }).slug).toBe('coins'); expect(categoryPageAuctionIds('')).toEqual([1263062, 99]); expect(parseAuctionPage('')).toBeNull(); expect(parseLotPage('', 'https://www.catawiki.com/en/l/1', 'unknown')).toBeNull(); }); });