Files
SGF/scripts/stock-ledger-http-proof.php
2026-11-04 09:37:20 +02:00

25 lines
7.2 KiB
PHP

<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
if(DB::table('gl_journals')->exists()||DB::table('gl_stock_rates')->exists())throw new LogicException('New HTTP fixture only');$before=$fingerprint();
$socket=stream_socket_server('tcp://127.0.0.1:0');$port=(int)substr(strrchr(stream_socket_get_name($socket,false),':'),1);fclose($socket);
$server=new Symfony\Component\Process\Process([PHP_BINARY,'-S','127.0.0.1:'.$port,__DIR__.'/stock-ledger-http-router.php'],dirname(__DIR__),['STOCK_LEDGER_SETTINGS'=>$s['root'].'/settings.json'],null,90);$server->start();
try{
for($i=0;$i<80;$i++){if($sock=@fsockopen('127.0.0.1',$port,$e,$m,.1)){fclose($sock);break;}usleep(50000);}
$client=new GuzzleHttp\Client(['base_uri'=>'http://127.0.0.1:'.$port,'http_errors'=>false,'timeout'=>15]);$sessions=[];
foreach(['prepare','validate','read']as$role){$jar=new GuzzleHttp\Cookie\CookieJar();$r=$client->get('/qa/stock-enter/'.$role,['cookies'=>$jar]);$ok($r->getStatusCode()===200,'native QA entry '.$role);$sessions[$role]=['jar'=>$jar]+json_decode((string)$r->getBody(),true,32,JSON_THROW_ON_ERROR);}
$call=function($role,$url,$body=null,$key=null,$overrides=[])use($client,&$sessions){$v=$sessions[$role];$headers=array_replace(['Accept'=>'application/json','X-CSRF-TOKEN'=>$v['csrf'],'X-Stock-Context'=>$v['context'],'Idempotency-Key'=>$key??(string)Str::uuid()],$overrides);$options=['cookies'=>$v['jar'],'headers'=>$headers];if($body!==null)$options['json']=$body;$r=$client->request($body===null?'GET':'POST',$url,$options);$text=(string)$r->getBody();return ['status'=>$r->getStatusCode(),'data'=>json_decode($text,true),'body'=>$text,'cache'=>$r->getHeaderLine('Cache-Control')];};
$r=$call('prepare','/stock-valuation');$ok($r['status']===200&&str_contains($r['body'],'id="rate"')&&str_contains($r['body'],'Incompleto'),'open UI presents rate and incomplete amounts');file_put_contents($s['root'].'/http-open.html',$r['body']);
$ok($call('read','/api/stock-valuation?per_page=100')['data']['stock']['pagination']['total']===3,'API server page100 has three actual movements');$ok($call('read','/api/stock-valuation?unknown=1')['status']===422,'unknown read parameter refused');$ok($call('read','/api/stock-valuation',null,null,['X-Stock-Context'=>'stale'])['status']===409,'stale context refused');
$d=['book_id'=>$f['book_id'],'version'=>0,'unit_minor'=>5000,'reason'=>'Explicit selected synthetic50 per UN through HTTP.'];$ok($call('prepare','/api/stock-valuation/rate-prepare',$d,null,['X-CSRF-TOKEN'=>''])['status']===419,'CSRF retained');$ok($call('read','/api/stock-valuation/rate-prepare',$d)['status']===403,'read profile cannot prepare rate');$key=(string)Str::uuid();$r=$call('prepare','/api/stock-valuation/rate-prepare',$d,$key);$ok($r['status']===200,'rate proposal through HTTP');$rate=$r['data'];$ok($call('prepare','/api/stock-valuation/rate-prepare',$d,$key)['data']['rate_id']===$rate['rate_id'],'rate exact retry same id');
$receipt=$call('prepare','/api/stock-valuation/receipts/rate/'.$key);$ok($receipt['status']===200&&$receipt['data']['operation_executed']===false,'rate receipt recovery does not execute');$ok($call('read','/api/stock-valuation/receipts/rate/'.$key)['status']===404,'another actor cannot read rate receipt');
$r=$call('validate','/api/stock-valuation/rate-validate',['rate_id'=>$rate['rate_id'],'version'=>1,'decision'=>'approved','reason'=>'Distinct actual HTTP rate review.']);$ok($r['status']===200,'rate approval through HTTP');
foreach($f['movements']as$m){$r=$call('prepare','/api/stock-valuation/effect-prepare',$body($m));$ok($r['status']===200,'capture actual stock movement '.$m['id']);$p=$r['data'];$preview=$call('validate','/stock-valuation');$ok($preview['status']===200&&str_contains($preview['body'],$p['revision'])&&str_contains($preview['body'],'Fotografia completa'),'review UI shows exact impact revision and dependencies');file_put_contents($s['root'].'/http-review-'.$m['id'].'.html',$preview['body']);$r=$call('validate','/api/stock-valuation/validate',['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'decision'=>'approved','reason'=>'Distinct HTTP review of the actual two-line impact.']);$ok($r['status']===200,'independent HTTP journal review '.$m['id']);$d=['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'reason'=>'Apply only selected physical value through the common writer.'];$key=(string)Str::uuid();$r=$call('prepare','/api/stock-valuation/post',$d,$key);$ok($r['status']===200,'HTTP post selected stock movement '.$m['id']);$ok($call('prepare','/api/stock-valuation/post',$d,$key)['data']['journal_id']===$r['data']['journal_id'],'HTTP retry one journal '.$m['id']);}
$r=$call('prepare','/api/stock-valuation/receipts/ledger/'.$key);$ok($r['status']===200&&$r['data']['operation_executed']===false,'journal receipt recovery does not execute');$ok($call('read','/api/stock-valuation/receipts/ledger/'.$key)['status']===404,'another actor cannot read journal receipt');
$r=$call('read','/stock-valuation/reports/trial-balance?from_on=2026-10-01&to_on=2026-10-31');$ok($r['status']===200&&str_contains($r['body'],'175,00'),'same common balancete rendered');$r=$call('read','/stock-valuation/reports/account-ledger?from_on=2026-10-01&to_on=2026-10-31&account_id='.$f['accounts']['QA-STOCK']);$ok($r['status']===200&&str_contains($r['body'],'75,00'),'same common stock account ledger rendered');
$p=$call('prepare','/api/stock-valuation/period-prepare',['period_key'=>'QA-2026-10','reason'=>'Complete HTTP stock and journal period conference.']);$ok($p['status']===200,'prepare period through HTTP');$p=$p['data'];$ok($call('validate','/api/stock-valuation/period-validate',['check_id'=>$p['check_id'],'revision'=>$p['revision'],'decision'=>'approved','reason'=>'Distinct HTTP complete period review.'])['status']===200,'validate period through HTTP');$d=['check_id'=>$p['check_id'],'revision'=>$p['revision'],'reason'=>'Explicit HTTP confirmation of synthetic stock period close.'];$key=(string)Str::uuid();$r=$call('prepare','/api/stock-valuation/period-close',$d,$key);$ok($r['status']===200,'close period through HTTP');$ok($call('prepare','/api/stock-valuation/period-close',$d,$key)['data']['closure_id']===$r['data']['closure_id'],'HTTP close retry same receipt');
$r=$call('prepare','/stock-valuation');$ok($r['status']===200&&!str_contains($r['body'],'class="command"')&&!str_contains($r['body'],'id="rate"'),'closed workspace has no mutation forms');file_put_contents($s['root'].'/http-closed.html',$r['body']);
$anonymous=$client->get('/api/stock-valuation',['headers'=>['Accept'=>'application/json']]);$ok($anonymous->getStatusCode()===401,'anonymous API refused');$ok(DB::table('gl_journals')->count()===3&&DB::table('gl_lines')->count()===6&&DB::table('gl_period_closures')->count()===1&&$fingerprint()===$before,'HTTP cycle exact totals preserves physical budget financial and grants');
file_put_contents($s['root'].'/http-proof.json',json_encode(['passed'=>true,'checks'=>$checks,'actual_http'=>true,'journals'=>3,'lines'=>6,'closures'=>1,'physical_financial_rights_unchanged'=>true,'visual_review'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." actual HTTP stock cycle checks passed.\n";
}finally{$server->stop(2);file_put_contents($s['root'].'/http-server.log',$server->getOutput()."\n".$server->getErrorOutput());}