verify-public.mjs 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import {fileURLToPath} from 'node:url';
  5. const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
  6. const manifest=JSON.parse(fs.readFileSync(path.join(root,'deploy/industry-ui-files.json')));
  7. const args=process.argv.slice(2);
  8. if(args.length&&!(args.length===2&&args[0]==='--origin'))throw Error('USAGE: --origin https://kangshifu.fmode.cn');
  9. const base=new URL(args[1]||'https://kangshifu.fmode.cn');
  10. if(!['https:','http:'].includes(base.protocol)||base.username||base.password||base.pathname!=='/')throw Error('INVALID_ORIGIN');
  11. const sha=b=>crypto.createHash('sha256').update(b).digest('hex');
  12. const normal=b=>Buffer.from(b.toString('utf8').replaceAll('\r\n','\n'));
  13. const result={ok:false,release:manifest.release,checkedAt:new Date().toISOString(),origin:base.origin,modules:[],checks:[],productionWrites:0,paidCalls:0};
  14. const check=(name,ok)=>{result.checks.push({name,ok:!!ok});};
  15. const post=async(endpoint,body)=>{
  16. const res=await fetch(new URL(endpoint,base),{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body),signal:AbortSignal.timeout(30000)});
  17. const data=await res.json();if(!res.ok||!data.ok)throw Error('API_READ_FAILED:'+endpoint+':'+body.op);
  18. check(endpoint+':'+body.op+':readOnly',data.readOnly===true&&data.databaseWrites===0);return data;
  19. };
  20. try{
  21. for(const file of manifest.files){
  22. const url=new URL(file.path,base);url.searchParams.set('r14verify',String(Date.now()));
  23. const res=await fetch(url,{headers:{'cache-control':'no-cache'},signal:AbortSignal.timeout(30000)});const bytes=Buffer.from(await res.arrayBuffer());
  24. const row={path:file.path,status:res.status,contentType:res.headers.get('content-type'),bytes:bytes.length,sha256:sha(bytes),normalizedSha256:sha(normal(bytes)),cacheControl:res.headers.get('cache-control'),etag:res.headers.get('etag')};
  25. row.matches=res.ok&&row.contentType?.includes('javascript')&&row.normalizedSha256===file.sha256;result.modules.push(row);
  26. }
  27. check('all-11-public-modules',result.modules.length===11&&result.modules.every(m=>m.matches));
  28. const home=await fetch(new URL('index.html',base),{headers:{'cache-control':'no-cache'},signal:AbortSignal.timeout(30000)});
  29. result.homepageSha256=sha(Buffer.from(await home.arrayBuffer()));check('homepage-unchanged',home.ok&&result.homepageSha256===manifest.homepageSha256);
  30. const s=await post('api/library',{op:'summary'}),c=await post('api/industry',{op:'contract'}),w=await post('api/industry',{op:'workshop',filters:{category:'banmian'}});
  31. result.generation=s.generation;result.counts=s.counts;result.analysisCounts=s.analysisCounts;result.defaultScope=w.scope;
  32. check('generation-consistent',s.generation===c.generation&&s.generation===w.generation);
  33. check('8-groups-52-options',Object.keys(w.dimensions).length===8&&Object.values(w.dimensions).flat().length===52);
  34. const iBrand=await post('api/industry',{op:'workshop',filters:{platform:'jd',brand:'tongyi'}});
  35. const iShop=await post('api/industry',{op:'workshop',filters:{platform:'jd',shopId:'1000084104'}});
  36. check('industry-brand-limits-shops',!iBrand.rangeFacets.shopId.some(r=>r.value==='1000084104'&&r.products>0));
  37. check('industry-shop-limits-brands',iShop.rangeFacets.brand.some(r=>r.value==='ksf'&&r.products>0)&&iShop.rangeFacets.brand.every(r=>r.value!=='tongyi'||!r.products));
  38. const lBrand=await post('api/library',{op:'browse',entity:'products',quality:'audit',filters:{platform:'jd',brand:'tongyi'},limit:1});
  39. const lShop=await post('api/library',{op:'browse',entity:'products',quality:'audit',filters:{platform:'jd',shopId:'1000084104'},limit:1});
  40. check('library-brand-limits-shops',!lBrand.facets.shopId.rows.some(r=>r.shop_id==='1000084104'&&r.recordCount>0));
  41. check('library-shop-limits-brands',lShop.facets.brand.rows.some(r=>r.brand==='ksf'&&r.recordCount>0)&&lShop.facets.brand.rows.every(r=>r.brand!=='tongyi'||!r.recordCount));
  42. const p=await post('api/industry',{op:'policies'});const review=p.applications.reviewedEvidence;
  43. result.policy={verified:review.verifiedClauses,pending:review.pendingClauses,originals:review.originalDocuments};
  44. check('policy-review-remains-12-19-13',result.policy.verified===12&&result.policy.pending===19&&result.policy.originals===13);
  45. result.ok=result.checks.every(c=>c.ok);
  46. }catch(e){result.error=e.message;}
  47. console.log(JSON.stringify(result,null,2));if(!result.ok)process.exitCode=1;