Files
baiyue-firstleg/scripts/inbound-plan-resource-fault-executor.test.mjs
T

94 lines
9.0 KiB
JavaScript

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);
});