TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import { readFileSync } from 'node:fs';3import path from 'node:path';4import { fileURLToPath } from 'node:url';5import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';6import { localMeta } from '../_lib/local-meta.js';7import createConnector, { parseLotLinks, parseLotPage, parseSalesDropdown, spinkDepartment } from './index.js';8import { parseCoinGrade } from '../../firecrawl/_g5-numismatics-lib/index.js';910const dir = path.dirname(fileURLToPath(import.meta.url));11const connector = createConnector(localMeta(JSON.parse(readFileSync(path.join(dir, 'meta.json'), 'utf8'))));1213const DROPDOWN = `<select id="idAuctionDropdown" class="selectbox" name="auction"><option value="-1">All</option>14<option value="SFW74">27 Aug 2026 - No SFW74 - Boozing with Spirits - e-Auction</option>15<option value="26300">27 Aug 2026 - No 26300 - World & British Banknotes - e-Auction</option>16<option value="25005">30 Sept 2025 - No 25005 - The Carrington and Pallas Collections of Exceptional English and Anglo-Gallic Gold Coins and Proof Sets</option>17<option value="1304">11 Dec 2002 - No 1304 - Stamps - Rhodesia Double Head & Admiral Issues </option></select>`;18const LIST = `<div class="head"><a href="/lot/25005000001" target="_blank"># 1 - NGC Choice VF | Gaul »</a></div><a href="/lot/25005000001"><img></a><div class="head"><a href="/lot/25005000338" target="_blank"># 338 - Edward VII »</a></div><a href="/auction/25005?page=2">2</a>`;19const LOT = `<span class="name">Auction: 25005 - The Carrington and Pallas Collections of Exceptional English and Anglo-Gallic Gold Coins and Proof Sets</span><br><span class="id">Lot: 338</span>20<p><span class="description">(g) <b>Edward VII</b> (1901-1910), Coronation Matte Proof 'Short' Set, 1902, Sovereign to Sixpence (S.PS12), including NGC PF64 MATTE | Matte Proof Crown, 1902, in NGC holder (Cert. #8464067-021); <i>much as issued</i> (11)<br><br><i>Subject to 20% VAT on Buyer’s Premium. For more information please view Terms and Conditions for Buyers.</i></span></p>21<p><span class="sold-for"> Sold for </span><br><span class="amount"> £1,900 </span></p><p><span class="sold-for"> Starting price </span><br><span class="amount"> £1600 </span></p>22<img class="image img-responsive" src="https://d3ums4016ncdkp.cloudfront.net/auction/main/25005/25005_338_1.jpg"><div class="thumbnail-inner"><img src="https://d3ums4016ncdkp.cloudfront.net/auction/main/25005/25005_338_2.jpg"></div>`;23const UNSOLD = LOT.replace(/<p><span class="sold-for"> Sold for[\s\S]*?<\/p>/, '');2425describe('spink', () => {26 runFixtureSuite(connector, it, expect);2728 it('parses the sales dropdown, classifies departments and extracts lot links', () => {29 const sales = parseSalesDropdown(DROPDOWN);30 expect(sales.length).toBe(4);31 expect(sales[2]).toEqual({ code: '25005', title: 'The Carrington and Pallas Collections of Exceptional English and Anglo-Gallic Gold Coins and Proof Sets', dateText: '30 Sept 2025' });32 expect(sales[3]!.dateText).toBe('11 Dec 2002');33 expect(spinkDepartment('Boozing with Spirits - e-Auction')).toBeNull();34 expect(spinkDepartment('World & British Banknotes - e-Auction')).toBe('banknotes');35 expect(spinkDepartment('Stamps - Rhodesia Double Head & Admiral Issues')).toBe('stamps');36 expect(spinkDepartment('Orders, Decorations and Medals')).toBe('medals');37 expect(spinkDepartment(sales[2]!.title)).toBe('coins');38 expect(parseLotLinks(LIST, '25005')).toEqual(['https://www.spink.com/lot/25005000001', 'https://www.spink.com/lot/25005000338']);39 });4041 it('parses a lot page and normalises "Sold for" with premium unknown; unsold pages yield nothing', async () => {42 const sale = { code: '25005', title: 'x', dateText: '30 Sept 2025' };43 const p = parseLotPage(LOT, 'https://www.spink.com/lot/25005000338', sale)!;44 expect(p).toMatchObject({ lotNumber: '338', headline: 'Edward VII', soldText: '£1,900', startingText: '£1600' });45 expect(p.sale.title).toMatch(/^The Carrington/);46 expect(p.description).not.toMatch(/VAT/);47 expect(p.images).toEqual(['https://d3ums4016ncdkp.cloudfront.net/auction/main/25005/25005_338_1.jpg', 'https://d3ums4016ncdkp.cloudfront.net/auction/main/25005/25005_338_2.jpg']);48 const out = await connector.normalize({ url: p.url, externalId: '25005000338', kind: 'sale', engine: 'api', fetchedAt: new Date('2026-09-08T00:00:00Z'), payload: p });49 expect(out.length).toBe(1);50 const s = out[0]!;51 if (s.kind !== 'sale') throw new Error('sale');52 expect(s).toMatchObject({ price: 1900, currency: 'GBP', buyerPremiumIncluded: null, lotNumber: '338', auctionHouse: 'Spink', isBundle: true, location: 'GB' });53 expect(s.saleDate.toISOString()).toBe('2025-09-30T00:00:00.000Z');54 expect(s.grade).toMatchObject({ grader: 'ngc', grade: 'PF64', certificationNumber: '8464067-021' });55 expect(s.attributes).toMatchObject({ categorySlug: 'coins', year: 1902, country: 'GB' });56 expect(s.attributes.identifiers).toEqual({ spink_lot: '25005-338', ngc_cert: '8464067-021' });57 expect(s.attributes.metadata).toMatchObject({ starting_price: 1600, spink_reference: 'S.PS12' });58 expect(await connector.normalize({ url: p.url, externalId: 'u', kind: 'sale', engine: 'api', fetchedAt: new Date(), payload: parseLotPage(UNSOLD, p.url, sale) })).toEqual([]);59 });6061 it('parses adjectival NGC Ancients grades and HK$ prices', () => {62 expect(parseCoinGrade('NGC Choice VF | Gaul, Aulerci Eburovices, Electrum Hemistater')).toMatchObject({ grader: 'ngc', grade: 'Choice VF' });63 expect(parseCoinGrade('NGC Ch XF Strike 5/5 Surface 4/5 Athens tetradrachm')).toMatchObject({ grader: 'ngc', grade: 'Choice XF' });64 });6566 it('fixtures: coins, ancients and banknotes lots normalise to GBP sales', async () => {67 for (const name of ['lot-25005-338-proof-set', 'lot-25005-1-celtic-ngc', 'lot-26300-10-banknotes']) {68 const out = await connector.normalize(loadFixture('spink', name).raw);69 expect(out.length).toBe(1);70 if (out[0]!.kind === 'sale') expect(out[0]).toMatchObject({ currency: 'GBP', buyerPremiumIncluded: null, auctionHouse: 'Spink' });71 }72 const notes = await connector.normalize(loadFixture('spink', 'lot-26300-10-banknotes').raw);73 if (notes[0]!.kind === 'sale') expect(notes[0]!.attributes.categorySlug).toBe('banknotes');74 });75});76