<?php
require __DIR__.'/env.php';define('APP_PATH',__DIR__.'/application/');require __DIR__.'/thinkphp/base.php';require APP_PATH.'common.php';
use think\Config;use app\common\service\Order;
Config::set(['redis'=>['host'=>'127.0.0.1','port'=>6379,'timeout'=>0.3],'cache'=>['type'=>'File','path'=>__DIR__.'/runtime/upgrade-cache/']]);
$mode=$argv[1]??'';
if($mode==='orders'){$a=[];for($i=0;$i<500;$i++)$a[]=Order::createOrderNo();echo json_encode($a);exit;}
if($mode==='guard'){echo prevent_repeat_submit('upgrade-shared-guard',30)?'1':'0';exit;}
if($mode==='offline'){
 Config::set(['redis'=>['host'=>'127.0.0.1','port'=>1,'timeout'=>0.1]]);
 try{Order::createOrderNo();throw new RuntimeException('must fail without Redis');}catch(RuntimeException $e){if($e->getMessage()!=='订单号生成服务暂不可用')throw $e;}
 if(!prevent_repeat_submit('upgrade-fallback',5)||prevent_repeat_submit('upgrade-fallback',5))throw new RuntimeException('cache fallback guard failed');
 echo 'PASS Redis unavailable: order number fails explicitly, duplicate guard falls back';exit;
}
function children($mode,$count){$children=[];for($i=0;$i<$count;$i++){$p=proc_open(escapeshellarg(PHP_BINARY).' '.escapeshellarg(__FILE__).' '.$mode,[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes);fclose($pipes[0]);$children[]=[$p,$pipes];}$results=[];foreach($children as [$p,$pipes]){$out=stream_get_contents($pipes[1]);$err=stream_get_contents($pipes[2]);fclose($pipes[1]);fclose($pipes[2]);$code=proc_close($p);if($code!==0||$err)throw new RuntimeException('test child failed: '.$err);$results[]=$out;}return $results;}
$checks=0;function ok($c,$m){global $checks;if(!$c)throw new RuntimeException($m);$checks++;echo 'PASS '.$m.PHP_EOL;}
$r=getRedis();ok($r instanceof Redis,'real isolated Redis connection');
$numbers=[];foreach(children('orders',4) as $out){$rows=json_decode($out,true);ok(is_array($rows)&&count($rows)===500,'parallel worker generated 500 order numbers');$numbers=array_merge($numbers,$rows);}
ok(count(array_unique($numbers))===2000,'2000 concurrent order numbers are unique');
foreach(array_unique(array_map(function($x){return substr($x,0,10);},$numbers)) as $prefix){$key='order_no:20'.$prefix;$ttl=$r->ttl($key);ok($ttl>0&&$ttl<=120,'order sequence has bounded 120-second expiry');}
Order::forceNextOrderNo('FIXED-REPLAY-ORDER');ok(Order::createOrderNo()==='FIXED-REPLAY-ORDER','existing replay order number preserved');ok(Order::createOrderNo()!=='FIXED-REPLAY-ORDER','forced replay consumed once');
$guard=children('guard',8);ok(count(array_filter($guard,function($v){return $v==='1';}))===1,'8 simultaneous duplicate submissions: exactly one accepted');
ok(prevent_repeat_submit('upgrade-a',1)&&prevent_repeat_submit('upgrade-b',1),'different business keys do not block each other');ok(!prevent_repeat_submit('upgrade-a',1),'duplicate key rejected before expiry');usleep(1200000);ok(prevent_repeat_submit('upgrade-a',1),'key eligible again after expiry');
$out=children('offline',1)[0];ok(strpos($out,'PASS Redis unavailable')===0,'Redis unavailable and cache fallback behavior verified');echo $out.PHP_EOL;
echo 'PASS Redis upgrade '.$checks.' checks'.PHP_EOL;
