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, { currencyFromText, hintForSale, lotSearchUrl, parseLotSearch, parseResultsMonth, resultsUrl, type PagePayload } from './index.js';56const connector = createConnector(getConnectorMeta('christies'));78describe('christies connector', () => {9 runFixtureSuite(connector, it, expect);1011 it('emits one premium-inclusive sale per realised lot, with the source currency and date', async () => {12 const fx = loadFixture('christies', 'results-1');13 const payload = fx.raw.payload as PagePayload;14 const out = await connector.normalize(fx.raw);15 const realised = payload.lots.filter((l) => !l.withdrawn && (l.priceRealised ?? 0) > 0);16 expect(out.length).toBeGreaterThan(0);17 expect(out.length).toBeLessThanOrEqual(realised.length);18 for (const r of out) {19 expect(r.kind).toBe('sale');20 if (r.kind !== 'sale') continue;21 const lot = payload.lots.find((l) => l.objectId === r.externalId)!;22 expect(r.price).toBe(lot.priceRealised);23 expect(r.currency).toBe(currencyFromText(lot.priceRealisedText));24 expect(r.buyerPremiumIncluded).toBe(true);25 expect(r.saleDate.toISOString()).toBe(new Date(lot.endDate ?? payload.sale.endDate!).toISOString());26 expect(r.auctionHouse).toBe("Christie's");27 expect(r.lotNumber).toBe(lot.lotNumber);28 expect(r.attributes.identifiers.christies_sale_number).toBe(payload.sale.saleNumber);29 expect(r.attributes.metadata.estimate_low).toBe(lot.estimateLow);30 }31 });3233 it('skips withdrawn and unsold lots', async () => {34 const fx = loadFixture('christies', 'results-1');35 const payload = structuredClone(fx.raw.payload) as PagePayload;36 const keep = payload.lots.find((l) => (l.priceRealised ?? 0) > 0)!;37 payload.lots = [{ ...keep, objectId: 'w', withdrawn: true }, { ...keep, objectId: 'u', priceRealised: null, priceRealisedText: null }, { ...keep, objectId: 'z', priceRealised: 0 }, keep];38 const out = await connector.normalize({ ...fx.raw, payload });39 expect(out.map((r) => ('externalId' in r ? r.externalId : null))).toEqual([keep.objectId]);40 });4142 it('parses month results and lot searches', () => {43 const month = {44 filters: { groups: [{ title_txt: 'Category', type: 'category', filters: [{ id: 'category_9', label_txt: 'Jewellery, Watches & Handbags' }] }] },45 events: [{ event_id: '31193', landing_url: 'https://www.christies.com/en/auction/x-31193/', title_txt: 'Important Watches', subtitle_txt: 'Live Auction 24546 | CLOSED', filter_ids: '|category_9|location_36|event_0||event_live|', location_txt: 'London', start_date: '2026-07-08T00:00:00', end_date: '2026-07-08T00:00:00', sale_total_value_txt: 'GBP 12,707,879' }],46 };47 const sales = parseResultsMonth(month);48 expect(sales).toHaveLength(1);49 expect(sales[0]).toMatchObject({ saleId: '31193', saleNumber: '24546', eventType: 'Live', categoryLabels: ['Jewellery, Watches & Handbags'], location: 'London' });50 expect(hintForSale(sales[0]!, ['Jewellery, Watches & Handbags'])).toBe('watches');51 expect(lotSearchUrl(sales[0]!, 2, 84)).toContain('SaleNumber=24546&SaleId=31193&page=2&pageSize=84');52 expect(resultsUrl(7, 2026)).toBe('https://www.christies.com/api/discoverywebsite/auctioncalendar/auctionresults?language=en&month=7&year=2026');53 const ls = parseLotSearch({ lots: [{ object_id: '1', lot_id_txt: '5', title_primary_txt: 'ROLEX', title_secondary_txt: 'REF. 6239 DAYTONA', price_realised: '190500.0', price_realised_txt: 'GBP 190,500', estimate_low: '40000.0', estimate_high: '60000.0', end_date: '2026-07-07T23:00Z', url: 'https://www.christies.com/en/lot/lot-1' }], total_hits_filtered: 1 });54 expect(ls.total).toBe(1);55 expect(ls.lots[0]).toMatchObject({ objectId: '1', lotNumber: '5', priceRealised: 190500, estimateLow: 40000 });56 expect(currencyFromText('HKD 1,250,000')).toBe('HKD');57 expect(currencyFromText(null)).toBeNull();58 });5960 it('maps a watch lot from a jewellery/watches sale to the brand slug with reference', async () => {61 const fx = loadFixture('christies', 'results-1');62 const payload = structuredClone(fx.raw.payload) as PagePayload;63 payload.sale = { ...payload.sale, categoryLabels: ['Jewellery, Watches & Handbags'], title: 'Important Watches' };64 const base = payload.lots.find((l) => (l.priceRealised ?? 0) > 0)!;65 payload.lots = [{ ...base, objectId: 'rolex1', titlePrimary: 'ROLEX', titleSecondary: 'REF. 116500LN DAYTONA, A STAINLESS STEEL AUTOMATIC CHRONOGRAPH WRISTWATCH', titleTertiary: null, description: null, priceRealised: 30000, priceRealisedText: 'USD 30,000' }];66 const out = await connector.normalize({ ...fx.raw, payload });67 expect(out).toHaveLength(1);68 const r = out[0]!;69 if (r.kind !== 'sale') throw new Error('expected sale');70 expect(r.attributes.categorySlug).toBe('rolex');71 expect(r.attributes.identifiers.reference).toBe('116500LN');72 expect(r.attributes.brand).toBe('Rolex');73 expect(r.currency).toBe('USD');74 });75});76