import test from 'node:test'; import assert from 'node:assert/strict'; import { libraryRequest,libraryPath,libraryLabel,libraryRecordInfo,libraryProductRequest,libraryCatalogQuery,LIBRARY_GROUPS } from '../src/core/library.js'; import { readLibrary } from '../src/services/library-service.js'; import { createLibraryApi } from '../server/library-api.mjs'; import { Readable } from 'node:stream'; test('资料条件同时保留,未知字段和日期不被关键词替代',()=>{ const r=libraryRequest({type:'reviews',platform:'jd',brand:'ksf',category:'banmian',q:'份量',from:'2026-10-01',to:'2026-11-05',shopId:'__unknown'}); assert.deepEqual(r.filters,{platform:'jd',brand:'ksf',category:'banmian',shopId:{exists:false},reviewDate:{min:'2026-10-01',max:'2026-11-05'}}); assert.equal(r.keyword,'份量');assert.equal(libraryRequest({type:'social',from:'2026-10-01'}).filters.publishedDate.min,'2026-10-01'); }); test('详情深链接保留查询,标签使用真实字典',()=>{ assert.equal(libraryPath({type:'products',brand:'ksf'},{entity:'products',id:'jd:123'}),'/library/products/jd%3A123?type=products&brand=ksf'); assert.equal(libraryLabel(null),'未知/待核对');assert.equal(libraryLabel('ksf',{taxonomy:{brands:[{code:'ksf',name:'康师傅'}],categories:[]}}),'康师傅'); }); test('商品评论和价格只排序,不缩小结果范围;URL保留排序且不影响商品内评论',()=>{ const query={type:'products',platform:'jd',brand:'ksf',category:'banmian',sort:'reviews_desc',cursor:'page-2'}; assert.deepEqual(libraryRequest(query).filters,{platform:'jd',brand:'ksf',category:'banmian'}); assert.equal(libraryRequest(query).sort,'reviews_desc'); assert.equal(libraryRequest({...query,sort:'price_asc'}).sort,'price_asc'); assert.equal(libraryRequest({...query,sort:'price_desc'}).sort,'price_desc'); assert.ok(libraryPath(query).includes('sort=reviews_desc')); assert.deepEqual(libraryProductRequest('jd:123',{...query,panel:'reviews'}).filters,{productId:'jd:123'}); assert.equal(libraryProductRequest('jd:123',{...query,panel:'reviews'}).sort,undefined); assert.equal(libraryRequest({...query,type:'social'}).sort,undefined); assert.equal(libraryRequest({...query,sort:'not-supported'}).sort,undefined); assert.deepEqual(libraryRequest({type:'products',reviewCount:'has',price:'10-20'}).filters,{}); }); test('阅读信息按资料类型选择业务事实,内部规则、编号和指纹不进入展示',()=>{ const record={marketplace:'jd',shop_name:'康师傅旗舰店',category_basis:'rule_title_explicit',brand_basis:'rule_title_prefix', classification_state:'supported',source_record_id:'internal-source',item_id:'123',shop_id:'456', expected_sha256:'a'.repeat(64),issues:['INTERNAL_RULE'],source_time_raw:'2026-11-04'}; assert.deepEqual(libraryRecordInfo(record,'products'),[['销售平台','京东'],['店铺','康师傅旗舰店'],['观察时间','2026-11-04']]); assert.deepEqual(libraryRecordInfo({...record,likes:0,comment_count:30},'social'),[['平台点赞数','0'],['平台显示评论量','30']]); }); test('阅读价格使用元且不暴露分单位和技术字段,单位价格只在核对后展示',()=>{ const record={amount_minor:2650,raw_amount:'26.5',price_basis:'search_listing_snapshot',price_per_100g:3.5,unit_price_verification:'unavailable'}; assert.deepEqual(libraryRecordInfo(record,'prices'),[['观察价格','¥26.50'],['价格口径','搜索展示价']]); assert.ok(libraryRecordInfo({...record,unit_price_verification:'verified'},'prices').some(([k,v])=>k==='每100克价格(元)'&&v==='3.5')); }); test('商品是浏览入口,详情内查询固定商品编号并隔离列表条件和分页',()=>{ assert.deepEqual(LIBRARY_GROUPS.flatMap(group=>group.entities),['products','social','documents','reports']); const query={type:'products',platform:'tmall',brand:'other-brand',category:'other-category',q:'旧列表词',cursor:'list-cursor',panel:'reviews',relatedQ:'包装',relatedQuality:'audit',relatedFrom:'2026-11-01',relatedTo:'2026-11-05',relatedCursor:'review-cursor'}; assert.deepEqual(libraryProductRequest('jd:123',query),{op:'browse',entity:'reviews',filters:{productId:'jd:123'},quality:'audit',limit:20,keyword:'包装'}); assert.equal(libraryProductRequest('jd:123',{panel:'reviews',relatedCursor:'review-cursor'}).cursor,'review-cursor'); assert.deepEqual(libraryProductRequest('jd:123',{...query,panel:'prices'}),{op:'browse',entity:'prices',filters:{productId:'jd:123'},quality:'audit',limit:20,keyword:'包装'}); assert.equal(libraryProductRequest('jd:123',{panel:'prices',relatedCursor:'price-cursor'}).cursor,'price-cursor'); assert.deepEqual(libraryCatalogQuery(query),{type:'products',platform:'tmall',brand:'other-brand',category:'other-category',q:'旧列表词',cursor:'list-cursor'}); assert.equal(libraryProductRequest('jd:123',{panel:'overview'}),null); assert.equal(libraryProductRequest('jd:123',{panel:'unknown'}),null); }); test('接口失败不作为空数据,浏览器不发送服务凭据',async()=>{ await assert.rejects(()=>readLibrary({op:'summary'},{fetchImpl:async(_url,opt)=>{assert.deepEqual(opt.headers,{'content-type':'application/json'});return {ok:false,json:async()=>({ok:false,error:{code:'DATABASE_NOT_INSTALLED'}})};}}),/尚未连接/); await assert.rejects(()=>readLibrary({op:'summary'},{fetchImpl:async()=>({ok:true,json:async()=>{throw Error('HTML')}})}),/尚未接通/); }); test('生产接口默认拒绝匿名及写操作,凭据不进入返回',async()=>{ const api=createLibraryApi({token:'test-token'});let status,output; const req=Readable.from(['{"op":"summary"}']);req.method='POST';req.headers={}; await api(req,{writeHead(s){status=s;},end(v){output=JSON.parse(v);}}); assert.equal(status,401);assert.equal(output.error.code,'AUTH_REQUIRED');assert.ok(!JSON.stringify(output).includes('test-token')); });