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, { itemsUrl, searchUrl, toItem } from './index.js'; import { cjkLanguage, unixToDate } from '../_g9-asia-watch-sneaker-lib/index.js'; const connector = createConnector(getConnectorMeta('ruten')); const sample = JSON.parse(readFileSync(path.join(fixtureDir('ruten'), 'samples', 'rapi-item.json'), 'utf8')) as { data: Record[] }; describe('ruten', () => { runFixtureSuite(connector, it, expect); it('maps a raw rapi item (TWD price, stock, store, image, post time)', () => { const it0 = toItem(sample.data[0]!); expect(it0).not.toBeNull(); expect(it0!.id).toBe('22629136833498'); expect(it0!.currency).toBe('TWD'); expect(it0!.price).toBe(10500); expect(it0!.priceMin).toBe(10500); expect(it0!.storeName).toBe('91特賣場'); expect(it0!.class).toBe('000500140015'); expect(it0!.image).toMatch(/^https:\/\/gcs\.rimg\.com\.tw\//); expect(it0!.available).toBe(true); expect(unixToDate(it0!.postTime)?.toISOString()).toBe('2026-07-16T04:17:16.000Z'); // post_time 1784175436 expect(toItem({ id: 'x', name: 'no price' })).toBeNull(); }); it('builds the public search and batch item URLs', () => { expect(searchUrl('寶可夢 PSA10', 31)).toBe('https://rtapi.ruten.com.tw/api/search/v3/index.php/core/prod?q=%E5%AF%B6%E5%8F%AF%E5%A4%A2%20PSA10&type=direct&sort=rnk%2Fdc&offset=31&limit=30'); expect(itemsUrl(['1', '2'])).toBe('https://rapi.ruten.com.tw/api/items/v2/list?gno=1,2&level=simple'); }); it('normalises the search fixture into TWD listings with grades, language and attribution URLs', async () => { const fx = loadFixture('ruten', 'pokemon-psa10-search-p1'); const out = await connector.normalize(fx.raw); expect(out.length).toBeGreaterThan(5); const l = out[0]; if (l?.kind !== 'listing') throw new Error('expected listing'); expect(l.currency).toBe('TWD'); expect(l.price).toBe(10500); expect(l.attributes.categorySlug).toBe('pokemon'); expect(l.attributes.identifiers.ruten_item_id).toBe('22629136833498'); expect(l.attributes.language).toBe('Chinese'); expect(l.grade.grader).toBe('psa'); expect(l.grade.grade).toBe('10'); expect(l.sourceUrl).toBe('https://www.ruten.com.tw/item/show?22629136833498'); expect(l.listingType).toBe('fixed_price'); expect(l.endsAt).toBeNull(); // far-future placeholder deadline is dropped expect(l.attributes.metadata.sold_count).toBe(0); const sold = out.find((r) => r.kind === 'listing' && r.availability === 'sold'); expect(sold).toBeDefined(); // stock 0 with sold_num > 0 → sold, never emitted as a sale }); it('detects card language from Chinese titles', () => { expect(cjkLanguage('寶可夢 PTCG 日文版 SM8a 058 SR')).toBe('Japanese'); expect(cjkLanguage('寶可夢 PTCG 中文版 莉莉艾的全力')).toBe('Chinese'); expect(cjkLanguage('PTCG 寶可夢 PSA10 竹蘭的霸氣')).toBeNull(); }); });