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%
3.3 KB · 55 lines typescript
Raw Blame History
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