import { describe, expect, it } from 'vitest'; import { getConnectorMeta, missingRequirements } from '@rareindex/connectors'; import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing'; import createConnector, { buildUrl, redact, toHit } from './index.js'; const connector = createConnector(getConnectorMeta('yahoo-shopping-jp')); /** Verbatim hit from the official itemSearch V3 documentation sample (query=nike). */ const DOC_HIT = { index: 1, name: '<NIKE(ナイキ)>WOMENS AIR RIFT エアリフト', description: 'ZOZO問い合わせ番号:53582864…', headLine: '', url: 'https://store.shopping.yahoo.co.jp/zozo/52582864.html', inStock: true, code: 'zozo_52582864', condition: 'new', imageId: 'zozo_52582864', image: { small: 'https://item-shopping.c.yimg.jp/i/c/zozo_52582864', medium: 'https://item-shopping.c.yimg.jp/i/g/zozo_52582864' }, review: { rate: 0, count: 0, url: 'https://shopping.yahoo.co.jp/review/item/list?store_id=zozo&page_key=52582864' }, affiliateRate: 1, price: 10450, premiumPrice: 10450, priceLabel: { taxable: null, defaultPrice: 10450 }, shipping: { code: 1, name: '設定なし' }, genreCategory: { id: 23699, name: 'レディーススニーカー', depth: 4 }, brand: { id: 1319, name: 'NIKE' }, janCode: '', releaseDate: null, seller: { sellerId: 'zozo', name: 'ZOZOTOWN', url: 'https://paypaymall.yahoo.co.jp/store/zozo/top/', isBestSeller: false }, payment: '1 4 16 4096' }; describe('yahoo-shopping-jp (gated)', () => { runFixtureSuite(connector, it, expect); it('is reported as gated on YAHOO_JP_APP_ID', () => { expect(missingRequirements(connector.meta, {})).toEqual(['YAHOO_JP_APP_ID']); expect(missingRequirements(connector.meta, { YAHOO_JP_APP_ID: 'x' })).toEqual([]); }); it('maps a documented hit (nested brand/seller/genre/image, empty JAN → null)', () => { const h = toHit(DOC_HIT); expect(h).toMatchObject({ code: 'zozo_52582864', price: 10450, condition: 'new', inStock: true, janCode: null, brand: 'NIKE', sellerId: 'zozo', sellerName: 'ZOZOTOWN', genreCategory: 'レディーススニーカー', genreCategoryId: 23699, image: 'https://item-shopping.c.yimg.jp/i/g/zozo_52582864', reviewCount: 0 }); expect(toHit({ ...DOC_HIT, janCode: '4905524535815' })?.janCode).toBe('4905524535815'); expect(toHit({ ...DOC_HIT, price: 0 })).toBeNull(); }); it('builds the documented request and redacts the app id', () => { const u = buildUrl({ appId: 'CLIENT', query: 'ロレックス', start: 51, condition: 'used' }); expect(u.startsWith('https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch?')).toBe(true); expect(u).toContain('results=50'); expect(u).toContain('start=51'); expect(u).toContain('condition=used'); expect(u).toContain('image_size=600'); expect(redact(u)).toContain('appid=***'); expect(redact(u)).not.toContain('CLIENT'); }); it('normalises the documentation sample into a JPY store listing with JAN/EAN identifiers when present', async () => { const fx = loadFixture('yahoo-shopping-jp', 'docs-sample-nike'); const out = await connector.normalize(fx.raw); const l = out[0]; if (l?.kind !== 'listing') throw new Error('expected listing'); expect(l.price).toBe(10450); expect(l.currency).toBe('JPY'); expect(l.attributes.categorySlug).toBe('nike_jordan'); expect(l.attributes.identifiers).toEqual({ yahoo_shopping_code: 'zozo_52582864' }); const withJan = await connector.normalize({ ...fx.raw, payload: { ...(fx.raw.payload as object), hits: [{ ...toHit(DOC_HIT)!, janCode: '4905524535815', condition: 'used' }] } }); const j = withJan[0]; if (j?.kind !== 'listing') throw new Error('expected listing'); expect(j.attributes.identifiers.jan).toBe('4905524535815'); expect(j.attributes.identifiers.ean).toBe('4905524535815'); expect(j.condition.conditionRaw).toBe('Used'); expect(j.confidence).toBeGreaterThan(l.confidence); }); });