SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
4.0 KB · 69 lines typescript
Raw Blame History
1import { describe, expect, it } from 'vitest';2import { readFileSync } from 'node:fs';3import path from 'node:path';4import meta from './meta.json' with { type: 'json' };5import { localMeta } from '../_lib/local-meta.js';6import { fixtureDir, listFixtures, loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';7import createConnector, { parseListPage } from './index.js';8import { jpCondition, parseJpCardTitle } from '../_g1-cards-eu-jp-lib/index.js';910const connector = createConnector(localMeta(meta));1112describe('cardrush', () => {13  runFixtureSuite(connector, it, expect);1415  it('parses MTG and Pokémon store titles', () => {16    const m = parseJpCardTitle('[PLD](黒枠)ラノワールのエルフ/Llanowar Elves《日本語》【4ED】', { bracket: 'set' });17    expect(m).toMatchObject({ name: 'Llanowar Elves', conditionRaw: 'PLD', setCode: '4ED', language: 'Japanese', notes: ['黒枠'], rarity: null });18    expect(jpCondition('PLD')).toBeNull();19    const e = parseJpCardTitle('[EX]暗黒の儀式/Dark Ritual《英語》【4ED】', { bracket: 'set' });20    expect(e).toMatchObject({ name: 'Dark Ritual', language: 'English', setCode: '4ED' });21    expect(jpCondition(e.conditionRaw)).toBe('excellent');22    const p = parseJpCardTitle('〔PSA10鑑定済〕ミロカロスδ-デルタ種【★】{013/068}');23    expect(p).toMatchObject({ name: 'ミロカロスδ-デルタ種', grader: 'psa', grade: '10', rarity: '★', number: '013', total: '068' });24    const box = parseJpCardTitle('ブースターパック 受け継がれる意志【未開封BOX】{-}');25    expect(box.sealed).toBe(true);26    expect(box.number).toBeNull();27    const mixed = parseJpCardTitle('〔※状態難/PSA8鑑定済〕リザードン LV.76(かえん/マークあり)【★】{旧裏}');28    expect(mixed.grade).toBe('8');29    expect(mixed.number).toBe('旧裏');30  });3132  it('parses the saved list page: items, yen prices, stock, sold-out flag, pager', () => {33    const doc = readFileSync(path.join(fixtureDir('cardrush'), 'list-page.html'), 'utf8');34    const { title, items, hasNext } = parseListPage(doc, 'www.cardrush-mtg.jp');35    expect(title).toBeTruthy();36    expect(items.length).toBe(100);37    expect(hasNext).toBe(true);38    const first = items[0]!;39    expect(first.url).toMatch(/^https:\/\/www\.cardrush-mtg\.jp\/product\/\d+$/);40    expect(first.priceJpy).toBeGreaterThan(0);41    expect(first.image).toMatch(/^https:\/\//);42    expect(items.some((i) => i.soldOut && i.stock === 0)).toBe(true);43    expect(items.some((i) => !i.soldOut && (i.stock ?? 0) > 0)).toBe(true);44  });4546  it('emits JPY listings per store with the right category, language and availability', async () => {47    for (const name of listFixtures('cardrush')) {48      const fx = loadFixture('cardrush', name);49      const out = await connector.normalize(fx.raw);50      expect(out.length).toBeGreaterThan(0);51      const payload = fx.raw.payload as { store: { categorySlug: string }; items: Array<{ soldOut: boolean; title: string }> };52      for (const r of out) {53        if (r.kind !== 'listing') continue;54        expect(r.currency).toBe('JPY');55        expect(r.price).toBeGreaterThan(0);56        expect(r.seller).toBe('Cardrush');57        expect(r.attributes.categorySlug).toBe(payload.store.categorySlug);58        expect(['Japanese', 'English', 'Korean', 'Chinese']).toContain(r.attributes.language);59        expect(r.attributes.identifiers.cardrush_product_id).toMatch(/^www\.cardrush[a-z-]*\.jp\/\d+$/);60      }61      const ended = out.filter((r) => r.kind === 'listing' && r.availability === 'ended').length;62      const soldOutIn = payload.items.filter((i) => i.soldOut && !/オリパ|サプライ/.test(i.title)).length;63      expect(ended).toBe(soldOutIn);64      if (name.includes('sealed')) for (const r of out) if (r.kind === 'listing') expect(r.condition.completeness).toBe('sealed');65      if (name.includes('pokemon')) expect(out.some((r) => r.kind === 'listing' && r.grade.grader === 'psa' && r.grade.grade)).toBe(true);66    }67  });68});69