import { readFileSync } from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { getConnectorMeta } from '@rareindex/connectors'; import { fixtureDir, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { ListResponseSchema, listUrl, toAuctions } from './index.js'; import { alnumReferenceFromText, watchFromTitle } from '../_g9-asia-watch-sneaker-lib/index.js'; const connector = createConnector(getConnectorMeta('loupe-this')); const sample = ListResponseSchema.parse(JSON.parse(readFileSync(path.join(fixtureDir('loupe-this'), 'samples', 'api-closed.json'), 'utf8'))); describe('loupe-this', () => { runFixtureSuite(connector, it, expect); it('maps JSON:API resources and resolves brand names from `included`', () => { const auctions = toAuctions(sample.data, sample.included); expect(auctions).toHaveLength(3); const a = auctions[0]!; expect(a.title).toBe('MING 37.02 Minimalist'); expect(a.brand).toBe('Ming'); expect(a.isClosed).toBe(true); expect(a.soldPriceCents).toBe(352000); expect(a.currentBidCents).toBe(320000); expect(a.buyersPremiumPercent).toBe(10); expect(a.endsAt).toBe('2026-08-10T16:25:00.000Z'); expect(sample.meta?.pagination?.total_pages).toBeGreaterThan(40); expect(listUrl('closed', 3)).toBe('https://api.loupethis.com/api/v1/auctions?status=closed&per_page=100&page=3&include=brand'); }); it('closed lots with a sold price become sales (buyer premium included, hammer in metadata)', async () => { const auctions = toAuctions(sample.data, sample.included); const out = await connector.normalize({ url: 'https://api.loupethis.com/api/v1/auctions?status=closed', externalId: null, kind: 'sale', engine: 'api', fetchedAt: new Date('2026-09-08T00:00:00Z'), payload: { kind: 'auction_page', status: 'closed', page: 2, totalPages: 43, totalCount: 4251, auctions } }); const s = out[0]; if (s?.kind !== 'sale') throw new Error('expected sale'); expect(s.price).toBe(3520); expect(s.currency).toBe('USD'); expect(s.buyerPremiumIncluded).toBe(true); expect(s.attributes.metadata.hammer_price).toBe(3200); expect(s.saleDate.toISOString()).toBe('2026-08-10T16:25:00.000Z'); expect(s.saleType).toBe('auction'); expect(s.auctionHouse).toBe('Loupe This'); expect(s.attributes.brand).toBe('Ming'); expect(s.attributes.categorySlug).toBe('other_watches'); expect(s.sourceUrl).toBe('https://loupethis.com/auctions/ming-37-02-minimalist'); expect(s.attributes.identifiers.loupe_this_id).toBe(auctions[0]!.id); }); it('closed lots without a sold price are skipped; open lots become auction_lot records', async () => { const auctions = toAuctions(sample.data, sample.included); const unsold = { ...auctions[0]!, soldPriceCents: null }; const live = { ...auctions[1]!, isClosed: false, soldPriceCents: null, startsAt: '2026-09-01T16:00:00.000Z', endsAt: '2026-09-30T16:00:00.000Z', currentBidCents: 45000 }; const upcoming = { ...auctions[2]!, isClosed: false, soldPriceCents: null, startsAt: '2026-10-01T16:00:00.000Z', endsAt: '2026-10-08T16:00:00.000Z', currentBidCents: 0 }; const out = await connector.normalize({ url: 'x', externalId: null, kind: 'auction_lot', engine: 'api', fetchedAt: new Date('2026-09-08T00:00:00Z'), payload: { kind: 'auction_page', status: 'live', page: 1, totalPages: 1, totalCount: 3, auctions: [unsold, live, upcoming] } }); expect(out).toHaveLength(2); const [l, u] = out; if (l?.kind !== 'auction_lot' || u?.kind !== 'auction_lot') throw new Error('expected auction lots'); expect(l.status).toBe('live'); expect(l.currentBid).toBe(450); expect(l.currency).toBe('USD'); expect(u.status).toBe('upcoming'); expect(u.currentBid).toBeNull(); }); it('live fixture yields auction lots with watch references (incl. Seiko-style codes)', async () => { const fx = loadFixture('loupe-this', 'live-p1'); const out = await connector.normalize(fx.raw); expect(out.length).toBeGreaterThan(3); expect(out.every((r) => r.kind === 'auction_lot')).toBe(true); const gs = out.find((r) => r.kind === 'auction_lot' && /SBGA211/.test(r.rawTitle)); expect(gs && gs.kind === 'auction_lot' ? gs.attributes.reference : null).toBe('SBGA211'); }); it('watch title parsing', () => { expect(watchFromTitle('Patek Philippe Hour Glass 2456J 18K YG With Extract From The Archives')).toMatchObject({ brand: 'Patek Philippe', categorySlug: 'patek_philippe', reference: '2456J', material: 'gold' }); expect(watchFromTitle('Rolex Submariner Date 126610LN Unworn 2023 41mm').reference).toBe('126610LN'); expect(alnumReferenceFromText('IWC Pilot IW371446 18K')).toBe('IW371446'); expect(alnumReferenceFromText('Steel GMT 40mm')).toBeNull(); }); });