86 lines
7.1 KiB
PHP
86 lines
7.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\service {
|
|
function hrtime(bool $number=false){return $number?$GLOBALS['r26FinalNs']:[0,0];}
|
|
function usleep(int $us):void{$GLOBALS['r26FinalNs']+=$us*1000;}
|
|
}
|
|
namespace app\adminapi\controller {class BaseAdminController{}}
|
|
namespace app\adminapi\logic\tcm {
|
|
class DiagnosisAiLogic {
|
|
public static function getAssistantErrorCode():string{return 'UPSTREAM_TIMEOUT';}
|
|
public static function getError():string{return 'original fixture error';}
|
|
public static function getAssistantErrorMeta():array{return ['retryable'=>true];}
|
|
}
|
|
}
|
|
namespace app\common\service {
|
|
class DifyWorkflowRunStatus {
|
|
public static function lookup(array $id,int $ms):array{
|
|
++$GLOBALS['r26FinalGets'];return ($GLOBALS['r26FinalLookup'])($id,$ms);
|
|
}
|
|
}
|
|
}
|
|
namespace think\facade {
|
|
class Log {public static function write(string $message,string $level=''):void{if(!empty($GLOBALS['r26FinalLogThrows']))throw new \RuntimeException('fixture log failure');}}
|
|
}
|
|
namespace {
|
|
require dirname(__DIR__).'/app/adminapi/service/AssistantRequestControl.php';
|
|
require dirname(__DIR__).'/app/adminapi/controller/tcm/DiagnosisController.php';
|
|
use app\adminapi\service\AssistantRequestControl as Control;
|
|
use app\adminapi\controller\tcm\DiagnosisController;
|
|
$checks=0;$number=0;$GLOBALS['r26FinalNs']=100000000000;$GLOBALS['r26FinalGets']=0;
|
|
$expect=static function(bool $ok,string $why)use(&$checks):void{++$checks;if(!$ok)throw new \RuntimeException($why);};
|
|
$directory=sys_get_temp_dir().'/r26-final-'.bin2hex(random_bytes(8));
|
|
$make=static function()use($directory,&$number,$expect):array{
|
|
$GLOBALS['r26FinalNs']+=10000000000;$uuid=sprintf('e2345678-1234-4234-8234-%012d',++$number);
|
|
$c=new Control(7,11,$uuid,$directory);$expect($c->begin(),'Synthetic request begins.');$c->bindTask('qwen','fixture-user','fixture-task','fixture-run');
|
|
return [$c,$directory.'/'.hash('sha256','7:11:'.$uuid).'.json'];
|
|
};
|
|
$method=new \ReflectionMethod(DiagnosisController::class,'finishAssistantError');$controller=new DiagnosisController();
|
|
$emit=static function(array &$events):callable{return static function(string $event,array $payload)use(&$events):bool{$events[]=[$event,$payload];return true;};};
|
|
try {
|
|
foreach(['ordinary_timeout','cancelled']as$intent)foreach(['running','stopped','succeeded','failed']as$status){
|
|
[$c,$path]=$make();$cancelled=$intent==='cancelled';if($cancelled)$c->signalCancel();else$c->signalTimeout();
|
|
$c->markTerminal('fixture-task','stopped','fixture-run');$before=$GLOBALS['r26FinalGets'];
|
|
$GLOBALS['r26FinalLookup']=static function(array $id,int $ms)use($c,$expect,$status):array{
|
|
$expect($c->publicState()['transport_closed'],'Full-request recovery occurs after finish.');
|
|
$expect($ms>0&&$ms<=1000,'Original bounded final adapter budget.');
|
|
return ['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>$status];
|
|
};
|
|
$events=[];$method->invoke($controller,$emit($events),$c,$cancelled);$last=end($events)[1];
|
|
$expect($last['code']===($cancelled?'AI_ASSISTANT_CANCELLED':'UPSTREAM_TIMEOUT') && $last['message']===($cancelled?'本次请求已取消':'original fixture error'),'Independent recovery preserves original error/cancel outcome and exact message.');
|
|
$expect(count($events)===($cancelled?2:1) && $last['cancel_requested']===$cancelled && $last['timeout_reached']===!$cancelled,'Ordinary timeout never becomes user cancel; original progress intent remains.');
|
|
$expect($GLOBALS['r26FinalGets']-$before===($status==='running'?2:1),'Only bounded exact original run lookups occur.');
|
|
$expect($last['producer_terminal_status']==='stopped' && $last['persisted_run_status']===$status && $last['upstream_pending_task_count']===($status==='running'?1:0),'Final error receipt reports producer and independent status honestly.');
|
|
$expect($last['upstream_terminal_confirmed']===($status!=='running') && $last['lifecycle_conflict']===($status!=='stopped'),'Terminal source is independent GET; contrary receipt history stays visible.');
|
|
$expect(!str_contains(json_encode($events),'fixture-task')&&!str_contains(json_encode($events),'fixture-run')&&!str_contains(json_encode($events),'fixture-user'),'Final client error/progress contain no original private identities.');
|
|
if($status==='running')$expect(isset(json_decode(file_get_contents($path),true)['upstream']),'Full-request finish retains conflicting original pair.');
|
|
}
|
|
|
|
[$c,$path]=$make();$c->markTerminal('fixture-task','stopped','fixture-run');
|
|
$GLOBALS['r26FinalLookup']=static fn(array $id):array=>['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>'running'];
|
|
$c->closeGeneration('fixture-task',[\app\common\service\DifyWorkflowRunStatus::class,'lookup']);$before=$GLOBALS['r26FinalGets'];
|
|
$events=[];$method->invoke($controller,$emit($events),$c,false);
|
|
$expect($GLOBALS['r26FinalGets']===$before && end($events)[1]['upstream_pending_task_count']===1,'Final recovery cannot renew a generation already verified at EOF.');
|
|
|
|
foreach(['adapter_throws','log_throws','corrupt_control']as$failure){
|
|
[$c,$path]=$make();$c->signalTimeout();$c->markTerminal('fixture-task','stopped','fixture-run');
|
|
$GLOBALS['r26FinalLogThrows']=$failure==='log_throws';
|
|
$GLOBALS['r26FinalLookup']=static function(array $id)use($failure,$path):array{
|
|
if($failure==='adapter_throws')throw new \RuntimeException('fixture private adapter prose');
|
|
if($failure==='corrupt_control')file_put_contents($path,'fixture corrupt state');
|
|
return ['ok'=>true,'workflow_run_id'=>$id['workflow_run_id'],'status'=>'failed'];
|
|
};
|
|
$events=[];$method->invoke($controller,$emit($events),$c,false);$last=end($events)[1];
|
|
$expect($last['code']==='UPSTREAM_TIMEOUT' && $last['message']==='original fixture error' && $last['retryable'],'Recovery/telemetry/state failure never replaces original application error.');
|
|
$expect(!str_contains(json_encode($events),'private adapter prose')&&!str_contains(json_encode($events),'corrupt state'),'Failure prose is discarded.');
|
|
if($failure==='corrupt_control')$expect($last===['code'=>'UPSTREAM_TIMEOUT','message'=>'original fixture error','retryable'=>true],'Unavailable state does not fabricate a cleanup receipt.');
|
|
}
|
|
echo "R26 real controller final error/recovery: {$checks} assertions passed (synthetic controls and adapter only).\n";
|
|
} finally {
|
|
$real=realpath($directory);$temp=realpath(sys_get_temp_dir());
|
|
if($real!==false){if($temp===false||!str_starts_with(str_replace('\\','/',$real),rtrim(str_replace('\\','/',$temp),'/').'/r26-final-'))throw new \RuntimeException('Cleanup boundary');
|
|
foreach(new \DirectoryIterator($real)as$f){if($f->isDot())continue;if($f->isLink()||!$f->isFile())throw new \RuntimeException('Unexpected temp entry');unlink($f->getPathname());}rmdir($real);}
|
|
}
|
|
}
|