TypeScript 61.9%
HTML 37.2%
SQL 0.7%
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