import test from 'node:test'; import assert from 'node:assert/strict'; import {spawnSync} from 'node:child_process'; import {mkdtempSync,writeFileSync} from 'node:fs'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; import {fileURLToPath} from 'node:url'; import {scopeFor,batchPrefixHash,renderInboundResourceAction,createInboundResourceRuntime,actions} from './inbound-plan-resource-fault-executor.mjs'; const plan={environment:'TEST',marker:'F27-B-TEST',containerBookingId:'2106714159440728066',warehouseId:'193000000000200003',runtimeChangesExecuted:false}; plan.batchPrefixHash=batchPrefixHash(plan.containerBookingId); const scope=scopeFor(plan); const env={CHONGCHONG_ENV:'test',JENKINS_URL:'https://offline.invalid/',JENKINS_USER:'offline',JENKINS_API_TOKEN:'secret-do-not-render',FBA_CORE_JENKINS_AGENT:'test-agent'}; const routing={configurationReviewed:true,nacos:{baseUrl:'http://offline.invalid:8848',loginPath:'/nacos/v3/auth/user/login',configPath:'/nacos/v3/admin/cs/config'},upstreams:{WAREHOUSE:'http://offline.invalid:9305'},sourceSha256:'a'.repeat(64)}; test('coordinator hashes the JSON string including quotes, never raw booking bytes',()=>{ assert.equal(plan.batchPrefixHash,'4c9034968e117813e549a12f5e63f1c8366a7f2dbb6722a3ff4967b983a5f9e7'); }); test('scope rejects non-TEST, executed, multiple-container, unknown and bad hash plans',()=>{ for(const change of [p=>p.environment='PROD',p=>p.runtimeChangesExecuted=true,p=>p.containerBookingId=['1','2'],p=>p.extra='secret',p=>p.batchPrefixHash='b'.repeat(64)]){ const value=structuredClone(plan);change(value);assert.throws(()=>scopeFor(value)); } }); const arrays=text=>Array.from(text.matchAll(/\[('[A-Za-z0-9+/=]+'(?:,'[A-Za-z0-9+/=]+')*)\]\.join\(''\)/g),m=>Array.from(m[1].matchAll(/'([^']+)'/g),p=>p[1])); test('renderer chunks complete payload and carries both proxy modules and reviewed primitives',()=>{ const rendered=renderInboundResourceAction('prepare',scope,'test-agent',{routing,expectedImageId:'sha256:'+'c'.repeat(64)}); assert.ok(!rendered.includes(env.JENKINS_API_TOKEN)); const groovy=Buffer.from(arrays(rendered)[0].join(''),'base64').toString();const embedded=arrays(groovy); assert.ok(embedded.flat().every(s=>s.length<=4000)); const config=JSON.parse(Buffer.from(embedded[1].join(''),'base64').toString()); assert.deepEqual(config.scope,scope);assert.equal(config.action,'prepare'); for(const key of ['proxySource','httpProxySource','commonSource','baseSource'])assert.ok(config[key].length>100); const runtime=Buffer.from(embedded[0].join(''),'base64').toString();assert.ok(runtime.includes('firstleg-test-lastleg')); assert.match(groovy,/new ProcessBuilder\(\['python3','-c',new String\(.+?,'UTF-8'\)\]\)/s); assert.match(groovy,/p\.outputStream\.withCloseable\{s->s\.write\(java\.util\.Base64\.decoder\.decode\(/); assert.ok(Buffer.byteLength(runtime)<65536); assert.ok(embedded[1].join('').length>131072,'prepare reproduces the old over-limit configuration argv'); assert.ok(runtime.includes('main(read_stdin_config())')); assert.ok(!runtime.includes('base64.b64decode(sys.argv[1])')); }); test('complete prepare configuration travels over stdin to actual Python with bounded argv and no config echo',()=>{ const rendered=renderInboundResourceAction('prepare',scope,'test-agent',{routing,expectedImageId:'sha256:'+'c'.repeat(64)}); const groovy=Buffer.from(arrays(rendered)[0].join(''),'base64').toString();const embedded=arrays(groovy); const runtime=Buffer.from(embedded[0].join(''),'base64').toString(); const config=JSON.parse(Buffer.from(embedded[1].join(''),'base64').toString()); // Reject scope before locks/Docker/network. JSON still must consume the complete four-source body. config.scope.environment='PROD';const input=JSON.stringify(config); const argv=['-c',runtime];assert.equal(argv.length,2);assert.ok(argv.every(a=>Buffer.byteLength(a)<65536)); assert.ok(Buffer.byteLength(input)>100000);for(const key of ['baseSource','commonSource','proxySource','httpProxySource'])assert.ok(config[key].length>100); const result=spawnSync(process.env.PYTHON||'python',argv,{input,encoding:'utf8',env:{...process.env,PYTHONDONTWRITEBYTECODE:'1'}}); assert.equal(result.status,0,result.stderr||result.error?.message);assert.equal(result.stderr,''); const rows=result.stdout.trim().split(/\r?\n/);assert.equal(rows.length,1);assert.ok(rows[0].startsWith('INBOUND_RESOURCE_JSON=')); const value=JSON.parse(rows[0].slice('INBOUND_RESOURCE_JSON='.length));assert.equal(value.error,'EXPLICIT_TEST_SCOPE_REQUIRED'); assert.equal(value.diagnostic.stage,'READ_STDIN_CONFIG');assert.ok(!result.stdout.includes(config.baseSource.slice(0,80)));assert.ok(!result.stdout.includes('upstreams')); }); test('origin/schema guards reject credential embedding, wrong imports and unbounded fault windows',()=>{ for(const change of [r=>r.password='secret',r=>r.upstreams.IAM='http://offline.invalid',r=>r.nacos.baseUrl='http://user:secret@offline.invalid',r=>r.nacos.configPath='/fallback']){ const r=structuredClone(routing);change(r);assert.throws(()=>renderInboundResourceAction('prepare',scope,'test-agent',{routing:r,expectedImageId:'sha256:'+'c'.repeat(64)})); } for(const ttl of [0,61,1.5])assert.throws(()=>renderInboundResourceAction('mode',scope,'agent',{mode:'CONFIRM_ONCE',ttl})); assert.throws(()=>renderInboundResourceAction('mode',scope,'agent',{mode:'FAIL_ALL',ttl:1})); assert.throws(()=>renderInboundResourceAction('status',{...scope,warehouseId:'0'},'agent')); assert.throws(()=>renderInboundResourceAction('business-convert',scope,'agent')); }); test('transport unknown, duplicate result and malformed result each fail after exactly one call',async()=>{ for(const fetcher of [async()=>{throw Error('lost secret')},async()=>({ok:true,text:async()=> 'INBOUND_RESOURCE_JSON={}\nINBOUND_RESOURCE_JSON={}'}),async()=>({ok:true,text:async()=> 'INBOUND_RESOURCE_JSON={'})]){ let calls=0;const runtime=createInboundResourceRuntime(env,scope,async(...args)=>{calls++;return fetcher(...args)}); await assert.rejects(runtime('activate'),/UNKNOWN|AMBIGUOUS|INVALID_RESPONSE/);assert.equal(calls,1); } assert.throws(()=>createInboundResourceRuntime({...env,CHONGCHONG_ENV:'prod'},scope)); }); test('structured remote process-start/stdin errors remain single results and never echo private payloads',async()=>{ for(const [code,stage] of [['REMOTE_PROCESS_START_FAILED','REMOTE_PROCESS_START'],['RUNTIME_STDIN_OUTCOME_UNKNOWN_USE_STATUS','REMOTE_PROCESS_STDIN']]){ let calls=0;const runtime=createInboundResourceRuntime(env,scope,async()=>{calls++;return{ok:true,text:async()=> 'INBOUND_RESOURCE_JSON='+JSON.stringify({ok:false,error:code,diagnostic:{stage,errorType:'IOException'}})}}); await assert.rejects(runtime('prepare',{routing,expectedImageId:'sha256:'+'c'.repeat(64)}),error=>{assert.equal(error.message,code);assert.deepEqual(error.runtimeDiagnostic,{stage,errorType:'IOException'});return true;});assert.equal(calls,1); } }); test('remote error preserves safe stage/type/status and strips untrusted diagnostic fields',async()=>{ const runtime=createInboundResourceRuntime(env,scope,async()=>({ok:true,text:async()=> 'INBOUND_RESOURCE_JSON='+JSON.stringify({ok:false,error:'NACOS_CONFIG_READ_HTTP_404',diagnostic:{stage:'NACOS_CONFIG_READ',errorType:'HTTPError',httpStatus:404,token:'must-never-escape',body:'secret',url:'http://private.invalid'}})})); await assert.rejects(runtime('preflight',{routing}),error=>{ assert.equal(error.message,'NACOS_CONFIG_READ_HTTP_404');assert.deepEqual(error.runtimeDiagnostic,{stage:'NACOS_CONFIG_READ',errorType:'HTTPError',httpStatus:404});return true; }); }); test('DNS diagnostics preserve only bounded errno and transport class, no private resolver message',async()=>{ const runtime=createInboundResourceRuntime(env,scope,async()=>({ok:true,text:async()=> 'INBOUND_RESOURCE_JSON='+JSON.stringify({ok:false,error:'NACOS_SOURCE_READ_TRANSPORT_FAILED',diagnostic:{stage:'NACOS_SOURCE_READ',errorType:'URLError',transportErrorType:'gaierror',errno:-2,message:'private resolver secret'}})})); await assert.rejects(runtime('preflight',{routing}),error=>{ assert.deepEqual(error.runtimeDiagnostic,{stage:'NACOS_SOURCE_READ',errorType:'URLError',transportErrorType:'gaierror',errno:-2});return true; }); }); test('default plan CLI has zero HTTP and exposes no business conversion action',()=>{ const dir=mkdtempSync(join(tmpdir(),'inbound-fault-plan-'));const file=join(dir,'plan.json');writeFileSync(file,JSON.stringify(plan)); const run=spawnSync(process.execPath,[fileURLToPath(new URL('./inbound-plan-resource-fault-executor.mjs',import.meta.url)),'plan','--plan',file],{encoding:'utf8',env:{...process.env,CHONGCHONG_ENV:'prod',JENKINS_URL:'http://127.0.0.1:1'}}); assert.equal(run.status,0,run.stderr);const output=JSON.parse(run.stdout);assert.equal(output.networkCalls,0);assert.equal(output.businessWritesSupported,false); assert.equal(output.target,'firstleg-test-lastleg');assert.equal(output.property,'baiyue.warehouse.base-url');assert.deepEqual(output.actions,actions); });