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.9 KB · 83 lines typescript
Raw Blame History
1import { afterEach, describe, expect, it } from 'vitest';2import metaJson from './meta.json' with { type: 'json' };3import { loadFixture, runFixtureSuite } from '@rareindex/connectors/testing';4import { missingRequirements } from '@rareindex/connectors';5import { localMeta } from '../_lib/local-meta.js';6import { clearTokenCache } from '../_g10-lib/index.js';7import createConnector, { apiBase, getAppToken, parseSearchResponse, searchUrl } from './index.js';89const meta = localMeta(metaJson);10const connector = createConnector(meta);1112describe('ebay-browse (gated)', () => {13  runFixtureSuite(connector, it, expect);14  afterEach(() => clearTokenCache());1516  it('is reported as gated until EBAY_CLIENT_ID / EBAY_CLIENT_SECRET exist', () => {17    expect(meta.requires).toEqual(['EBAY_CLIENT_ID', 'EBAY_CLIENT_SECRET']);18    expect(missingRequirements(meta, {})).toEqual(['EBAY_CLIENT_ID', 'EBAY_CLIENT_SECRET']);19    expect(missingRequirements(meta, { EBAY_CLIENT_ID: 'a', EBAY_CLIENT_SECRET: 'b' })).toEqual([]);20    expect(apiBase(undefined).token).toBe('https://api.ebay.com/identity/v1/oauth2/token');21    expect(apiBase('sandbox').api).toBe('https://api.sandbox.ebay.com');22  });2324  it('builds search URLs with marketplace-independent params and filters', () => {25    const url = searchUrl('https://api.ebay.com', { q: 'pokemon psa 10', categoryIds: ['183454'], categorySlug: 'pokemon', filter: 'price:[100..],priceCurrency:USD' }, { limit: 200, offset: 400, filter: 'buyingOptions:{FIXED_PRICE|AUCTION}', sort: 'newlyListed' });26    const u = new URL(url);27    expect(u.pathname).toBe('/buy/browse/v1/item_summary/search');28    expect(u.searchParams.get('q')).toBe('pokemon psa 10');29    expect(u.searchParams.get('category_ids')).toBe('183454');30    expect(u.searchParams.get('filter')).toBe('buyingOptions:{FIXED_PRICE|AUCTION},price:[100..],priceCurrency:USD');31    expect(u.searchParams.get('limit')).toBe('200');32    expect(u.searchParams.get('offset')).toBe('400');33  });3435  it('requests and caches the client-credentials token (Basic auth, form body, api_scope)', async () => {36    let calls = 0;37    const fakeFetch = (async (url: string | URL | Request, init?: RequestInit) => {38      calls++;39      expect(String(url)).toBe('https://api.ebay.com/identity/v1/oauth2/token');40      expect((init?.headers as Record<string, string>).authorization).toBe(`Basic ${Buffer.from('id:secret').toString('base64')}`);41      expect((init?.headers as Record<string, string>)['content-type']).toBe('application/x-www-form-urlencoded');42      expect(String(init?.body)).toBe('grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope');43      return new Response(JSON.stringify({ access_token: 'v^1.1#tok', expires_in: 7200, token_type: 'Application Access Token' }), { status: 200 });44    }) as typeof fetch;45    const t1 = await getAppToken('id', 'secret', 'https://api.ebay.com/identity/v1/oauth2/token', fakeFetch);46    const t2 = await getAppToken('id', 'secret', 'https://api.ebay.com/identity/v1/oauth2/token', fakeFetch);47    expect(t1).toBe('v^1.1#tok');48    expect(t2).toBe(t1);49    expect(calls).toBe(1);50  });5152  it('parses the documented search response shape and normalises listings in native currency', async () => {53    const fx = loadFixture('ebay-browse', 'docs-search-ebay-us');54    const payload = fx.raw.payload as { items: unknown[] };55    const parsed = parseSearchResponse({ itemSummaries: payload.items, total: 12345, next: 'https://api.ebay.com/buy/browse/v1/item_summary/search?q=x&limit=2&offset=2' });56    expect(parsed?.items.length).toBe(payload.items.length);57    expect(parsed?.next).toContain('offset=2');58    expect(parseSearchResponse({ errors: [{ errorId: 12000 }] })).toBeNull();59    const out = await connector.normalize(fx.raw);60    expect(out.length).toBeGreaterThan(0);61    const first = out[0]!;62    if (first.kind !== 'listing') throw new Error('expected listing');63    expect(first.currency).toBe('USD');64    expect(first.attributes.identifiers.ebay_item_id).toMatch(/^v1\|\d+\|\d+$/);65    expect(first.attributes.identifiers.ebay_epid).toBeTruthy();66    expect(first.seller).toBeTruthy();67    expect(first.sellerReputation).toMatch(/%/);68    expect(first.shippingCost).not.toBeNull();69    expect(first.listingType).toBe('best_offer'); // FIXED_PRICE + BEST_OFFER → best_offer70    const auction = out.find((r) => r.kind === 'listing' && r.listingType === 'auction');71    expect(auction && auction.kind === 'listing' ? auction.bidCount : null).not.toBeNull();72    const gb = loadFixture('ebay-browse', 'docs-search-ebay-gb-converted');73    const gbOut = await connector.normalize(gb.raw);74    expect(gbOut.length).toBe(1);75    const g = gbOut[0]!;76    if (g.kind !== 'listing') throw new Error('expected listing');77    expect(g.currency).toBe('GBP');78    expect(g.attributes.metadata.price_converted_by_ebay).toBe(true);79    expect(g.grade.grader).toBe('psa');80    expect(g.grade.grade).toBe('10');81  });82});83