import { describe, expect, it } from 'vitest'; import { getConnectorMeta } from '@rareindex/connectors'; import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { auctionPageUrl, lotUrl, parseAuctionList, parseAuctionPage, type PagePayload } from './index.js'; import { hintFromLabel, slugFromTitle, watchReference } from '../_auction-lib/categories.js'; const connector = createConnector(getConnectorMeta('bonhams')); describe('bonhams connector', () => { runFixtureSuite(connector, it, expect); it('stores the premium-inclusive price and keeps the hammer', async () => { const fx = loadFixture('bonhams', 'results-1'); const payload = fx.raw.payload as PagePayload; const out = await connector.normalize(fx.raw); const sold = payload.lots.filter((l) => l.status === 'SOLD' && (l.hammerPremium ?? 0) > 0); expect(out.length).toBe(sold.length); const lot = sold[0]!; const rec = out.find((r) => r.kind === 'sale' && r.externalId === `${payload.auction.id}-${lot.lotNo}`); expect(rec && rec.kind === 'sale').toBe(true); if (!rec || rec.kind !== 'sale') return; expect(rec.price).toBe(lot.hammerPremium); expect(rec.buyerPremiumIncluded).toBe(true); expect(rec.attributes.metadata.hammer_price).toBe(lot.hammerPrice); expect(rec.currency).toBe(lot.currency); expect(rec.saleDate.toISOString()).toBe(new Date(lot.hammerTime!).toISOString()); expect(rec.auctionHouse).toBe('Bonhams'); expect(rec.lotNumber).toBe(lot.lotNo); expect(rec.sourceUrl).toBe(lotUrl(payload.auction.id, lot.lotNo, lot.slug)); expect(['rolex', 'patek_philippe', 'audemars_piguet', 'omega', 'other_watches']).toContain(rec.attributes.categorySlug); }); it('emits auction_lot records for lots that have not ended', async () => { const fx = loadFixture('bonhams', 'results-1'); const payload = structuredClone(fx.raw.payload) as PagePayload; const future = new Date(Date.now() + 5 * 86_400_000).toISOString(); payload.auction = { ...payload.auction, isEnded: false, end: future, start: new Date(Date.now() - 86_400_000).toISOString() }; payload.lots = payload.lots.slice(0, 3).map((l) => ({ ...l, status: 'READY', hammerPrice: null, hammerPremium: null, isEnded: false, hammerTime: future, endDate: future })); const out = await connector.normalize({ ...fx.raw, payload }); expect(out.length).toBe(3); for (const r of out) { expect(r.kind).toBe('auction_lot'); if (r.kind !== 'auction_lot') continue; expect(r.status).toBe('live'); expect(r.estimateLow).not.toBeNull(); expect(r.endsAt?.toISOString()).toBe(new Date(future).toISOString()); } }); it('skips unsold, withdrawn and unmapped-department lots', async () => { const fx = loadFixture('bonhams', 'results-1'); const payload = structuredClone(fx.raw.payload) as PagePayload; payload.lots = [ { ...payload.lots[0]!, status: 'UNSOLD', hammerPrice: null, hammerPremium: null }, { ...payload.lots[1]!, status: 'WITHDRAWN' }, { ...payload.lots[2]!, department: 'Carpets, Rugs & Tapestries', title: 'A Persian rug' }, payload.lots[3]!, ]; payload.auction = { ...payload.auction, departments: ['Watches'] }; const out = await connector.normalize({ ...fx.raw, payload }); expect(out.map((r) => ('externalId' in r ? r.externalId : null))).toEqual([`${payload.auction.id}-${payload.lots[3]!.lotNo}`]); }); it('parses listing and auction pages defensively', () => { expect(parseAuctionList('')).toEqual({ auctions: [], nbHits: null }); expect(parseAuctionPage('')).toBeNull(); expect(auctionPageUrl({ id: '31992', slug: 'weekly-watches' }, 2)).toBe('https://www.bonhams.com/auction/31992/weekly-watches/?page=2'); }); it('maps departments and titles to taxonomy slugs', () => { expect(slugFromTitle('ROLEX. A STAINLESS STEEL AUTOMATIC CHRONOGRAPH WRISTWATCH REF 116500LN DAYTONA', hintFromLabel('Watches'))).toBe('rolex'); expect(watchReference('REF 116500LN DAYTONA')).toBe('116500LN'); expect(slugFromTitle('Château Lafite Rothschild 1982 (12 bottles)', hintFromLabel('Wine'))).toBe('wine'); expect(slugFromTitle('The Macallan 25 Year Old Sherry Oak', hintFromLabel('Whisky'))).toBe('whisky'); expect(slugFromTitle('1965 Ferrari 275 GTB Berlinetta Chassis no. 07589', hintFromLabel('Cars'))).toBe('automobiles'); expect(slugFromTitle('Hermès Birkin 30 Togo leather 2019', hintFromLabel('Designer Handbags & Fashion'))).toBe('luxury_handbags'); expect(slugFromTitle('A Gibson Les Paul Standard owned by ...', hintFromLabel('Popular Culture'))).toBe('musical_instruments'); expect(slugFromTitle('Star Wars 1977 original one-sheet poster', hintFromLabel('Popular Culture'))).toBe('movie_posters'); expect(slugFromTitle('Elizabeth II gold sovereign 1958', hintFromLabel('Coins, Medals and Banknotes'))).toBe('coins'); expect(slugFromTitle('Bank of England £5 banknote 1935', hintFromLabel('Coins, Medals and Banknotes'))).toBe('banknotes'); expect(slugFromTitle('A large ammonite fossil, Madagascar', hintFromLabel('Natural History'))).toBe('fossils'); expect(slugFromTitle('Untitled, acrylic on canvas', hintFromLabel('Post-War and Contemporary Art'))).toBe('contemporary_art'); }); });