import { describe, expect, it } from 'vitest'; import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import { localMeta } from '../_lib/local-meta.js'; import createConnector, { gradeToken, parseCategories, parseSeriesPage, parseYearMint, updatedDate } from './index.js'; const dir = path.dirname(fileURLToPath(import.meta.url)); const connector = createConnector(localMeta(JSON.parse(readFileSync(path.join(dir, 'meta.json'), 'utf8')))); const PAGE = `

Morgan Dollars (1878-1921)

Year/MintDenomDesig
1878 8TF1878 8TF $1 MSUpdated: 9/8/2026$1MS0
1878 8TF1878 8TF $1 PFUpdated: 1/24/2025$1PF0
1893-S1893-S $1 MS$1MS
`; describe('ngc-priceguide', () => { runFixtureSuite(connector, it, expect); it('parses categories JSON into subcategories', () => { const subs = parseCategories([{ CategoryID: 14, Name: 'Dollars', SeoName: 'dollars', Subcategories: [{ SubcategoryID: 49, Name: 'Morgan Dollars (1878-1921)', SubcategorySeoName: 'morgan-dollars-1878-1921', HasMS: true, HasPF: true }] }]); expect(subs).toEqual([{ id: '49', name: 'Morgan Dollars (1878-1921)', categorySeo: 'dollars', categoryName: 'Dollars' }]); expect(parseCategories({ nope: 1 })).toEqual([]); }); it('parses a series page: coin rows joined with price cells by coin id', () => { const p = parseSeriesPage(PAGE, 'https://www.ngccoin.com/price-guide/united-states/dollars/49/', 'dollars')!; expect(p.seriesName).toBe('Morgan Dollars (1878-1921)'); expect(p.subcategoryId).toBe('49'); expect(p.coins.length).toBe(3); expect(p.coins[0]).toMatchObject({ coinId: '17072', yearMint: '1878 8TF', description: '1878 8TF $1 MS', denomination: '$1', designation: 'MS', strike: 'ms', updatedText: '9/8/2026' }); expect(p.coins[0]!.prices).toEqual({ PrAg: 59, VF: 100, 'XF+': 135, '65': 2350, '65+': 3000, '66★': 9000 }); expect(p.coins[1]).toMatchObject({ strike: 'pf', designation: 'PF', prices: { '63': 8500 } }); expect(p.coins[2]!.updatedText).toBeNull(); }); it('helpers: dates, year/mint, grade tokens', () => { expect(updatedDate('9/8/2026')?.toISOString()).toBe('2026-09-08T00:00:00.000Z'); expect(updatedDate(null)).toBeNull(); expect(parseYearMint('1878 8TF')).toEqual({ year: 1878, mintMark: null, variety: '8TF' }); expect(parseYearMint('1893-S')).toEqual({ year: 1893, mintMark: 'S', variety: null }); expect(gradeToken('65', 'MS', 'ms')).toBe('MS65'); expect(gradeToken('65+', 'MS', 'ms')).toBe('MS65+'); expect(gradeToken('63', 'PF', 'pf')).toBe('PF63'); expect(gradeToken('66★', 'MS', 'ms')).toBe('MS66★'); expect(gradeToken('XF+', 'MS', 'ms')).toBe('XF+'); expect(gradeToken('PrAg', 'MS', 'ms')).toBe('PrAg'); expect(gradeToken('65', 'MSDPL', 'ms')).toBe('MS65'); }); it('normalises catalog items + dated guide observations; rows without an Updated date are dropped', async () => { const p = parseSeriesPage(PAGE, 'https://www.ngccoin.com/price-guide/united-states/dollars/49/', 'dollars')!; const out = await connector.normalize({ url: p.url, externalId: 'x', kind: 'price_observation', engine: 'api', fetchedAt: new Date('2026-09-08T12:00:00Z'), payload: p }); const items = out.filter((r) => r.kind === 'catalog_item'); const obs = out.filter((r) => r.kind === 'price_observation'); expect(items.length).toBe(2); // 1893-S has no Updated date → skipped expect(obs.length).toBe(7); const o = obs.find((r) => r.kind === 'price_observation' && r.grade.grade === 'PrAg')!; if (o.kind !== 'price_observation') throw new Error('obs'); expect(o).toMatchObject({ priceKind: 'guide_value', price: 59, currency: 'USD', grade: { grader: 'ngc', grade: 'PrAg' } }); expect(o.observationDate.toISOString()).toBe('2026-09-08T00:00:00.000Z'); expect(o.attributes).toMatchObject({ categorySlug: 'coins', year: 1878, country: 'US', series: 'Morgan Dollars (1878-1921)', identifiers: { ngc_coin_id: '17072' }, variant: '8TF' }); const ms65 = obs.find((r) => r.kind === 'price_observation' && r.grade.grade === 'MS65'); expect(ms65 && ms65.kind === 'price_observation' ? ms65.price : null).toBe(2350); const pf = obs.find((r) => r.kind === 'price_observation' && r.grade.grade === 'PF63'); expect(pf && pf.kind === 'price_observation' ? pf.observationDate.toISOString() : null).toBe('2025-01-24T00:00:00.000Z'); }); it('fixtures: Morgan dollars and Walking Liberty halves', async () => { for (const name of ['series-morgan-dollars-49', 'series-walking-liberty-halves-42']) { const out = await connector.normalize(loadFixture('ngc-priceguide', name).raw); expect(out.some((r) => r.kind === 'price_observation' && r.grade.grader === 'ngc')).toBe(true); for (const r of out) if (r.kind === 'price_observation') expect(r.currency).toBe('USD'); } }); });