53 lines
7.0 KiB
PHP
53 lines
7.0 KiB
PHP
<?php
|
|
|
|
use App\Modules\Purchasing\InternalRequisitionQa;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tempo\Purchasing\SgfCandidate\Schema;
|
|
|
|
$s = json_decode(file_get_contents(getenv('IR_COMPOSITION_STATE')), true, 64, JSON_THROW_ON_ERROR);
|
|
if(file_exists($s['root'].'/result.json'))throw new LogicException('Completed composition proof preserved');
|
|
require $s['app'].'/vendor/autoload.php';
|
|
$app = require $s['app'].'/bootstrap/app.php';
|
|
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
|
$checks=[]; $check=function(bool $v,string $name) use (&$checks) {if(!$v)throw new RuntimeException($name);$checks[]=$name;};
|
|
$check(!DB::getSchemaBuilder()->hasTable('users'),'new database; provider mount performed no migration');
|
|
$routes=Illuminate\Support\Facades\Route::getRoutes();
|
|
$check(count(array_filter(iterator_to_array($routes),fn($r)=>$r->uri()==='api/internal-requisitions'))===1,'provider mounts one canonical list route');
|
|
config(['internal_requisitions.qa_enabled'=>false]);$check(InternalRequisitionQa::mount()===null,'default off does not mount');config(['internal_requisitions.qa_enabled'=>true]);
|
|
require $s['app'].'/packages/budget-control/tests/sgf-schema.php';
|
|
require $s['app'].'/packages/budget-control/tests/host-support.php';
|
|
$f=DB::transaction(fn()=>budgetQaFixture('sgf'));budgetQaCreate('sgf',$f,1000000,true);Schema::install(DB::connection());
|
|
$grant=fn($id,$p)=>DB::table('permission_grants')->insertOrIgnore($f['scope']+['user_id'=>$id,'permission'=>$p,'created_at'=>now(),'updated_at'=>now()]);
|
|
foreach([$f['actor'],$f['validator']]as$id)foreach(['view','prepare','approve.department_head']as$p)$grant($id,'purchasing_requisitions.'.$p);
|
|
$grant($f['actor'],'purchasing_requisitions.configure');$grant($f['validator'],'purchasing_budget.record');
|
|
file_put_contents($s['root'].'/fixture.json',json_encode($f,JSON_THROW_ON_ERROR));
|
|
$socket=stream_socket_server('tcp://127.0.0.1:0');$address=stream_socket_get_name($socket,false);fclose($socket);$port=(int)substr(strrchr($address,':'),1);
|
|
$server=new Symfony\Component\Process\Process([PHP_BINARY,'-S','127.0.0.1:'.$port,dirname(__DIR__).'/scripts/requisition-composition-http.php'],$s['app'],null,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'=>20]);$sessions=[];
|
|
$enter=function($role,$module='purchasing')use($client,&$sessions){$jar=new GuzzleHttp\Cookie\CookieJar();$r=$client->get('/qa/enter/'.$role,['cookies'=>$jar,'query'=>['module'=>$module]]);if($r->getStatusCode()!==200)throw new RuntimeException('QA entry failed');$sessions[$role]=['jar'=>$jar]+json_decode((string)$r->getBody(),true,32,JSON_THROW_ON_ERROR);};
|
|
$call=function($role,$url,$body=null,$context=null)use($client,&$sessions){$v=$sessions[$role];$options=['cookies'=>$v['jar'],'headers'=>['Accept'=>'application/json','X-CSRF-TOKEN'=>$v['csrf'],'X-SGF-Context'=>$context??$v['context'],'X-Purchasing-Context'=>'procurement.internal']];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];};
|
|
$enter('actor');$enter('validator');
|
|
$policy=['version'=>0,'reason'=>'Explicit isolated shared requisition composition.','enabled'=>true,'budget_required'=>true,'first_limit_minor'=>1000000,'second_limit_minor'=>2000000,'approval_includes_tax'=>true,'centres'=>[['id'=>$f['centre'],'department_ref'=>'OPER']],'members'=>[['user_id'=>$f['actor'],'department_ref'=>'OPER'],['user_id'=>$f['validator'],'department_ref'=>'OPER']]];
|
|
$r=$call('actor','/api/internal-requisitions/policy',$policy);$check($r['status']===200,'configure through provider routes');
|
|
$r=$call('actor','/purchasing/internal-requisitions');$check($r['status']===200&&str_contains($r['body'],'Compras internas'),'canonical workflow screen available');
|
|
$input=['operation_key'=>(string)Str::uuid(),'cost_center_id'=>$f['centre'],'priority'=>'normal','needed_by'=>now()->addWeek()->toDateString(),'justification'=>'Isolated integration of existing shared requisitions.','items'=>[['description'=>'Material QA','category'=>'Consumíveis','purchase_type'=>'consumable','quantity'=>'2.000','unit'=>'UN','unit_price'=>'50.00','tax_rate'=>'0.00']]];
|
|
$r=$call('actor','/api/internal-requisitions/save',$input);$check($r['status']===200,'create through shared adapter');$record=$r['data'];$id=$record['id'];
|
|
$check($call('actor','/api/internal-requisitions/save',$input)['data']['id']===$id&&DB::table('ir_requests')->count()===1,'create replay no duplicate');
|
|
$submit=['operation_key'=>(string)Str::uuid(),'version'=>$record['version']];$r=$call('actor','/api/internal-requisitions/'.$id.'/submit',$submit);$check($r['status']===200,'submit reserves common budget');
|
|
$check($call('actor','/api/internal-requisitions/'.$id.'/submit',$submit)['status']===200&&DB::table('cb_budget_entries')->where('kind','reserve')->count()===1,'submission replay reserves once');
|
|
$decision=['operation_key'=>(string)Str::uuid(),'version'=>$r['data']['version'],'decision'=>'approved'];
|
|
$check($call('actor','/api/internal-requisitions/'.$id.'/decide',$decision)['status']===403,'distinct approval retained');
|
|
$r=$call('validator','/api/internal-requisitions/'.$id.'/decide',$decision);$check($r['status']===200&&$r['data']['status']==='approved','independent approval through mounted routes');
|
|
$check($call('validator','/api/internal-requisitions/'.$id.'/decide',$decision)['status']===200&&DB::table('ir_commands')->count()===3,'approval replay no duplicate');
|
|
$check($call('actor','/api/internal-requisitions/'.$id.'/save',[...$input,'operation_key'=>(string)Str::uuid(),'version'=>1])['status']===409,'stale version refused');
|
|
$check($call('actor','/api/internal-requisitions',null,'stale-context')['status']===409,'read context token revalidated');
|
|
$enter('actor','commercial');$check(in_array($call('actor','/api/internal-requisitions')['status'],[403,409],true),'foreign active module refused');$enter('actor');
|
|
DB::table('permission_grants')->where($f['scope'])->where('user_id',$f['actor'])->where('permission','purchasing_requisitions.view')->delete();
|
|
$check($call('actor','/api/internal-requisitions')['status']===403,'revoked read permission refused');
|
|
$check(DB::table('cb_budget_entries')->where('kind','reserve')->sum('reserved_minor')==10000&&DB::table('cb_budget_consumptions')->count()===0&&DB::table('ir_requests')->count()===1,'one request and single100MZN reserve; no automatic consumption');
|
|
file_put_contents($s['root'].'/result.json',json_encode(['passed'=>true,'checks'=>$checks,'requests'=>1,'reservations'=>1,'reservation_minor'=>10000,'actual_http'=>true,'new_qa_database'=>true,'existing_shared_core'=>true,'destination_activation'=>false,'visual_review'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." directed composition checks passed.\n";
|
|
}finally{$server->stop(2);file_put_contents($s['root'].'/http-output.txt',$server->getOutput()."\n".$server->getErrorOutput());}
|