import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import test from 'node:test'; import { JdSpClient } from '../src/modules/listing-ai/clients/jd-sp.client.js'; import { normalizeJdListing } from '../src/modules/listing-ai/normalization/jd-listing.normalizer.js'; test('JD detail path productId participates in signature but not query string', async () => { let capturedUrl='';let capturedHeaders:Headers|null=null; const fetchImpl:typeof fetch=async(input,init)=>{capturedUrl=String(input);capturedHeaders=new Headers(init?.headers);return new Response(JSON.stringify({success:true,data:{productInfo:{productId:1001}}}),{status:200,headers:{'content-type':'application/json'}});}; const client=new JdSpClient({baseUrl:'https://api-cn.jd.com/rest',appKey:'app-key',appSecret:'app-secret',timeoutMs:1_000,retries:0},fetchImpl); await client.get('/sp-product/v0/products/1001',{scene:'pop'},'access-token',{productId:'1001'}); assert.equal(new URL(capturedUrl).searchParams.get('scene'),'pop'); assert.equal(new URL(capturedUrl).searchParams.has('productId'),false); const timestamp=capturedHeaders!.get('X-JOS-Timestamp')!; const fields:Record={'X-JOS-Access-Token':'access-token','X-JOS-App-Key':'app-key','X-JOS-Timestamp':timestamp,productId:'1001',scene:'pop'}; const plain=Object.keys(fields).sort().map((key)=>`${key}${fields[key]}`).join(''); const expected=createHash('md5').update(`app-secret${plain}app-secret`).digest('hex').toUpperCase(); assert.equal(capturedHeaders!.get('X-JOS-Sign'),expected); }); test('JD image paths are converted to safe HTTPS CDN URLs',()=>{ const source=normalizeJdListing({workspaceId:'demashi',shopId:'shop',row:{productId:1001},detail:{productInfo:{productId:1001,productName:'商品'},material:{mainImages:[{imageInfoList:[{imgUrl:'jfs/t1/demo.jpg',orderSort:1,primaryFlag:true}]}]}}}); assert.equal(source.images[0]?.url,'https://img10.360buyimg.com/n1/jfs/t1/demo.jpg'); });