TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { describe, expect, it } from 'vitest';2import { getConnectorMeta, missingRequirements } from '@rareindex/connectors';3import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';4import createConnector, { buildUrl, redact, toItem } from './index.js';56const connector = createConnector(getConnectorMeta('rakuten-ichiba'));78describe('rakuten-ichiba (gated)', () => {9 runFixtureSuite(connector, it, expect);1011 it('is reported as gated on RAKUTEN_APP_ID', () => {12 expect(connector.meta.requires).toContain('RAKUTEN_APP_ID');13 expect(missingRequirements(connector.meta, {})).toEqual(['RAKUTEN_APP_ID']);14 expect(missingRequirements(connector.meta, { RAKUTEN_APP_ID: 'x' })).toEqual([]);15 });1617 it('accepts formatVersion 1 ({ Item: {…} }, [{ imageUrl }]) and formatVersion 2 (flat, string images)', () => {18 const v1 = toItem({ Item: { itemCode: 'shop:1', itemName: 'A', itemPrice: 1200, itemUrl: 'https://item.rakuten.co.jp/shop/1/', mediumImageUrls: [{ imageUrl: 'https://img/1.jpg?_ex=128x128' }], availability: 1, shopName: 'S', genreId: 101905 } });19 expect(v1).toMatchObject({ itemCode: 'shop:1', itemPrice: 1200, images: ['https://img/1.jpg'], availability: 1, genreId: '101905' });20 const v2 = toItem({ itemCode: 'shop:2', itemName: 'B', itemPrice: '3000', itemUrl: 'https://item.rakuten.co.jp/shop/2/', mediumImageUrls: ['https://img/2.jpg'], availability: 0 });21 expect(v2).toMatchObject({ itemCode: 'shop:2', itemPrice: 3000, images: ['https://img/2.jpg'], availability: 0 });22 expect(toItem({ itemCode: 'x', itemName: 'no price', itemUrl: 'u' })).toBeNull();23 });2425 it('builds the documented request (2026-07-01 endpoint with access key, legacy without) and redacts credentials', () => {26 const modern = buildUrl({ appId: 'APP', accessKey: 'KEY', keyword: 'ポケモンカード', page: 2 });27 expect(modern.startsWith('https://openapi.rakuten.co.jp/ichibams/api/IchibaItem/Search/20260701?')).toBe(true);28 expect(modern).toContain('formatVersion=2');29 expect(modern).toContain('hits=30');30 expect(modern).toContain('page=2');31 expect(modern).toContain('accessKey=KEY');32 const legacy = buildUrl({ appId: 'APP', accessKey: null, keyword: 'x', page: 1 });33 expect(legacy.startsWith('https://app.rakuten.co.jp/services/api/IchibaItem/Search/20220601?')).toBe(true);34 expect(legacy).not.toContain('accessKey');35 expect(redact(modern)).not.toContain('KEY');36 expect(redact(modern)).toContain('applicationId=***');37 });3839 it('normalises the documented response shape into JPY listings (grade, condition, bundle flag, sale window)', async () => {40 const fx = loadFixture('rakuten-ichiba', 'docs-format-v2');41 const out = await connector.normalize(fx.raw);42 expect(out).toHaveLength(2);43 const [a, b] = out;44 if (a?.kind !== 'listing' || b?.kind !== 'listing') throw new Error('expected listings');45 expect(a.currency).toBe('JPY');46 expect(a.grade).toMatchObject({ grader: 'psa', grade: '10' });47 expect(a.condition.conditionRaw).toBe('Used');48 expect(a.attributes.metadata.tax_included).toBe(true);49 expect(b.availability).toBe('ended');50 expect(b.attributes.metadata.is_bundle).toBe(true);51 expect(b.listedAt?.toISOString()).toBe('2026-09-01T01:00:00.000Z');52 expect(b.endsAt?.toISOString()).toBe('2026-09-30T14:59:00.000Z');53 });54});55