import { describe, expect, it } from 'vitest';
import { getConnectorMeta } from '@rareindex/connectors';
import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';
import createConnector, { categoryPageAuctionIds, classifyLot, parseAuctionPage, parseLotPage, type AuctionLotsPayload, type LotPayload } from './index.js';
const connector = createConnector(getConnectorMeta('catawiki'));
describe('catawiki connector', () => {
runFixtureSuite(connector, it, expect);
it('turns a closed, sold lot into a hammer-price sale in EUR with the source end time', async () => {
const fx = loadFixture('catawiki', 'seeded-1');
const lot = fx.raw.payload as LotPayload;
const out = await connector.normalize(fx.raw);
expect(out).toHaveLength(1);
const r = out[0]!;
if (r.kind !== 'sale') throw new Error('expected sale');
expect(r.price).toBe(lot.bidding.finalBidEur);
expect(r.currency).toBe('EUR');
expect(r.buyerPremiumIncluded).toBe(false);
expect(r.saleDate.getTime()).toBe(lot.bidding.biddingEndTime);
expect(r.auctionHouse).toBe('Catawiki');
expect(r.location).toBe(lot.sellerCountry);
expect(r.attributes.identifiers.catawiki_lot_id).toBe(String(lot.id));
expect(r.attributes.metadata.estimate_min_eur).toBe(lot.estimateMinEur);
expect(r.attributes.material).toBe('Wood');
expect(r.attributes.categorySlug).toBe('antiques');
});
it('drops closed-unsold lots and emits live lots as auction_lot', async () => {
const fx = loadFixture('catawiki', 'seeded-1');
const lot = structuredClone(fx.raw.payload) as LotPayload;
const unsold = { ...lot, bidding: { ...lot.bidding, sold: false } };
expect(await connector.normalize({ ...fx.raw, payload: unsold })).toHaveLength(0);
const live = { ...lot, bidding: { ...lot.bidding, closed: false, sold: null, biddingEndTime: Date.now() + 3 * 86_400_000 } };
const out = await connector.normalize({ ...fx.raw, payload: live });
expect(out).toHaveLength(1);
expect(out[0]!.kind).toBe('auction_lot');
if (out[0]!.kind === 'auction_lot') {
expect(out[0]!.status).toBe('live');
expect(out[0]!.currentBid).toBe(lot.bidding.finalBidEur);
expect(out[0]!.estimateLow).toBe(lot.estimateMinEur);
}
});
it('emits auction_lot records for a live themed auction and none for a stale one', async () => {
const fx = loadFixture('catawiki', 'seeded-2');
const payload = structuredClone(fx.raw.payload) as AuctionLotsPayload;
const future = new Date(Date.now() + 2 * 86_400_000).toISOString();
payload.auction = { ...payload.auction, closeAt: future, startAt: new Date(Date.now() - 86_400_000).toISOString() };
const out = await connector.normalize({ ...fx.raw, payload });
expect(out.length).toBeGreaterThan(payload.lots.length * 0.8);
for (const r of out) {
expect(r.kind).toBe('auction_lot');
if (r.kind !== 'auction_lot') continue;
expect(r.endsAt?.toISOString()).toBe(new Date(future).toISOString());
expect(r.currency).toBe('EUR');
expect(['rolex', 'patek_philippe', 'audemars_piguet', 'omega', 'other_watches', 'pens', 'lighters', 'clocks', 'jewelry']).toContain(r.attributes.categorySlug);
}
const stale = { ...payload, auction: { ...payload.auction, closeAt: '2020-01-01T00:00:00Z' } };
expect(await connector.normalize({ ...fx.raw, payload: stale })).toHaveLength(0);
});
it('classifies lots from category path, specs and titles', () => {
const base: LotPayload = { kind: 'lot', seedHint: 'toys', id: 1, url: 'https://www.catawiki.com/en/l/1', title: 'LEGO - Star Wars - 10179 - Millennium Falcon UCS', subtitle: 'Sealed', description: null, images: [], categoryId: null, categoryUrl: null, auction: { id: 1, title: 'LEGO Auction', url: 'https://www.catawiki.com/en/a/1', status: 'closed', startAt: null, closeAt: null, closedAt: null, categories: ['Toys & Models', 'LEGO'], lotCount: null }, specs: [{ name: 'Set number', value: '10179' }], estimateMinEur: null, estimateMaxEur: null, sellerCountry: 'NL', sellerName: null, sellerIsPro: null, bidding: { closed: true, sold: true, finalBidEur: 2500, biddingStartTime: null, biddingEndTime: 1, bidCount: null, reservePriceMet: null } };
expect(classifyLot(base).slug).toBe('lego_sets');
expect(classifyLot({ ...base, seedHint: 'cards', title: 'Pokémon - Charizard Base Set 4/102 - PSA 9', auction: { ...base.auction!, categories: ['Trading Cards', 'Pokémon'] } }).slug).toBe('pokemon');
expect(classifyLot({ ...base, seedHint: 'watches', title: 'Rolex - Submariner 16610 - Men - 2005', auction: { ...base.auction!, categories: ['Watches, Pens & Lighters', 'Watches'] } }).slug).toBe('rolex');
expect(classifyLot({ ...base, seedHint: 'wine', title: 'Macallan 18 Sherry Oak 2020 release', auction: { ...base.auction!, categories: ['Wine, Whisky & Spirits', 'Whisky'] } }).slug).toBe('whisky');
expect(classifyLot({ ...base, seedHint: 'coins', title: 'Netherlands 10 Gulden 1875 gold', auction: { ...base.auction!, categories: ['Coins & Stamps', 'Coins'] } }).slug).toBe('coins');
expect(categoryPageAuctionIds('')).toEqual([1263062, 99]);
expect(parseAuctionPage('')).toBeNull();
expect(parseLotPage('', 'https://www.catawiki.com/en/l/1', 'unknown')).toBeNull();
});
});