TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { readFileSync } from 'node:fs';2import path from 'node:path';3import { describe, expect, it } from 'vitest';4import { getConnectorMeta } from '@rareindex/connectors';5import { fixtureDir, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';6import createConnector, { itemsUrl, searchUrl, toItem } from './index.js';7import { cjkLanguage, unixToDate } from '../_g9-asia-watch-sneaker-lib/index.js';89const connector = createConnector(getConnectorMeta('ruten'));10const sample = JSON.parse(readFileSync(path.join(fixtureDir('ruten'), 'samples', 'rapi-item.json'), 'utf8')) as { data: Record<string, unknown>[] };1112describe('ruten', () => {13 runFixtureSuite(connector, it, expect);1415 it('maps a raw rapi item (TWD price, stock, store, image, post time)', () => {16 const it0 = toItem(sample.data[0]!);17 expect(it0).not.toBeNull();18 expect(it0!.id).toBe('22629136833498');19 expect(it0!.currency).toBe('TWD');20 expect(it0!.price).toBe(10500);21 expect(it0!.priceMin).toBe(10500);22 expect(it0!.storeName).toBe('91特賣場');23 expect(it0!.class).toBe('000500140015');24 expect(it0!.image).toMatch(/^https:\/\/gcs\.rimg\.com\.tw\//);25 expect(it0!.available).toBe(true);26 expect(unixToDate(it0!.postTime)?.toISOString()).toBe('2026-07-16T04:17:16.000Z'); // post_time 178417543627 expect(toItem({ id: 'x', name: 'no price' })).toBeNull();28 });2930 it('builds the public search and batch item URLs', () => {31 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');32 expect(itemsUrl(['1', '2'])).toBe('https://rapi.ruten.com.tw/api/items/v2/list?gno=1,2&level=simple');33 });3435 it('normalises the search fixture into TWD listings with grades, language and attribution URLs', async () => {36 const fx = loadFixture('ruten', 'pokemon-psa10-search-p1');37 const out = await connector.normalize(fx.raw);38 expect(out.length).toBeGreaterThan(5);39 const l = out[0];40 if (l?.kind !== 'listing') throw new Error('expected listing');41 expect(l.currency).toBe('TWD');42 expect(l.price).toBe(10500);43 expect(l.attributes.categorySlug).toBe('pokemon');44 expect(l.attributes.identifiers.ruten_item_id).toBe('22629136833498');45 expect(l.attributes.language).toBe('Chinese');46 expect(l.grade.grader).toBe('psa');47 expect(l.grade.grade).toBe('10');48 expect(l.sourceUrl).toBe('https://www.ruten.com.tw/item/show?22629136833498');49 expect(l.listingType).toBe('fixed_price');50 expect(l.endsAt).toBeNull(); // far-future placeholder deadline is dropped51 expect(l.attributes.metadata.sold_count).toBe(0);52 const sold = out.find((r) => r.kind === 'listing' && r.availability === 'sold');53 expect(sold).toBeDefined(); // stock 0 with sold_num > 0 → sold, never emitted as a sale54 });5556 it('detects card language from Chinese titles', () => {57 expect(cjkLanguage('寶可夢 PTCG 日文版 SM8a 058 SR')).toBe('Japanese');58 expect(cjkLanguage('寶可夢 PTCG 中文版 莉莉艾的全力')).toBe('Chinese');59 expect(cjkLanguage('PTCG 寶可夢 PSA10 竹蘭的霸氣')).toBeNull();60 });61});62