TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import { getConnectorMeta } from '@rareindex/connectors';3import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';4import createConnector, { auctionPageUrl, lotUrl, parseAuctionList, parseAuctionPage, type PagePayload } from './index.js';5import { hintFromLabel, slugFromTitle, watchReference } from '../_auction-lib/categories.js';67const connector = createConnector(getConnectorMeta('bonhams'));89describe('bonhams connector', () => {10 runFixtureSuite(connector, it, expect);1112 it('stores the premium-inclusive price and keeps the hammer', async () => {13 const fx = loadFixture('bonhams', 'results-1');14 const payload = fx.raw.payload as PagePayload;15 const out = await connector.normalize(fx.raw);16 const sold = payload.lots.filter((l) => l.status === 'SOLD' && (l.hammerPremium ?? 0) > 0);17 expect(out.length).toBe(sold.length);18 const lot = sold[0]!;19 const rec = out.find((r) => r.kind === 'sale' && r.externalId === `${payload.auction.id}-${lot.lotNo}`);20 expect(rec && rec.kind === 'sale').toBe(true);21 if (!rec || rec.kind !== 'sale') return;22 expect(rec.price).toBe(lot.hammerPremium);23 expect(rec.buyerPremiumIncluded).toBe(true);24 expect(rec.attributes.metadata.hammer_price).toBe(lot.hammerPrice);25 expect(rec.currency).toBe(lot.currency);26 expect(rec.saleDate.toISOString()).toBe(new Date(lot.hammerTime!).toISOString());27 expect(rec.auctionHouse).toBe('Bonhams');28 expect(rec.lotNumber).toBe(lot.lotNo);29 expect(rec.sourceUrl).toBe(lotUrl(payload.auction.id, lot.lotNo, lot.slug));30 expect(['rolex', 'patek_philippe', 'audemars_piguet', 'omega', 'other_watches']).toContain(rec.attributes.categorySlug);31 });3233 it('emits auction_lot records for lots that have not ended', async () => {34 const fx = loadFixture('bonhams', 'results-1');35 const payload = structuredClone(fx.raw.payload) as PagePayload;36 const future = new Date(Date.now() + 5 * 86_400_000).toISOString();37 payload.auction = { ...payload.auction, isEnded: false, end: future, start: new Date(Date.now() - 86_400_000).toISOString() };38 payload.lots = payload.lots.slice(0, 3).map((l) => ({ ...l, status: 'READY', hammerPrice: null, hammerPremium: null, isEnded: false, hammerTime: future, endDate: future }));39 const out = await connector.normalize({ ...fx.raw, payload });40 expect(out.length).toBe(3);41 for (const r of out) {42 expect(r.kind).toBe('auction_lot');43 if (r.kind !== 'auction_lot') continue;44 expect(r.status).toBe('live');45 expect(r.estimateLow).not.toBeNull();46 expect(r.endsAt?.toISOString()).toBe(new Date(future).toISOString());47 }48 });4950 it('skips unsold, withdrawn and unmapped-department lots', async () => {51 const fx = loadFixture('bonhams', 'results-1');52 const payload = structuredClone(fx.raw.payload) as PagePayload;53 payload.lots = [54 { ...payload.lots[0]!, status: 'UNSOLD', hammerPrice: null, hammerPremium: null },55 { ...payload.lots[1]!, status: 'WITHDRAWN' },56 { ...payload.lots[2]!, department: 'Carpets, Rugs & Tapestries', title: 'A Persian rug' },57 payload.lots[3]!,58 ];59 payload.auction = { ...payload.auction, departments: ['Watches'] };60 const out = await connector.normalize({ ...fx.raw, payload });61 expect(out.map((r) => ('externalId' in r ? r.externalId : null))).toEqual([`${payload.auction.id}-${payload.lots[3]!.lotNo}`]);62 });6364 it('parses listing and auction pages defensively', () => {65 expect(parseAuctionList('<html></html>')).toEqual({ auctions: [], nbHits: null });66 expect(parseAuctionPage('<html></html>')).toBeNull();67 expect(auctionPageUrl({ id: '31992', slug: 'weekly-watches' }, 2)).toBe('https://www.bonhams.com/auction/31992/weekly-watches/?page=2');68 });6970 it('maps departments and titles to taxonomy slugs', () => {71 expect(slugFromTitle('ROLEX. A STAINLESS STEEL AUTOMATIC CHRONOGRAPH WRISTWATCH REF 116500LN DAYTONA', hintFromLabel('Watches'))).toBe('rolex');72 expect(watchReference('REF 116500LN DAYTONA')).toBe('116500LN');73 expect(slugFromTitle('Château Lafite Rothschild 1982 (12 bottles)', hintFromLabel('Wine'))).toBe('wine');74 expect(slugFromTitle('The Macallan 25 Year Old Sherry Oak', hintFromLabel('Whisky'))).toBe('whisky');75 expect(slugFromTitle('1965 Ferrari 275 GTB Berlinetta Chassis no. 07589', hintFromLabel('Cars'))).toBe('automobiles');76 expect(slugFromTitle('Hermès Birkin 30 Togo leather 2019', hintFromLabel('Designer Handbags & Fashion'))).toBe('luxury_handbags');77 expect(slugFromTitle('A Gibson Les Paul Standard owned by ...', hintFromLabel('Popular Culture'))).toBe('musical_instruments');78 expect(slugFromTitle('Star Wars 1977 original one-sheet poster', hintFromLabel('Popular Culture'))).toBe('movie_posters');79 expect(slugFromTitle('Elizabeth II gold sovereign 1958', hintFromLabel('Coins, Medals and Banknotes'))).toBe('coins');80 expect(slugFromTitle('Bank of England £5 banknote 1935', hintFromLabel('Coins, Medals and Banknotes'))).toBe('banknotes');81 expect(slugFromTitle('A large ammonite fossil, Madagascar', hintFromLabel('Natural History'))).toBe('fossils');82 expect(slugFromTitle('Untitled, acrylic on canvas', hintFromLabel('Post-War and Contemporary Art'))).toBe('contemporary_art');83 });84});85