<?php
declare(strict_types=1);
require __DIR__.'/bootstrap.php';require __DIR__.'/StrictWire.php';
$recovery=getenv('PAGE_RESUME')==='1';$check($recovery===is_file($runtime.'/private-journal.json'),'Fresh run or explicit own recovery only');$before=$snapshot();$tokens=[];$cases=[];$proofs=[];$owned=[];$errors=[];$collections=[];$cliStats=[];$dry=[];
if($recovery){$old=json_decode(file_get_contents($runtime.'/private-journal.json'),true);$check(count($old['cases'])===8&&count($old['owned'])===2,'Only exact first add checkpoints can resume');$cases=$old['cases'];$owned=$old['owned'];$collections=$old['collections'];$cliStats=$old['cli_stats'];$check($before===$old['before'],'Recovery natural rows unchanged');copy($runtime.'/private-report.json',$runtime.'/private-common-container-red-report.json');}
$checkpoint=static function()use($runtime,&$cases,&$owned,&$errors,&$collections,&$cliStats,$before,$boundary){$v=['cases'=>$cases,'owned'=>$owned,'errors'=>$errors,'collections'=>$collections,'cli_stats'=>$cliStats,'before'=>$before,'boundary'=>$boundary];file_put_contents($runtime.'/private-journal.json',json_encode($v,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION));chmod($runtime.'/private-journal.json',0600);};
foreach($tenants as $module=>$tenant){$tokens[$module]=bin2hex(random_bytes(24));$cache->set($tokens[$module],['user'=>['store_user_id'=>$tenant,'store_id'=>$tenant,'real_name'=>$marker,'user_name'=>$marker,'is_super'=>1],'store_id'=>$tenant,'is_login'=>true],3600);}
$shape=static function($v)use(&$shape){if(is_object($v)){$r=['kind'=>'object','members'=>[]];foreach(get_object_vars($v) as $k=>$x)$r['members'][$k]=$shape($x);return $r;}if(is_array($v))return ['kind'=>'array','items'=>array_map($shape,$v)];return ['kind'=>gettype($v)];};
$req=static function($module,$name,$action,$params=[],$expected=200,$auth=true)use($runtime,$base,$tokens,$check,$checkpoint,$shape,&$cases,&$collections,&$cliStats){$scenario='PILOT_PG_'.strtoupper($module.'_'.$name);$method=in_array($action,['list','detail','defaultData'],true)?'GET':'POST';$headers=[['key'=>'Host','value'=>$module.'.page-config.invalid'],['key'=>'Platform','value'=>'POC'],['key'=>'X-Pilot-Scenario','value'=>$scenario]];if($auth)$headers[]=['key'=>'Access-Token','value'=>$tokens[$module]];$url=$base.'/'.$module.'/page/'.$action;if($method==='GET')$url.='?'.http_build_query($params);else $headers[]=['key'=>'Content-Type','value'=>'application/json'];$request=['method'=>$method,'url'=>$url,'header'=>$headers];if($method==='POST')$request['body']=['mode'=>'raw','raw'=>json_encode($params,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION),'options'=>['raw'=>['language'=>'json']]];
    $item=['id'=>$scenario,'name'=>$module.' '.$name,'type'=>'http','metaInfo'=>['type'=>'HTTP'],'request'=>$request,'event'=>[['listen'=>'test','script'=>['type'=>'text/javascript','exec'=>["pm.test('HTTP 200',function(){pm.response.to.have.status(200);});","pm.test('JSON object response',function(){pm.response.to.be.json;});var b=pm.response.json();","pm.test('Numeric business status',function(){pm.expect(b.status).to.eql(".$expected.");});","pm.test('Trace contract',function(){pm.expect(b.trace_id).to.match(/^[0-9a-f]{13}$/);});","pm.test('Message and data types',function(){pm.expect(typeof b.message).to.eql('string');pm.expect(typeof b.data).to.eql('object');});"]]]]];
    $collection=['apifoxCli'=>'2.2.8','info'=>['name'=>'智慧餐厅Page自有元数据验收'],'item'=>[['name'=>'Page场景','item'=>[$item]]]];$path=$runtime.'/private-request.json';file_put_contents($path,json_encode($collection,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));chmod($path,0600);$reportDir=$runtime.'/private-cli-report/'.$scenario;if(!is_dir($reportDir))mkdir($reportDir,0700,true);$start=time();$cli=getenv('PAGE_CLI')?:'/tmp/cdx-apifox-cli-page-2.2.8/bin/cli.js';$p=proc_open(['node',$cli,'run',$path,'-r','json','--out-dir',$reportDir,'--out-file','result','--on-error','end','--timeout-request','30000','--silent'],[0=>['file','/dev/null','r'],1=>['file',$runtime.'/private-cli-output.log','a'],2=>['file',$runtime.'/private-cli-output.log','a']],$pipes);$exit=proc_close($p);$end=time();
    $wire=null;foreach(file($runtime.'/private-wire.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line){$w=json_decode($line,true);if($w['scenario']===$scenario)$wire=$w;}$check($wire!==null,'Native Apifox actual wire checkpoint '.$scenario);$raw=$wire['raw'];$body=json_decode($raw);$check(is_object($body),'Actual JSON object '.$scenario);$safe=clone $body;if(isset($safe->trace_id)&&preg_match('/^[0-9a-f]{13}$/D',$safe->trace_id))$safe->trace_id='VALID_DYNAMIC_TRACE';$case=['scenario'=>$scenario,'module'=>$module,'method'=>$method,'path'=>'/'.$module.'/Page/'.$action,'wire_path'=>'/'.$module.'/page/'.$action,'http_status'=>$wire['http_status'],'body'=>$safe,'shape'=>$shape($body),'window'=>[$start,$end],'raw_sha256'=>hash('sha256',$raw),'native_cli_exit'=>$exit,'params'=>$params];$cases[]=$case;$collections[]=$collection;$checkpoint();
    foreach(glob($reportDir.'/*.json') as $f){$r=json_decode(file_get_contents($f),true);if(isset($r['run']['stats']))$cliStats[]=['scenario'=>$scenario,'raw_report_sha256'=>hash_file('sha256',$f),'stats'=>$r['run']['stats'],'failures'=>count($r['run']['failures']??[])];}$checkpoint();$check($exit===0&&$wire['http_status']===200&&$body->status===$expected,'Native HTTP/business assertions '.$scenario);return $case;};
$rows=static function($tenant)use($query){return $query('SELECT * FROM yoshop_page WHERE store_id=? ORDER BY page_id',[$tenant]);};
$verify=static function($module,$id,$expected,$type,$deleted)use($query,$tenants,$check,&$proofs){$r=$query('SELECT * FROM yoshop_page WHERE page_id=? AND store_id=?',[$id,$tenants[$module]]);$check(count($r)===1,'Independent exact original row');$r=$r[0];$check((int)$r['page_type']===$type&&(int)$r['is_delete']===$deleted&&$r['page_name']===$expected['page']['params']['name'],'Original name/type/soft-delete');$check(strictWireEqual(json_decode($r['page_data']),json_decode(json_encode($expected,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION))),'Complete original JSON scalar/container/order');$check((int)$r['create_time']>0&&(int)$r['update_time']>0,'Original Unix time values');$proofs[]=['module'=>$module,'page_id'=>$id,'original_eight_columns_checked'=>true,'JSON_value_type_container_order'=>true,'type'=>$type,'is_delete'=>$deleted];return $r;};
$forms=[];$ids=[];
foreach($tenants as $module=>$tenant){try{
    if(!$recovery){$req($module,'DEFAULT_DATA','defaultData');$req($module,'AUTH_REFUSAL','list',[],401,false);$req($module,'EMPTY_LIST','list');}
    $form=['page'=>['params'=>['name'=>'营养健康服务页 '.$marker.' '.$module,'title'=>"营养服务'标题\\",'shareTitle'=>'健康餐厅分享'],'style'=>['titleTextColor'=>'black','titleBackgroundColor'=>'#ffffff']],'items'=>[['type'=>'notice','params'=>['text'=>"公告中文'\\","link"=>null,'showIcon'=>true,'scrollable'=>false],'style'=>['paddingTop'=>0]],['type'=>'image','style'=>['paddingTop'=>2.5],'data'=>[['imgUrl'=>'https://page-config.invalid/cover.png','imgName'=>'营养餐.jpg','link'=>null]]],['type'=>'richText','params'=>['content'=>'<p>营养健康餐厅</p>'],'style'=>['paddingTop'=>0]]],'audit'=>['marker'=>$marker,'boolean'=>true,'nothing'=>null,'decimal'=>2.5,'emptyArray'=>[],'emptyObject'=>(object)[],'ordered'=>['甲','乙','丙']]];
    if(!$recovery)$req($module,'ADD','add',['form'=>$form]);
    // ThinkPHP postForm requests associative arrays: an incoming empty JSON object becomes [].
    // Preserve the original raw request and assert this existing MySQL behavior explicitly.
    $form['audit']['emptyObject']=[];$r=$rows($tenant);$check(count($r)===1&&strpos($r[0]['page_data'],$marker)!==false,'Find add ID from exact marker/tenant');$id=(int)$r[0]['page_id'];
    if($recovery){$check($owned[$module]===[$id]&&(int)$r[0]['page_type']===20&&(int)$r[0]['is_delete']===1,'Recover only exact independently retained own initial page');$s=$db->prepare('UPDATE yoshop_page SET is_delete=0 WHERE page_id=? AND store_id=? AND page_type=20 AND is_delete=1');$s->execute([$id,$tenant]);}
    $owned[$module]=[$id];$ids[$module]=$id;$checkpoint();$verify($module,$id,$form,20,0);$req($module,'DETAIL_CREATED','detail',['pageId'=>$id]);
    $edit=$form;$edit['page']['params']['name']='营养健康服务页已更新 '.$marker.' '.$module;$edit['items'][2]['params']['content']='&lt;p&gt;中文更新&lt;/p&gt;';$req($module,'EDIT','edit',['pageId'=>$id,'form'=>$edit]);$edit['items'][2]['params']['content']='<p>中文更新</p>';$verify($module,$id,$edit,20,0);$req($module,'DETAIL_EDITED','detail',['pageId'=>$id]);$forms[$module]=$edit;
    $other=$form;$other['page']['params']['name']='餐厅帮助页 '.$marker.' '.$module;$other['items']=[];$req($module,'ADD_SECOND','add',['form'=>$other]);$r=$rows($tenant);$check(count($r)===2,'Two exact own rows');$otherId=(int)$r[1]['page_id'];$owned[$module][]=$otherId;$ids[$module.'_second']=$otherId;$forms[$module.'_second']=$other;$checkpoint();$verify($module,$otherId,$other,20,0);$req($module,'FILTER_LIST','list',['name'=>'服务页','page'=>1,'listRows'=>1]);
}catch(Throwable $e){$errors[]=['module'=>$module,'stage'=>'CRUD','message'=>$e->getMessage()];$checkpoint();}}
foreach($tenants as $module=>$tenant){if(!isset($forms[$module.'_second']))continue;try{$otherModule=$module==='store'?'terminal':'store';$sentinel=$rows($tenants[$otherModule]);\think\App::$store_id=$tenant;\cores\BaseModel::$storeId=$tenant;$class='app\\'.$module.'\\model\\Page';$m=new $class;$sql=$m->where(['page_type'=>10])->fetchSql()->update(['page_type'=>20]);$dry[$module]=['sha256'=>hash('sha256',$sql),'own_tenant_in_actual_framework_SQL'=>preg_match('/store_id["`]?\s*=\s*[\x27]?'.$tenant.'\b/i',$sql)===1];$check($dry[$module]['own_tenant_in_actual_framework_SQL'],'setHome first UPDATE lacks tenant');$id=$ids[$module];$otherId=$ids[$module.'_second'];$req($module,'SET_HOME','setHome',['pageId'=>$id]);$verify($module,$id,$forms[$module],10,0);$check($rows($tenants[$otherModule])===$sentinel,'Other tenant exact full rows unchanged');$req($module,'HOME_DELETE_REFUSAL','delete',['pageId'=>$id],500);$verify($module,$id,$forms[$module],10,0);$req($module,'SET_HOME_SECOND','setHome',['pageId'=>$otherId]);$verify($module,$id,$forms[$module],20,0);$verify($module,$otherId,$forms[$module.'_second'],10,0);$req($module,'DELETE_CUSTOM','delete',['pageId'=>$id]);$verify($module,$id,$forms[$module],20,1);$req($module,'LIST_AFTER_DELETE','list');$req($module,'CROSS_TENANT_DETAIL','detail',['pageId'=>$ids[$otherModule]]);$check($rows($tenants[$otherModule])===$sentinel,'Cross tenant row unchanged');}catch(Throwable $e){$errors[]=['module'=>$module,'stage'=>'HOME_OR_DELETE','message'=>$e->getMessage()];$checkpoint();}}
// Retain only exact marker-owned rows, non-default and soft-deleted. No natural row is touched.
foreach($owned as $module=>$pageIds)foreach($pageIds as $id){$r=$query('SELECT * FROM yoshop_page WHERE page_id=? AND store_id=?',[$id,$tenants[$module]]);$check(count($r)===1&&strpos($r[0]['page_data'],$marker)!==false,'Cleanup exact own marker');$s=$db->prepare('UPDATE yoshop_page SET page_type=20,is_delete=1 WHERE page_id=? AND store_id=?');$s->execute([$id,$tenants[$module]]);}
$tokensGone=true;foreach($tokens as $token){$cache->delete($token);$tokensGone=$tokensGone&&$cache->get($token)===null;}$cache->clear();$after=$snapshot();$retained=[];foreach($owned as $module=>$pageIds){$retained[$module]=$rows($tenants[$module]);foreach($retained[$module] as $r)$check(in_array((int)$r['page_id'],$pageIds,true)&&(int)$r['page_type']===20&&(int)$r['is_delete']===1,'Final safe retention');}
$events=[];foreach(file($runtime.'/queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $l)$events[]=json_decode($l,true);$report=['lane'=>$lane,'cases'=>$cases,'proofs'=>$proofs,'errors'=>$errors,'owned'=>$owned,'before'=>$before,'after'=>$after,'natural_unchanged'=>$before===$after,'boundary'=>$boundary,'dry_setHome'=>$dry,'events'=>$events,'cli_stats'=>$cliStats,'resources'=>['tokens_absent'=>$tokensGone,'cache_files'=>0],'retained_rows'=>$retained];file_put_contents($runtime.'/private-report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION));chmod($runtime.'/private-report.json',0600);$checkpoint();echo json_encode(['lane'=>$lane,'HTTP'=>count($cases),'proofs'=>count($proofs),'errors'=>$errors,'natural_unchanged'=>$before===$after,'owned'=>$owned,'tokens_absent'=>$tokensGone]),"\n";exit(count($errors)||$before!==$after||!$tokensGone?1:0);
