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%
6.0 KB · 95 lines typescript
Raw Blame History
1import { describe, expect, it } from 'vitest';2import type { NormalizedRecord } from '@rareindex/shared';3import { ConnectorMetaSchema } from '@rareindex/connectors';4import { listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';5import metaJson from './meta.json' with { type: 'json' };6import createConnector, { humanizeSlug, parseCataloguePage, parseSalesSitemap } from './index.js';78const meta = ConnectorMetaSchema.parse(metaJson);9const connector = createConnector(meta);10const attrsOf = (r: NormalizedRecord) => {11  if (!('attributes' in r)) throw new Error(`record kind ${r.kind} has no attributes`);12  return r.attributes;13};1415const NEXT = {16  props: {17    pageProps: {18      auction: { uuid: 'a-1', sale_no: 'G000002', title: { en: 'Tour Auto 2025 • The Official Sale', fr: 'Tour Auto 2025 • La Vente Officielle' }, start_date: '2025-04-07T14:00:00.000Z', status: 'completed', type: 'live', currency: { code: 'EUR' }, premiums: [{ percent: 25, amount_over: 0 }], branch: { name: 'Aguttes', city: 'Neuilly-sur-seine', country_code: 'FR' } },19      auctionLots: {20        count: 39,21        limit: 24,22        page: 1,23        results: [24          { uuid: 'l-1', lot_no: '1', status: 'sold', low: '15000.00', high: '25000.00', hammer_price: '20000.00', title: { en: 'Michael Schumacher', fr: 'Michael Schumacher' }, quantity: 1, num_of_bids: 0, primary_image: { data: { lg: { url: 'https://media.app.artisio.co/x_lg.jpeg' } } }, dynamic_fields: { en: { title: 'Michael Schumacher', artist: '', car_brand: 'Ferrari', description: 'Official BELL DOMINATOR helmet, worn during the 1999 pre-season tests<br>Numbered 9th.' } } },25          { uuid: 'l-2', lot_no: '2', status: 'unsold', low: '1200.00', high: '1800.00', hammer_price: null, title: { en: 'Ferrari F2008', fr: 'Ferrari F2008' }, quantity: 1, dynamic_fields: { en: { description: 'Nose cone.' } } },26          { uuid: 'l-3', lot_no: '35', status: 'sold', low: '100000.00', high: '150000.00', hammer_price: '235000.00', title: { en: 'CARTIER', fr: 'CARTIER' }, dynamic_fields: { en: { description: 'Tank wristwatch, yellow gold, circa 1975.' } } },27        ],28      },29    },30  },31};32const HTML = `<html><body><a href="/lot/tour-auto-2025-la-vente-officielle-a-1/michael-schumacher-l-1">x</a><a href="/lot/tour-auto-2025-la-vente-officielle-a-1/cartier-l-3">y</a><script id="__NEXT_DATA__" type="application/json">${JSON.stringify(NEXT)}</script></body></html>`;3334describe('aguttes', () => {35  runFixtureSuite(connector, it, expect);3637  it('fixtures: EUR hammer-price sales with the Aguttes lot identifier', async () => {38    let sales = 0;39    for (const name of listFixtures('aguttes')) {40      const out = await connector.normalize(loadFixture('aguttes', name).raw);41      for (const r of out) {42        if (!('attributes' in r)) continue;43        expect(r.attributes.identifiers.aguttes_lot).toMatch(/^[^/]+\/\S+$/);44        expect(r.sourceUrl.startsWith('https://www.aguttes.com/')).toBe(true);45        if (r.kind === 'sale') {46          sales++;47          expect(r.currency).toBe('EUR');48          expect(r.buyerPremiumIncluded).toBe(false);49          expect(r.auctionHouse).toBe('Aguttes');50          expect(r.lotNumber).toBeTruthy();51        }52      }53    }54    expect(sales).toBeGreaterThan(5);55  });5657  it('parses the sitemap (newest catalogues first) and slugs', () => {58    const xml = `<urlset><url><loc>https://www.aguttes.com/catalogue/11442</loc></url><url><loc>https://www.aguttes.com/catalogue/160425</loc></url><url><loc>https://www.aguttes.com/catalogue/livres-manuscrits-6339ab29-3d37-453b-aeeb-d33046b92b2f</loc></url></urlset>`;59    const sales = parseSalesSitemap(xml);60    expect(sales.map((s) => s.id)).toEqual(['livres-manuscrits-6339ab29-3d37-453b-aeeb-d33046b92b2f', '160425', '11442']);61    expect(sales[0]!.title).toBe('livres manuscrits');62    expect(humanizeSlug('livres-manuscrits-6339ab29-3d37-453b-aeeb-d33046b92b2f')).toBe('livres manuscrits');63  });6465  it('parses a catalogue page from __NEXT_DATA__ (hammer, estimates, sold/unsold, pagination, lot URLs)', async () => {66    const sale = { id: '160425', title: '160425', url: 'https://www.aguttes.com/catalogue/160425', date: null, location: null, extra: {} };67    const p = parseCataloguePage(HTML, sale, 1)!;68    expect(p.totalLots).toBe(39);69    expect(p.hasMore).toBe(true);70    expect(p.sale?.date).toBe('2025-04-07T14:00:00.000Z');71    expect(p.sale?.title).toBe('Tour Auto 2025 • The Official Sale');72    expect(p.lots).toHaveLength(3);73    expect(p.lots[0]).toMatchObject({ lotNo: '1', price: 20000, currency: 'EUR', premiumIncluded: false, estimateLow: 15000, estimateHigh: 25000, sold: true, url: 'https://www.aguttes.com/lot/tour-auto-2025-la-vente-officielle-a-1/michael-schumacher-l-1', image: 'https://media.app.artisio.co/x_lg.jpeg' });74    expect(p.lots[0]!.description).toBe('Official BELL DOMINATOR helmet, worn during the 1999 pre-season tests Numbered 9th.');75    expect(p.lots[1]).toMatchObject({ lotNo: '2', price: null, sold: false });76    expect(p.lots[1]!.url).toContain('#lot-2');77    Object.assign(sale, p.sale, { extra: p.sale!.extra });78    const out = await connector.normalize({ url: sale.url, kind: 'sale', engine: 'api', fetchedAt: new Date('2026-09-08T00:00:00Z'), payload: { kind: 'sale_results', url: sale.url, sale, page: 1, totalLots: 39, lots: p.lots } });79    expect(out.map((r) => r.kind)).toEqual(['sale', 'auction_lot', 'sale']);80    const helmet = out[0]!;81    if (helmet.kind === 'sale') {82      expect(helmet.saleDate.toISOString()).toBe('2025-04-07T14:00:00.000Z');83      expect(helmet.attributes.categorySlug).toBe('automotive_memorabilia');84      expect(helmet.location).toBe('Neuilly-sur-seine, France');85      expect(helmet.attributes.metadata.sale_no).toBe('G000002');86    }87    const cartier = out[2]!;88    expect(attrsOf(cartier).categorySlug).toBe('other_watches');89    expect(attrsOf(cartier).brand).toBe('Cartier');90    const unsold = out[1]!;91    if (unsold.kind === 'auction_lot') expect(unsold.status).toBe('ended');92    expect(parseCataloguePage('<html></html>', sale, 1)).toBeNull();93  });94});95