test: verify self-operated analytics through TEST Gateway and synthetic query fixtures

This commit is contained in:
2026-10-12 20:59:54 +08:00
parent d4dc4f875a
commit b702ed9f45
3 changed files with 520 additions and 0 deletions
@@ -0,0 +1,308 @@
/**
* TEST-only Gateway acceptance. Default is a local plan; --execute performs login,
* read-only queries and, with --probe-off-command, one deliberately rejected INVENTORY
* command after verifying OFF. Never collects, publishes, creates fixtures or changes config.
* Credentials: node --env-file=<explicit test-*.env> BEFORE this script; this script does not read credential files.
* Required env: CHONGCHONG_ENV=TEST, CHONGCHONG_ADMIN_API_BASE_URL (ends /api),
* CHONGCHONG_ADMIN_USERNAME/PASSWORD, CHONGCHONG_MERCHANT_USERNAME/PASSWORD,
* SELFOP_READONLY_USERNAME/PASSWORD (read permission only),
* SELFOP_DENIED_USERNAME/PASSWORD (platform without read or sync permission).
* Example: node --env-file=<test file> <script> --environment TEST --source-account <account>
* --warehouse <configured warehouse> --other-warehouse <second configured warehouse>
* --report <local json> --synthetic-fixture <fixture notes json> --execute --probe-off-command
* Reports contain case IDs, status, machine codes and request/trace IDs only; no payloads,
* URLs, credentials or customer identifiers. Partial/missing evidence is BLOCKED (exit 2).
* Re-run reads current state and uses a fresh rejection request key. No deletion or SQL.
* Register this service-local entry in the maintained script index before release.
*/
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { randomUUID } from 'node:crypto';
const ROOT='/platform/v1/last-leg/self-operated-analytics';
const READ='platform:self-operated-analytics:list', SYNC='platform:self-operated-analytics:sync';
export const READ_ROUTES=Object.freeze(['sku-sales-summary','sku-daily-sales','hot-skus','slow-skus',
'inventory-coverage','owners','owner-overview','sync-overview','sync-tasks','sync-runs']);
export const SAMPLE_REQUIREMENTS=Object.freeze([
'Two configured warehouses with BUSINESS/PUBLISHED outbound and inventory versions; do not collect or publish via this script.',
'At least two owners sharing one SKU, with distinguishable quantities and complete/partial/unknown days.',
'At least one existing warehouse sync task and attempt for task-detail and task/run warehouse isolation.',
]);
const demand=(condition,code)=>{if(!condition)throw new Error(code);};
const success=r=>r.http===200&&String(r.body?.code)==='200';
export const denied=r=>[401,403].includes(r.http)||(r.http===200&&['A0401','A0403'].includes(String(r.body?.code)));
export const businessRejected=r=>r.http===200&&['B0001','A0400','A0404','A0409','A0422'].includes(String(r.body?.code));
// A wrong scope, generic business error or infrastructure failure is not evidence of the OFF gate.
export const offRejected=r=>businessRejected(r)&&r.body?.message==='采集功能未启用,当前环境不允许执行采集或重试';
const safeCode=value=>typeof value==='string'&&/^[A-Za-z0-9_-]{1,80}$/.test(value)?value:null;
export function evidence(r){return {http:r.http,code:safeCode(String(r.body?.code??'')),
requestId:safeCode(r.body?.requestId??r.requestId),traceId:safeCode(r.body?.traceId??r.traceId)};}
export function parseArguments(args){
const o={execute:false,probeOff:false};
const names={'--environment':'environment','--source-account':'account','--warehouse':'warehouse',
'--other-warehouse':'otherWarehouse','--report':'report','--synthetic-fixture':'syntheticFixture'};
const seen=new Set();
for(let i=0;i<args.length;i++){
const key=args[i];demand(!seen.has(key),'DUPLICATE_ARGUMENT');seen.add(key);
if(key==='--execute'){o.execute=true;continue;}
if(key==='--probe-off-command'){o.probeOff=true;continue;}
demand(names[key]&&args[i+1]&&!args[i+1].startsWith('--'),'INVALID_ARGUMENT');o[names[key]]=args[++i];
}
demand(o.environment==='TEST','EXPLICIT_TEST_REQUIRED');
if(o.execute){demand(o.account&&o.warehouse&&o.report,'SCOPE_AND_REPORT_REQUIRED');}
if(o.otherWarehouse)demand(o.otherWarehouse!==o.warehouse,'SECOND_WAREHOUSE_MUST_DIFFER');
return o;
}
export function validateEnvironment(env){
demand(env.CHONGCHONG_ENV?.toUpperCase()==='TEST','TEST_ENV_REQUIRED');
let base;try{base=new URL(env.CHONGCHONG_ADMIN_API_BASE_URL);}catch{throw new Error('INVALID_GATEWAY');}
demand(['https:','http:'].includes(base.protocol)&&!base.username&&!base.password&&!base.search&&!base.hash&&
/\/api\/?$/.test(base.pathname),'INVALID_GATEWAY');
return base.href.replace(/\/$/,'');
}
export function plan(){return {environment:'TEST',mode:'PLAN',thirdPartyCalls:false,fixtureWrites:false,
routes:READ_ROUTES,authentication:['anonymous','authorized platform','read-only platform','denied platform','merchant'],
offProbe:'Opt-in only; rechecks OFF immediately before one rejected INVENTORY command. S0500 is a failure.',
syntheticEvidence:'--synthetic-fixture <reviewed notes JSON> enables thirteen numerical/isolation checks; never proves provider coverage.',
samples:SAMPLE_REQUIREMENTS,report:'Only case identifiers, outcomes, machine codes and request/trace IDs.'};}
const query=(values)=>new URLSearchParams(Object.entries(values).filter(([,v])=>v!==null&&v!==undefined).map(([k,v])=>[k,String(v)])).toString();
export function pin(context){
demand(context&&context.salesRunId!==null&&context.inventoryRunId!==null&&context.queryAsOf,'CONTEXT_MISSING');
return Object.fromEntries(['salesRunId','inventoryRunId','salesScopeHash','inventoryScopeHash','queryAsOf'].map(k=>[k,context[k]]));
}
function sameContext(a,b){return ['salesRunId','inventoryRunId','salesScopeHash','inventoryScopeHash','queryAsOf'].every(k=>a?.[k]===b?.[k]);}
export function fixtureDate(anchor,offset=0){
demand(/^\d{4}-\d{2}-\d{2}$/.test(anchor??''),'FIXTURE_ANCHOR_REQUIRED');
const date=new Date(anchor+'T00:00:00Z');date.setUTCDate(date.getUTCDate()+offset);return date.toISOString().slice(0,10);
}
export function validateSyntheticNotes(notes,options){
demand(notes?.synthetic===true&&notes.fixtureId==='SYNTHETIC-SELFOP-20261012'&&notes.sourceAccount==='lingxing-primary'&&
options.account===notes.sourceAccount&&options.warehouse==='SELFOP-TEST-A'&&options.otherWarehouse==='SELFOP-TEST-B'&&
notes.currentVersions?.A?.salesRunId&&notes.currentVersions?.B?.inventoryRunId&&notes.oldSalesRunId&&
Array.isArray(notes.assertions),'SYNTHETIC_SCOPE_MISMATCH');
}
// TEST uses Jackson NON_NULL; these nullable OpenAPI fields are optional. Missing is unknown, never zero.
const nullish=value=>value===null||value===undefined;
const quantity=(actual,expected)=>expected===null?nullish(actual):!nullish(actual)&&Number.isFinite(Number(actual))&&Number(actual)===Number(expected);
const fieldsMatch=(actual,expected)=>actual&&Object.entries(expected).every(([key,value])=>value===null?nullish(actual[key]):typeof value==='number'?quantity(actual[key],value):actual[key]===value);
/** Expectations come from the reviewed synthetic manifest; only GET requests are returned. */
export function syntheticCases(notes,context){
const d=context.anchorDate,from=fixtureDate(d,-2),until=fixtureDate(d,-1),window={rangePreset:'CUSTOM',startDate:from,endDate:until,bucket:'DAY'};
const shared='SYNTH-SHARED-SKU',owner='SYNTH-OWNER-A';
const assertion=(endpoint,predicate=()=>true)=>{
const rows=notes.assertions.filter(a=>a.endpoint===endpoint&&predicate(a));demand(rows.length===1,'FIXTURE_EXPECTATION_AMBIGUOUS');return rows[0];
};
const pair=(actual,version,hash)=>actual&&String(actual.salesRunId)===String(version.salesRunId)&&String(actual.inventoryRunId)===String(version.inventoryRunId)&&
actual.salesScopeHash===hash&&actual.inventoryScopeHash===hash;
const scopedRows=(data,warehouse)=>Array.isArray(data?.records)&&data.records.every(r=>r.warehouseCode===warehouse&&r.sourceAccount===notes.sourceAccount);
const cases=[];
const add=(id,route,params,verify,{warehouse='SELFOP-TEST-A',version=notes.currentVersions.A,pinned=true}={})=>cases.push({id:'synthetic.'+id,route,
params:{sourceAccount:notes.sourceAccount,warehouseCode:warehouse,current:1,size:100,...(pinned?pin(context):{}),...params},
verify:data=>['sync-overview'].includes(route)?verify(data):pair(data?.context,version,notes.hashes[warehouse==='SELFOP-TEST-A'?'A':'B'])&&
(!pinned||data.context.queryAsOf===context.queryAsOf)&&verify(data)});
const owners=assertion('owners').expected;
add('owners','owners',{},data=>quantity(data.total,owners.total)&&data.records.length===owners.total&&
JSON.stringify(data.records.map(r=>r.customerCode).sort())===JSON.stringify([...owners.customerCodes].sort())&&data.records.every(r=>nullish(r.customerName)));
const historical=assertion('sku-sales-summary',a=>a.expected?.records).expected;
add('date-range-and-owner-isolation','sku-sales-summary',{...window,sku:shared},data=>scopedRows(data,'SELFOP-TEST-A')&&quantity(data.total,historical.total)&&
data.records.length===historical.total&&Object.entries(historical.records).every(([code,expected])=>{
const rows=data.records.filter(r=>r.customerCode===code&&r.sku===shared);return rows.length===1&&fieldsMatch(rows[0],expected);
}));
const daily=assertion('sku-daily-sales').expected;
add('daily-exact-axis','sku-daily-sales',{...window,customerCode:owner,sku:shared},data=>data.points?.length===daily.points.length&&
data.points.every((p,i)=>p.businessDate===fixtureDate(d,Number(daily.points[i].businessDate.slice(1)))&&
quantity(p.outboundQuantity,daily.points[i].outboundQuantity)&&p.dataStatus===daily.points[i].dataStatus)&&
quantity(data.summary?.outboundQuantity,daily.summaryOutboundQuantity)&&data.summary.customerCode===owner&&data.summary.sku===shared);
const today=assertion('sku-sales-summary',a=>a.params?.rangePreset==='TODAY'&&typeof a.expected==='object').expected;
add('today-partial','sku-sales-summary',{rangePreset:'TODAY',sku:shared},data=>scopedRows(data,'SELFOP-TEST-A')&&data.records.length===2&&
Object.entries(today).every(([code,expected])=>fieldsMatch(data.records.find(r=>r.customerCode===code&&r.sku===shared),expected)));
const old=assertion('sku-sales-summary',a=>a.params?.salesRunId).expected;
add('old-version-fixed','sku-sales-summary',{...window,customerCode:owner,sku:shared,salesRunId:notes.oldSalesRunId},data=>
scopedRows(data,'SELFOP-TEST-A')&&data.records.length===1&&fieldsMatch(data.records[0],old),
{version:{...notes.currentVersions.A,salesRunId:notes.oldSalesRunId}});
const inventory=assertion('inventory-coverage').expected;
add('inventory-zero-unknown-and-age','inventory-coverage',{},data=>{
if(!scopedRows(data,'SELFOP-TEST-A')||!quantity(data.total,inventory.total)||data.records.length!==inventory.total)return false;
const row=sku=>data.records.find(r=>r.customerCode===owner&&r.sku===sku);
return Object.entries(inventory.sharedByOwner).every(([code,expected])=>fieldsMatch(data.records.find(r=>r.customerCode===code&&r.sku===shared),expected))&&
fieldsMatch(row(inventory.zero.sku),{physicalQuantity:0,availableQuantity:0})&&
fieldsMatch(row(inventory.unknown.sku),{physicalQuantity:null,availableQuantity:null,inventoryCoverageDays:null})&&
row(inventory.unknown.sku)?.quantityStatuses?.physicalQuantity==='UNKNOWN'&&row(inventory.unknown.sku)?.quantityStatuses?.availableQuantity==='UNKNOWN'&&
fieldsMatch(row(inventory.new.sku),{observationEligible:false,inventoryCoverageDays:null});
});
for(const route of ['hot-skus','slow-skus']){
const expected=assertion(route).expected;
add(route+'-eligibility-order',route,route==='hot-skus'?{windowDays:30}:{},data=>scopedRows(data,'SELFOP-TEST-A')&&quantity(data.total,expected.total)&&
data.records.length===expected.total&&data.records.every((r,i)=>r.customerCode===expected.order[i]&&r.sku===expected.sku&&quantity(r.outboundQuantity,expected.outboundQuantities[i])));
}
const other=assertion('sku-sales-summary',a=>a.params?.warehouseCode==='SELFOP-TEST-B').expected;
add('warehouse-b-positive','sku-sales-summary',{rangePreset:'CUSTOM',startDate:until,endDate:until,bucket:'DAY',sku:shared},data=>
scopedRows(data,'SELFOP-TEST-B')&&quantity(data.total,other.total)&&data.records.length===1&&fieldsMatch(data.records[0],{customerCode:other.customerCode,outboundQuantity:other.outboundQuantity}),
{warehouse:'SELFOP-TEST-B',version:notes.currentVersions.B,pinned:false});
add('zero-sales-versus-unknown','sku-daily-sales',{...window,customerCode:owner,sku:'SYNTH-ZERO'},data=>data.points?.length===2&&
data.points.every((p,i)=>p.businessDate===fixtureDate(d,i-2)&&p.dataStatus==='COMPLETE'&&quantity(p.outboundQuantity,0)&&quantity(p.observedQuantity,0)));
add('pre-existence-is-unknown','sku-daily-sales',{...window,customerCode:owner,sku:'SYNTH-NEW'},data=>data.points?.length===2&&
data.points[0].businessDate===from&&data.points[0].dataStatus==='UNKNOWN'&&nullish(data.points[0].outboundQuantity)&&nullish(data.points[0].observedQuantity)&&
data.points[1].businessDate===until&&data.points[1].dataStatus==='COMPLETE'&&quantity(data.points[1].outboundQuantity,0));
const overview=assertion('sync-overview').expected;
for(const [letter,warehouse] of [['A','SELFOP-TEST-A'],['B','SELFOP-TEST-B']]){
const expected=overview[letter];
const present=new Set((expected.sampleDates??[]).map(date=>fixtureDate(d,Number(date.slice(1)))));
const missing=Array.from({length:30},(_,i)=>fixtureDate(d,i-30)).filter(date=>!present.has(date));
add('daily-sample-gaps-'+letter,'sync-overview',{},data=>data.collectionMode==='OFF'&&data.warehouses?.some(w=>{
const samples=w.inventoryDailySamples;return w.warehouseCode===warehouse&&w.dataFamily==='INVENTORY'&&!w.executionEnabled&&!w.publicationEnabled&&samples&&
samples.windowFrom===fixtureDate(d,-30)&&samples.windowUntil===d&&samples.expectedDays===30&&quantity(samples.sampleCount,expected.sampleCount)&&
(expected.accumulationStartedOn===null?nullish(samples.accumulationStartedOn):samples.accumulationStartedOn===fixtureDate(d,Number(expected.accumulationStartedOn.slice(1))))&&
JSON.stringify(samples.missingDates)===JSON.stringify(missing);
}),{warehouse,pinned:false});
}
return cases;
}
export async function runAcceptance(options,{env=process.env,fetchImpl=fetch,save=()=>{},syntheticNotes}={}){
if(!options.execute)return plan();
demand(options.environment==='TEST','EXPLICIT_TEST_REQUIRED');
const base=validateEnvironment(env),sessions=[];
const notes=syntheticNotes??(options.syntheticFixture?JSON.parse(readFileSync(resolve(options.syntheticFixture),'utf8')):null);
if(notes)validateSyntheticNotes(notes,options);
const result={environment:'TEST',mode:'EXECUTE',evidenceType:notes?'SYNTHETIC_QUERY_FIXTURE_NOT_PROVIDER_EVIDENCE':'HTTP_ACCESS_AND_QUERY_CHECKS',
synthetic:!!notes,startedAt:new Date().toISOString(),cases:[],sampleRequirements:SAMPLE_REQUIREMENTS,
uncovered:['Actual context expiration after 30 elapsed minutes is deferred; not exercised by backdating queryAsOf.',
'Upstream Lingxing coverage, collection and publication transactions, real concurrency and performance are not verified.',
'Impersonated platform identity is not prepared or exercised.']};
const checkpoint=()=>save(result);
const record=(id,status,response,reason)=>{result.cases.push({id,status,...(response?evidence(response):{}),...(reason?{reason}: {})});checkpoint();};
const call=async(method,route,session,body)=>{
demand(method==='GET'||(method==='POST'&&['/auth/login','/auth/logout',ROOT+'/sync-tasks'].includes(route)),'UNAUTHORIZED_ROUTE');
let response;try{response=await fetchImpl(base+route,{method,redirect:'error',signal:AbortSignal.timeout(20000),
headers:{Accept:'application/json',...(session?{Authorization:`Bearer ${session.accessToken}`} :{}),
...(body===undefined?{}:{'Content-Type':'application/json'})},...(body===undefined?{}:{body:JSON.stringify(body)})});}
catch{throw new Error('HTTP_TRANSPORT_FAILED');}
let payload;try{payload=await response.json();}catch{throw new Error('HTTP_JSON_REQUIRED');}
return {http:response.status,body:payload,requestId:response.headers?.get('x-request-id'),traceId:response.headers?.get('x-trace-id')};
};
const login=async(label,prefix,check)=>{
if(!env[prefix+'_USERNAME']||!env[prefix+'_PASSWORD']){record(label+'.login','BLOCKED',null,'CREDENTIALS_MISSING');return null;}
const r=await call('POST','/auth/login',null,{loginId:env[prefix+'_USERNAME'],password:env[prefix+'_PASSWORD']});
if(!success(r)||!r.body.data?.accessToken){record(label+'.login','FAIL',r,'LOGIN_FAILED');return null;}
const session=r.body.data;sessions.push(session);
if(!check(new Set(session.authorities??[]))){record(label+'.login','BLOCKED',r,'PERMISSION_FIXTURE_MISMATCH');return null;}
record(label+'.login','PASS',r);return session;
};
const scoped={sourceAccount:options.account,warehouseCode:options.warehouse,current:1,size:20};
const get=(route,session,params=scoped)=>call('GET',ROOT+'/'+route+'?'+query(params),session);
const check=async(id,route,session,params,predicate)=>{
const r=await get(route,session,params);const passed=predicate(r);record(id,passed?'PASS':'FAIL',r);return passed?r.body.data:null;
};
try{
for(const route of [...READ_ROUTES,'sync-tasks/SELFOP-NOT-EXIST'])
await check('anonymous.'+route,route,null,scoped,denied);
const admin=await login('platform','CHONGCHONG_ADMIN',a=>a.has(READ)&&a.has(SYNC));
const readonly=await login('readonly','SELFOP_READONLY',a=>a.has(READ)&&!a.has(SYNC));
const forbidden=await login('denied','SELFOP_DENIED',a=>!a.has(READ)&&!a.has(SYNC));
const merchant=await login('merchant','CHONGCHONG_MERCHANT',()=>true);
for(const [label,session] of [['denied',forbidden],['merchant',merchant]])if(session){
for(const route of [...READ_ROUTES,'sync-tasks/SELFOP-NOT-EXIST'])await check(label+'.'+route,route,session,scoped,denied);
}
if(!admin){record('business.reads','BLOCKED',null,'AUTHORIZED_PLATFORM_REQUIRED');return result;}
const overview=await check('off.overview','sync-overview',admin,scoped,r=>success(r)&&r.body.data?.collectionMode==='OFF'&&
r.body.data.warehouses?.length>0&&r.body.data.warehouses.every(w=>w.warehouseCode===options.warehouse&&!w.executionEnabled&&!w.publicationEnabled));
if(!overview){record('off.commands','BLOCKED',null,'OFF_PREFLIGHT_REQUIRED');return result;}
if(readonly)await check('readonly.query','sync-overview',readonly,scoped,r=>success(r)&&r.body.data.collectionMode==='OFF');
const sales=await check('query.sales','sku-sales-summary',admin,{...scoped,rangePreset:'LAST_3_DAYS',bucket:'DAY'},r=>
success(r)&&Array.isArray(r.body.data?.records)&&r.body.data.records.every(row=>row.sourceAccount===options.account&&row.warehouseCode===options.warehouse));
const context=sales?pin(sales.context):null;
const fixed=context?{...scoped,...context}:scoped;
for(const route of ['hot-skus','slow-skus','inventory-coverage','owners'])await check('query.'+route,route,admin,fixed,r=>
success(r)&&Array.isArray(r.body.data?.records)&&(!context||sameContext(sales.context,r.body.data.context))&&
r.body.data.records.every(row=>row.warehouseCode===undefined||row.warehouseCode===options.warehouse));
for(const route of ['sync-tasks','sync-runs']){
const data=await check('query.'+route,route,admin,scoped,r=>success(r)&&Array.isArray(r.body.data?.records)&&
(route!=='sync-tasks'||r.body.data.records.every(t=>t.warehouseCode===options.warehouse&&t.taskRole==='WAREHOUSE_SYNC')));
if(route==='sync-runs'){
if(data?.records.length){
for(const taskNo of new Set(data.records.map(r=>r.taskNo))){
if(!taskNo){record('scope.run-warehouse','FAIL',null,'ATTEMPT_TASK_NUMBER_REQUIRED');continue;}
await check('scope.run-warehouse','sync-tasks/'+encodeURIComponent(taskNo),admin,scoped,r=>success(r)&&
r.body.data.task?.taskNo===taskNo&&r.body.data.task.warehouseCode===options.warehouse);
}
}else record('query.attempt-sample','BLOCKED',null,'EXISTING_WAREHOUSE_ATTEMPT_REQUIRED');
}
}
await check('scope.foreign-account','sync-overview',admin,{...scoped,sourceAccount:'SELFOP-UNCONFIGURED-'+randomUUID()},businessRejected);
await check('scope.foreign-warehouse','sku-sales-summary',admin,{...scoped,warehouseCode:'SELFOP-UNCONFIGURED-'+randomUUID(),rangePreset:'TODAY'},businessRejected);
if(context){
await check('context.second-page','sku-sales-summary',admin,{...fixed,current:2,rangePreset:'LAST_3_DAYS',bucket:'DAY'},r=>success(r)&&sameContext(sales.context,r.body.data.context));
await check('context.future-clock','sku-sales-summary',admin,{...fixed,queryAsOf:new Date(Date.now()+86400000).toISOString(),rangePreset:'TODAY'},businessRejected);
if(options.otherWarehouse&&(Number(context.salesRunId)>0||Number(context.inventoryRunId)>0)){
const configured=await check('scope.second-warehouse-configured','sync-overview',admin,{...scoped,warehouseCode:options.otherWarehouse},r=>success(r)&&
r.body.data.warehouses?.some(w=>w.warehouseCode===options.otherWarehouse));
if(configured)await check('context.cross-warehouse','sku-sales-summary',admin,{...fixed,warehouseCode:options.otherWarehouse,rangePreset:'TODAY'},businessRejected);
}
else record('context.cross-warehouse','BLOCKED',null,'SECOND_CONFIGURED_WAREHOUSE_REQUIRED');
}
const sample=sales?.records?.[0];
if(sample){
await check('scope.exact-owner-sku','sku-sales-summary',admin,{...fixed,customerCode:sample.customerCode,sku:sample.sku,rangePreset:'LAST_3_DAYS'},r=>success(r)&&
r.body.data.records.length>0&&r.body.data.records.every(row=>row.customerCode===sample.customerCode&&row.sku===sample.sku));
await check('query.daily','sku-daily-sales',admin,{...fixed,customerCode:sample.customerCode,sku:sample.sku,rangePreset:'LAST_3_DAYS',bucket:'DAY'},r=>success(r)&&
r.body.data.summary?.customerCode===sample.customerCode&&r.body.data.summary?.sku===sample.sku&&r.body.data.points?.length===3&&
r.body.data.points.every((p,i,arr)=>['COMPLETE','PARTIAL','UNKNOWN'].includes(p.dataStatus)&&(!i||p.businessDate>arr[i-1].businessDate)&&
(p.dataStatus!=='UNKNOWN'||nullish(p.outboundQuantity)&&nullish(p.observedQuantity))));
const {current:unusedCurrent,size:unusedSize,...overviewScope}=fixed;
await check('query.owner-overview','owner-overview',admin,{...overviewScope,customerCode:sample.customerCode},r=>success(r)&&r.body.data.customerCode===sample.customerCode);
const peer=sales.records.find(row=>row.sku===sample.sku&&row.customerCode!==sample.customerCode);
if(peer)await check('scope.same-sku-other-owner','sku-sales-summary',admin,{...fixed,customerCode:peer.customerCode,sku:peer.sku,rangePreset:'LAST_3_DAYS'},r=>success(r)&&
r.body.data.records.length===1&&r.body.data.records[0].customerCode===peer.customerCode&&r.body.data.records[0].sku===peer.sku&&
r.body.data.records[0].observedQuantity===peer.observedQuantity);
else record('scope.same-sku-other-owner','BLOCKED',null,'SECOND_OWNER_SAME_SKU_REQUIRED');
}else record('query.sample-dependent','BLOCKED',null,'PUBLISHED_SALES_SAMPLE_REQUIRED');
if(notes&&sales){
demand(sales.context.anchorDate,'FIXTURE_ANCHOR_REQUIRED');
for(const scenario of syntheticCases(notes,sales.context))await check(scenario.id,scenario.route,admin,scenario.params,r=>success(r)&&scenario.verify(r.body.data));
}else if(notes)record('synthetic.business','BLOCKED',null,'FIRST_SCREEN_CONTEXT_REQUIRED');
else result.uncovered.push('Reviewed synthetic fixture numerical, old-version and daily representative assertions were not requested.');
const taskPage=await get('sync-tasks',admin,scoped);const task=success(taskPage)?taskPage.body.data.records?.[0]:null;
if(task?.taskNo){await check('query.task-detail','sync-tasks/'+encodeURIComponent(task.taskNo),admin,scoped,r=>success(r)&&r.body.data.task?.taskNo===task.taskNo);
if(options.otherWarehouse)await check('scope.cross-warehouse-task','sync-tasks/'+encodeURIComponent(task.taskNo),admin,{...scoped,warehouseCode:options.otherWarehouse},businessRejected);
}else record('query.task-detail','BLOCKED',null,'EXISTING_WAREHOUSE_TASK_REQUIRED');
// Probe is opt-in and OFF is checked again, immediately before any command request.
if(options.probeOff){
const before=await get('sync-overview',admin,scoped);
demand(success(before)&&before.body.data?.collectionMode==='OFF','OFF_CHANGED_STOPPED');
const command={requestKey:'off-acceptance-'+randomUUID(),warehouseCode:options.warehouse,action:'INVENTORY'};
for(const [label,session] of [['anonymous',null],['readonly',readonly],['denied',forbidden],['merchant',merchant]]){
if(label!=='anonymous'&&!session)continue;
const r=await call('POST',ROOT+'/sync-tasks',session,command);record('sync.permission.'+label,denied(r)?'PASS':'FAIL',r);
demand(!success(r),'UNEXPECTED_ACCEPTANCE_STOPPED');
}
const immediatelyBefore=await get('sync-overview',admin,scoped);
demand(success(immediatelyBefore)&&immediatelyBefore.body.data.collectionMode==='OFF','OFF_CHANGED_STOPPED');
const tasksBefore=await get('sync-tasks',admin,scoped);demand(success(tasksBefore),'TASK_BASELINE_REQUIRED');
const r=await call('POST',ROOT+'/sync-tasks',admin,command);
record('sync.off-controlled-rejection',offRejected(r)?'PASS':'FAIL',r);
demand(!success(r),'UNEXPECTED_ACCEPTANCE_STOPPED');
const tasksAfter=await get('sync-tasks',admin,scoped);
record('sync.off-no-task-created',success(tasksAfter)&&tasksBefore.body.data.total===tasksAfter.body.data.total?'PASS':'FAIL',tasksAfter);
}else record('sync.off-controlled-rejection','BLOCKED',null,'OPT_IN_PROBE_REQUIRED');
}catch(error){record('run.blocker','FAIL',null,safeCode(error.message)??'UNEXPECTED_ERROR');}
finally{
for(const session of sessions)if(session.refreshToken){try{const r=await call('POST','/auth/logout',session,{refreshToken:session.refreshToken});record('session.logout',success(r)?'PASS':'FAIL',r);}catch{record('session.logout','FAIL',null,'LOGOUT_FAILED');}}
result.finishedAt=new Date().toISOString();result.outcome=result.cases.some(c=>c.status==='FAIL')?'FAIL':result.cases.some(c=>c.status==='BLOCKED')?'BLOCKED':'PASS';checkpoint();
}
return result;
}
if(process.argv[1]&&import.meta.url===pathToFileURL(resolve(process.argv[1])).href){
try{
const options=parseArguments(process.argv.slice(2));
const save=result=>{if(options.report){mkdirSync(dirname(resolve(options.report)),{recursive:true});writeFileSync(resolve(options.report),JSON.stringify(result,null,2));}};
const report=await runAcceptance(options,{save});
if(!options.execute)console.log(JSON.stringify(report,null,2));
else {console.log(JSON.stringify({outcome:report.outcome,cases:report.cases.length}));process.exitCode=report.outcome==='PASS'?0:report.outcome==='BLOCKED'?2:1;}
}catch{console.error('SELFOP_ACCEPTANCE_CONFIGURATION_FAILED');process.exitCode=1;}
}
@@ -0,0 +1,210 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {parseArguments,validateEnvironment,runAcceptance,businessRejected,offRejected,evidence,pin,fixtureDate,validateSyntheticNotes,syntheticCases} from './selfoperated-test-env-acceptance.mjs';
const env={CHONGCHONG_ENV:'TEST',CHONGCHONG_ADMIN_API_BASE_URL:'https://test.example.invalid/api',
CHONGCHONG_ADMIN_USERNAME:'admin-secret',CHONGCHONG_ADMIN_PASSWORD:'password-secret',
CHONGCHONG_MERCHANT_USERNAME:'merchant-secret',CHONGCHONG_MERCHANT_PASSWORD:'merchant-password-secret',
SELFOP_READONLY_USERNAME:'readonly-secret',SELFOP_READONLY_PASSWORD:'readonly-password-secret',
SELFOP_DENIED_USERNAME:'denied-secret',SELFOP_DENIED_PASSWORD:'denied-password-secret'};
const options={execute:true,environment:'TEST',probeOff:true,account:'fixture-account',warehouse:'W1',otherWarehouse:'W2',report:'unused.json'};
const READ='platform:self-operated-analytics:list',SYNC='platform:self-operated-analytics:sync';
function fixture({mode='OFF',offCode='B0001',offMessage='采集功能未启用,当前环境不允许执行采集或重试',acceptCommand=false,changeMode=false}={}){
const requests=[];
const context={salesRunId:'11',inventoryRunId:'22',salesScopeHash:'sales-hash',inventoryScopeHash:'stock-hash',queryAsOf:new Date().toISOString()};
const sales=[{sourceAccount:options.account,warehouseCode:'W1',customerCode:'owner-secret-A',sku:'sku-secret',observedQuantity:4},
{sourceAccount:options.account,warehouseCode:'W1',customerCode:'owner-secret-B',sku:'sku-secret',observedQuantity:8}];
const response=(code='200',data=null,http=200,message='do-not-report-sensitive-message')=>({status:http,headers:new Headers({'x-request-id':'request-1','x-trace-id':'trace-1'}),
json:async()=>({code,data,requestId:'request-1',traceId:'trace-1',message})});
const fetchImpl=async(url,init)=>{
const u=new URL(url),path=u.pathname.substring(4),params=u.searchParams,body=init.body?JSON.parse(init.body):null;
requests.push({url,method:init.method,body,headers:init.headers});
if(path==='/auth/login'){
const label=body.loginId.split('-')[0];const authorities=label==='admin'?[READ,SYNC]:label==='readonly'?[READ]:[];
return response('200',{accessToken:'token-secret-'+label,refreshToken:'refresh-secret-'+label,authorities});
}
if(path==='/auth/logout')return response();
const bearer=init.headers.Authorization??'';
if(!bearer)return response('A0401',null,401);
if(bearer.endsWith('denied')||bearer.endsWith('merchant'))return response('A0403');
if(init.method==='POST'){
if(bearer.endsWith('readonly'))return response('A0403');
return response(acceptCommand?'200':offCode,acceptCommand?{taskNo:'unexpected'}:null,200,offMessage);
}
const route=path.split('/').at(-1),warehouse=params.get('warehouseCode');
if(params.get('sourceAccount')?.startsWith('SELFOP-UNCONFIGURED')||warehouse?.startsWith('SELFOP-UNCONFIGURED'))return response('B0001');
if(route==='sync-overview'){
const count=requests.filter(r=>new URL(r.url).pathname.endsWith('/sync-overview')).length;
return response('200',{collectionMode:changeMode&&count>6?'BUSINESS':mode,warehouses:[{warehouseCode:warehouse,executionEnabled:false,publicationEnabled:false}]});
}
if(params.has('queryAsOf')&&(params.get('queryAsOf')!==context.queryAsOf||warehouse==='W2'))return response('B0001');
if(route==='sku-sales-summary')return response('200',{context,records:params.get('current')==='2'?[]:sales.filter(r=>
!params.has('customerCode')||r.customerCode===params.get('customerCode')),total:2});
if(route==='sku-daily-sales')return response('200',{context,summary:{customerCode:params.get('customerCode'),sku:params.get('sku')},points:[
{businessDate:'2026-10-10',dataStatus:'COMPLETE',outboundQuantity:4,observedQuantity:4},
{businessDate:'2026-10-11',dataStatus:'UNKNOWN',outboundQuantity:null,observedQuantity:null},
{businessDate:'2026-10-12',dataStatus:'PARTIAL',outboundQuantity:null,observedQuantity:1}]});
if(route==='owner-overview')return params.has('current')||params.has('size')?response('B0001'):response('200',{context,customerCode:params.get('customerCode')});
if(path.includes('/sync-tasks/'))return warehouse==='W1'?response('200',{task:{taskNo:'TASK-FIXTURE',warehouseCode:'W1'}}):response('B0001');
if(route==='sync-tasks')return response('200',{records:[{taskNo:'TASK-FIXTURE',warehouseCode:warehouse,taskRole:'WAREHOUSE_SYNC'}],total:1});
if(route==='sync-runs')return response('200',{records:[{taskNo:'TASK-FIXTURE'}],total:1});
return response('200',{context,records:[],total:0});
};
return {requests,fetchImpl};
}
test('explicit TEST execution and independent warehouse are required',()=>{
assert.throws(()=>parseArguments(['--environment','prod']),/TEST/);
assert.throws(()=>parseArguments(['--environment','TEST','--execute']),/SCOPE/);
assert.throws(()=>parseArguments(['--environment','TEST','--warehouse','W1','--other-warehouse','W1']),/DIFFER/);
assert.throws(()=>parseArguments(['--environment','TEST','--execute','--execute']),/DUPLICATE/);
assert.equal(parseArguments(['--environment','TEST']).execute,false);
assert.throws(()=>validateEnvironment({...env,CHONGCHONG_ENV:'DEV'}),/TEST/);
assert.throws(()=>validateEnvironment({...env,CHONGCHONG_ADMIN_API_BASE_URL:'https://user:secret@test.example.invalid/api'}),/GATEWAY/);
assert.throws(()=>validateEnvironment({...env,CHONGCHONG_ADMIN_API_BASE_URL:'https://test.example.invalid/api?token=secret'}),/GATEWAY/);
});
test('plan makes no network requests and never reads env files',async()=>{
const p=await runAcceptance({execute:false},{env:{},fetchImpl:()=>{throw Error('network');}});
assert.equal(p.mode,'PLAN');assert.equal(p.fixtureWrites,false);assert.equal(p.thirdPartyCalls,false);
});
test('real protocol coverage passes without printing or saving sensitive payloads',async()=>{
const f=fixture(),snapshots=[];const result=await runAcceptance(options,{env,fetchImpl:f.fetchImpl,save:r=>snapshots.push(JSON.stringify(r))});
assert.equal(result.outcome,'PASS',JSON.stringify(result));
for(const id of ['scope.same-sku-other-owner','context.cross-warehouse','sync.off-controlled-rejection','sync.off-no-task-created'])
assert.equal(result.cases.find(c=>c.id===id)?.status,'PASS',id);
const serialized=snapshots.join('');
for(const forbidden of ['password-secret','token-secret','refresh-secret','owner-secret','sku-secret','test.example.invalid','sensitive-message'])assert.ok(!serialized.includes(forbidden),forbidden);
const commands=f.requests.filter(r=>r.method==='POST'&&!new URL(r.url).pathname.startsWith('/api/auth/'));
assert.equal(commands.length,5);assert.ok(commands.every(r=>new URL(r.url).pathname.endsWith('/sync-tasks')&&r.body.action==='INVENTORY'));
assert.equal(f.requests.filter(r=>new URL(r.url).pathname.endsWith('/auth/logout')).length,4);
});
test('OFF preflight blocks every command when mode is not OFF',async()=>{
const f=fixture({mode:'VALIDATION'});const result=await runAcceptance(options,{env,fetchImpl:f.fetchImpl});
assert.equal(result.outcome,'FAIL');assert.ok(!f.requests.some(r=>r.method==='POST'&&new URL(r.url).pathname.endsWith('/sync-tasks')));
});
test('without opt-in no sync POST occurs; missing permission identity stays blocked',async()=>{
const f=fixture();const result=await runAcceptance({...options,probeOff:false},{env:{...env,SELFOP_READONLY_PASSWORD:''},fetchImpl:f.fetchImpl});
assert.equal(result.outcome,'BLOCKED');assert.ok(!f.requests.some(r=>r.method==='POST'&&new URL(r.url).pathname.endsWith('/sync-tasks')));
});
test('a changed OFF preflight stops before all sync POST requests',async()=>{
const f=fixture({changeMode:true});const result=await runAcceptance(options,{env,fetchImpl:f.fetchImpl});
assert.equal(result.outcome,'FAIL');assert.ok(!f.requests.some(r=>r.method==='POST'&&new URL(r.url).pathname.endsWith('/sync-tasks')));
assert.equal(result.cases.find(c=>c.id==='run.blocker').reason,'OFF_CHANGED_STOPPED');
});
test('internal errors never pass as controlled OFF rejection; unexpected acceptance aborts',async()=>{
assert.equal(businessRejected({http:200,body:{code:'S0500'}}),false);
assert.equal(businessRejected({http:503,body:{code:'B0001'}}),false);
assert.equal(offRejected({http:200,body:{code:'B0001',message:'SELFOP_WAREHOUSE_UNCONFIGURED'}}),false);
for(const config of [{offCode:'S0500'},{offMessage:'SELFOP_WAREHOUSE_UNCONFIGURED'},{acceptCommand:true}]){
const f=fixture(config);const result=await runAcceptance(options,{env,fetchImpl:f.fetchImpl});
assert.equal(result.outcome,'FAIL');assert.equal(result.cases.find(c=>c.id==='sync.off-controlled-rejection').status,'FAIL');
}
});
test('evidence whitelist retains diagnosis and drops arbitrary body content',()=>{
assert.deepEqual(evidence({http:200,body:{code:'B0001',requestId:'request-2',traceId:'trace-2',password:'secret',data:{accessToken:'secret'}}}),
{http:200,code:'B0001',requestId:'request-2',traceId:'trace-2'});
assert.equal(evidence({http:500,body:{code:'secret with spaces'}}).code,null);
assert.throws(()=>pin({salesRunId:0,inventoryRunId:0}),/CONTEXT/);
assert.equal(pin({salesRunId:0,inventoryRunId:0,queryAsOf:'2026-10-12T00:00:00Z'}).salesRunId,0);
});
function syntheticFixture(){
const notes={synthetic:true,fixtureId:'SYNTHETIC-SELFOP-20261012',sourceAccount:'lingxing-primary',hashes:{A:'hash-A',B:'hash-B'},
currentVersions:{A:{salesRunId:'202',inventoryRunId:'203'},B:{salesRunId:'206',inventoryRunId:'207'}},oldSalesRunId:'201',assertions:[
{endpoint:'owners',expected:{total:2,customerCodes:['SYNTH-OWNER-A','SYNTH-OWNER-B']}},
{endpoint:'sku-sales-summary',expected:{total:2,records:{'SYNTH-OWNER-A':{outboundQuantity:30,observedQuantity:30,dataStatus:'COMPLETE',completeDayCount:2,partialDayCount:0,unknownDayCount:0},
'SYNTH-OWNER-B':{outboundQuantity:10,observedQuantity:10,dataStatus:'COMPLETE'}}}},
{endpoint:'sku-daily-sales',expected:{points:[{businessDate:'D-2',outboundQuantity:10,dataStatus:'COMPLETE'},{businessDate:'D-1',outboundQuantity:20,dataStatus:'COMPLETE'}],summaryOutboundQuantity:30}},
{endpoint:'sku-sales-summary',params:{rangePreset:'TODAY'},expected:{'SYNTH-OWNER-A':{outboundQuantity:null,observedQuantity:5,dataStatus:'PARTIAL'},'SYNTH-OWNER-B':{outboundQuantity:null,observedQuantity:2,dataStatus:'PARTIAL'}}},
{endpoint:'sku-sales-summary',params:{salesRunId:'201'},expected:{outboundQuantity:3,observedQuantity:3,dataStatus:'COMPLETE'}},
{endpoint:'inventory-coverage',expected:{total:5,sharedByOwner:{'SYNTH-OWNER-A':{physicalQuantity:60,availableQuantity:60,outboundQuantity30d:30,inventoryCoverageDays:60},
'SYNTH-OWNER-B':{physicalQuantity:20,availableQuantity:20,outboundQuantity30d:10,inventoryCoverageDays:60}},zero:{sku:'SYNTH-ZERO'},unknown:{sku:'SYNTH-UNKNOWN'},new:{sku:'SYNTH-NEW'}}},
{endpoint:'hot-skus',expected:{total:2,order:['SYNTH-OWNER-A','SYNTH-OWNER-B'],sku:'SYNTH-SHARED-SKU',outboundQuantities:[30,10]}},
{endpoint:'slow-skus',expected:{total:2,order:['SYNTH-OWNER-B','SYNTH-OWNER-A'],sku:'SYNTH-SHARED-SKU',outboundQuantities:[10,30]}},
{endpoint:'sku-sales-summary',params:{warehouseCode:'SELFOP-TEST-B'},expected:{total:1,customerCode:'SYNTH-OWNER-A',outboundQuantity:999}},
{endpoint:'sync-overview',expected:{A:{sampleCount:2,sampleDates:['D-3','D-1'],accumulationStartedOn:'D-3'},B:{sampleCount:0,accumulationStartedOn:null}}},
]};
const context={...notes.currentVersions.A,salesScopeHash:'hash-A',inventoryScopeHash:'hash-A',queryAsOf:'2026-10-12T04:00:00Z',anchorDate:'2026-10-12'};
const cases=syntheticCases(notes,context),payloads=new Map();
const row=(customerCode,sku,fields={})=>({sourceAccount:notes.sourceAccount,warehouseCode:'SELFOP-TEST-A',customerCode,sku,...fields});
for(const scenario of cases){
const id=scenario.id.substring(10),data={context:{...context},records:[],total:0};
if(id==='owners'){data.records=['SYNTH-OWNER-A','SYNTH-OWNER-B'].map(customerCode=>({customerCode,customerName:null}));data.total=2;}
if(id==='date-range-and-owner-isolation'){data.records=Object.entries(notes.assertions[1].expected.records).map(([code,fields])=>row(code,'SYNTH-SHARED-SKU',fields));data.total=2;}
if(id==='daily-exact-axis'){data.summary={customerCode:'SYNTH-OWNER-A',sku:'SYNTH-SHARED-SKU',outboundQuantity:30};data.points=notes.assertions[2].expected.points.map((p,i)=>({...p,businessDate:fixtureDate(context.anchorDate,i-2)}));}
if(id==='today-partial'){data.records=Object.entries(notes.assertions[3].expected).map(([code,fields])=>row(code,'SYNTH-SHARED-SKU',fields));data.total=2;}
if(id==='old-version-fixed'){data.context.salesRunId='201';data.records=[row('SYNTH-OWNER-A','SYNTH-SHARED-SKU',notes.assertions[4].expected)];data.total=1;}
if(id==='inventory-zero-unknown-and-age'){
data.records=Object.entries(notes.assertions[5].expected.sharedByOwner).map(([code,fields])=>row(code,'SYNTH-SHARED-SKU',fields));
data.records.push(row('SYNTH-OWNER-A','SYNTH-ZERO',{physicalQuantity:0,availableQuantity:0}),
row('SYNTH-OWNER-A','SYNTH-UNKNOWN',{physicalQuantity:null,availableQuantity:null,inventoryCoverageDays:null,quantityStatuses:{physicalQuantity:'UNKNOWN',availableQuantity:'UNKNOWN'}}),
row('SYNTH-OWNER-A','SYNTH-NEW',{observationEligible:false,inventoryCoverageDays:null}));data.total=5;
}
if(id==='hot-skus-eligibility-order'||id==='slow-skus-eligibility-order'){
const expected=notes.assertions[id.startsWith('hot')?6:7].expected;
data.records=expected.order.map((code,i)=>row(code,expected.sku,{outboundQuantity:expected.outboundQuantities[i]}));data.total=2;
}
if(id==='warehouse-b-positive'){
data.context={...context,...notes.currentVersions.B,salesScopeHash:'hash-B',inventoryScopeHash:'hash-B'};
data.records=[{...row('SYNTH-OWNER-A','SYNTH-SHARED-SKU',{outboundQuantity:999}),warehouseCode:'SELFOP-TEST-B'}];data.total=1;
}
if(id==='zero-sales-versus-unknown')data.points=[-2,-1].map(offset=>({businessDate:fixtureDate(context.anchorDate,offset),dataStatus:'COMPLETE',outboundQuantity:'0.000000',observedQuantity:0}));
if(id==='pre-existence-is-unknown')data.points=[{businessDate:'2026-10-10',dataStatus:'UNKNOWN',outboundQuantity:null,observedQuantity:null},
{businessDate:'2026-10-11',dataStatus:'COMPLETE',outboundQuantity:0}];
if(id.startsWith('daily-sample-gaps-')){
const letter=id.at(-1),expected=notes.assertions[9].expected[letter],present=new Set((expected.sampleDates??[]).map(p=>fixtureDate(context.anchorDate,Number(p.slice(1)))));
data.collectionMode='OFF';data.warehouses=[{warehouseCode:'SELFOP-TEST-'+letter,dataFamily:'INVENTORY',executionEnabled:false,publicationEnabled:false,
inventoryDailySamples:{windowFrom:'2026-09-12',windowUntil:'2026-10-12',expectedDays:30,sampleCount:expected.sampleCount,
accumulationStartedOn:expected.accumulationStartedOn===null?null:'2026-10-09',missingDates:Array.from({length:30},(_,i)=>fixtureDate(context.anchorDate,i-30)).filter(p=>!present.has(p))}}];
}
payloads.set(scenario.id,data);
}
return {notes,context,cases,payloads};
}
test('reviewed synthetic fixture covers all thirteen business checks with bound old/current version pairs',()=>{
const f=syntheticFixture();assert.equal(f.cases.length,13);
validateSyntheticNotes(f.notes,{account:'lingxing-primary',warehouse:'SELFOP-TEST-A',otherWarehouse:'SELFOP-TEST-B'});
assert.throws(()=>validateSyntheticNotes({...f.notes,synthetic:false},options),/SYNTHETIC/);
for(const c of f.cases)assert.equal(c.verify(f.payloads.get(c.id)),true,c.id);
const old=f.cases.find(c=>c.id==='synthetic.old-version-fixed');
assert.equal(old.params.salesRunId,'201');assert.equal(old.params.inventoryRunId,'203');assert.equal(old.params.queryAsOf,f.context.queryAsOf);
assert.ok(f.cases.every(c=>c.route!=='sync-tasks'&&!c.route.includes('publish')));
assert.equal(fixtureDate('2026-03-01',-1),'2026-02-28');
});
test('synthetic checks reject mixed owners/warehouses, silent current-version fallback, zero-filling and ineligible Top rows',()=>{
const f=syntheticFixture();
const reject=(id,mutate)=>{const c=f.cases.find(c=>c.id==='synthetic.'+id),data=structuredClone(f.payloads.get(c.id));mutate(data);assert.equal(c.verify(data),false,id);};
reject('date-range-and-owner-isolation',d=>{d.records[0].observedQuantity+=999;});
reject('date-range-and-owner-isolation',d=>{d.records[1].customerCode='SYNTH-OWNER-A';});
reject('warehouse-b-positive',d=>{d.records[0].warehouseCode='SELFOP-TEST-A';});
reject('old-version-fixed',d=>{d.context.salesRunId='202';});
reject('inventory-zero-unknown-and-age',d=>{d.records[3].availableQuantity=0;});
reject('pre-existence-is-unknown',d=>{d.points[0].observedQuantity=0;});
reject('daily-exact-axis',d=>{d.points[1].businessDate='2026-10-12';});
reject('hot-skus-eligibility-order',d=>{d.records[0].sku='SYNTH-NEW';});
reject('slow-skus-eligibility-order',d=>{d.records.reverse();});
reject('daily-sample-gaps-A',d=>{d.warehouses[0].inventoryDailySamples.missingDates.push('2026-10-11');});
reject('daily-sample-gaps-B',d=>{d.warehouses[0].inventoryDailySamples.sampleCount=1;});
});
test('synthetic report explicitly marks provider evidence and elapsed expiration as unverified without adding a wait blocker',async()=>{
const f=syntheticFixture();const result=await runAcceptance({...options,account:'lingxing-primary',warehouse:'SELFOP-TEST-A',otherWarehouse:'SELFOP-TEST-B'},
{env:{CHONGCHONG_ENV:'TEST',CHONGCHONG_ADMIN_API_BASE_URL:env.CHONGCHONG_ADMIN_API_BASE_URL},syntheticNotes:f.notes,
fetchImpl:async()=>({status:401,headers:new Headers(),json:async()=>({code:'A0401'})})});
assert.equal(result.synthetic,true);assert.equal(result.evidenceType,'SYNTHETIC_QUERY_FIXTURE_NOT_PROVIDER_EVIDENCE');
assert.ok(result.uncovered.some(item=>item.includes('30 elapsed minutes')));
assert.ok(!result.cases.some(c=>c.id==='context.expired'));
assert.equal(parseArguments(['--environment','TEST','--synthetic-fixture','notes.json']).syntheticFixture,'notes.json');
});
test('optional nullable fields survive NON_NULL wire serialization while zero-filling stays rejected',()=>{
const f=syntheticFixture();
const omitNulls=value=>Array.isArray(value)?value.map(omitNulls):value&&typeof value==='object'?
Object.fromEntries(Object.entries(value).filter(([,v])=>v!==null).map(([k,v])=>[k,omitNulls(v)])):value;
for(const c of f.cases)assert.equal(c.verify(omitNulls(f.payloads.get(c.id))),true,c.id);
for(const id of ['synthetic.today-partial','synthetic.pre-existence-is-unknown','synthetic.inventory-zero-unknown-and-age']){
const c=f.cases.find(c=>c.id===id),data=omitNulls(f.payloads.get(id));
if(id.endsWith('today-partial'))data.records[0].outboundQuantity=0;
else if(id.endsWith('pre-existence-is-unknown'))data.points[0].observedQuantity=0;
else data.records[3].availableQuantity=0;
assert.equal(c.verify(data),false,id);
}
});
+2
View File
@@ -1,5 +1,7 @@
# 后端脚本管理与全量 HTTP 验收索引
`../lastleg-service/scripts/selfoperated-test-env-acceptance.mjs`:自营统计 TEST Gateway 验收,默认仅输出计划;显式 `--execute --environment TEST --source-account <账号键> --warehouse <仓库编码> --other-warehouse <第二仓> --report <报告路径>` 才登录并查询。凭据由运行者通过脚本之前的 `node --env-file=<明确TEST文件>` 注入,需平台、只读平台、无权限平台及客户身份。`--probe-off-command` 仅在确认 OFF 后提交预期被拒绝的命令并核对无新增任务;不采集、发布、造数或写 SQL。`--synthetic-fixture <审核后的说明文件>` 验证合成样本,不能证明真实来源覆盖。报告脱敏记录各项结果及请求标识;失败保留报告,重跑使用新报告路径并重新登录,结束退出自身会话,不清理业务样本。离线合同为同目录 `selfoperated-test-env-acceptance.test.mjs`。
`db/return-consolidated-incremental-20261012.sql`:10月10日/12日退货、签收、清点、换标与按件出库增量汇总,显式 `USE baiyue_lastleg`,MySQL 8.0.16+ 已有业务库可重复执行。本轮补齐签收原包装、尺寸重量来源及库存箱原包装三个字段;逐列判断存在性,不回填签收/清点事实,不改库存数量。连接实例及凭据由执行者明确指定,整份执行、遇错停止;保留执行错误,修复后可重跑,末尾输出字段、索引与约束供核对。托盘重量、体积汇总是应用逻辑,不新增资源库字段;不自动执行到测试或生产环境。
`procurement-sample-test-env.mjs` 是采购样品正常全链路 TEST 专项入口,默认或 `--plan` 零网络。真实执行使用 `node --env-file="<授权目录>/test-acceptance.env" scripts/procurement-sample-test-env.mjs --execute --environment TEST --run-id <唯一短批次> --warehouse-id <已核对有效海外收货仓>`。只允许当前 TEST 工作区及指定配置;由正式 API 创建独立客户并完成资质,人工充值10元,上传凭证/面单,调用内部 `procurement-sample-fulfillment.mjs` 完成两箱正常履约。充值、文件和采购请求前落盘,结果未知停写;报告独占 `reports/procurement-sample-<批次>/report.json`,不覆盖旧报告,不自动重跑,不执行 SQL/部署/删除。结束退出自身平台与客户会话,保留样本。通过只表示该正常路径,不替代异常、退款、权限、真实页面和请求量验收。离线入口合同为 `procurement-sample-test-env.test.mjs`;内部模块安全合同见模块索引。