Files
baiyue-firstleg/lastleg-service/scripts/selfoperated-test-env-acceptance.mjs

309 lines
27 KiB
JavaScript

/**
* 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;}
}