create(['name'=>'Осень','utm'=>$utm]); checkPreset(array_keys($saved)===['id','name','utm'],'preset has no source domain or target binding'); $utm->theme->weights->{'1'}=100;checkPreset($presets->get($saved['id'])['utm']->theme->weights->{'1'}===33.33,'draft is an immutable independent snapshot'); checkPreset((new UtmPresets(new Store($store->dir)))->get($saved['id'])['name']==='Осень','preset survives store reload'); foreach([['name'=>'','utm'=>$saved['utm']],['name'=>' ','utm'=>$saved['utm']],['name'=>'осень','utm'=>$saved['utm']],['name'=>'x','utm'=>$saved['utm'],'domain'=>'a.invalid'],['name'=>'x','utm'=>(object)[]],['name'=>'x','utm'=>(object)['theme'=>(object)['default'=>1,'weights'=>(object)['1'=>80]]]],['name'=>'x','utm'=>(object)['theme'=>(object)['default'=>1,'weights'=>(object)['1'=>100],'note'=>'secret']]],['name'=>'x','utm'=>(object)['unknown'=>(object)[]]]] as $bad)rejectsPreset(fn()=>$presets->create($bad),'strict independent preset validation'); $paths=[];foreach(['a','b'] as $id){$paths[$id]=$root.'/'.$id;mkdir($paths[$id]);}putenv('ADMIN_LOCAL_ROOTS='.implode(',',$paths)); $raw='{"host":"keep.invalid","utm":{"theme":{"default":"dark","weights":{"dark":100},"note":"keep"},"reg":{"default":2,"weights":{"2":100}},"part":{"default":"1","weights":{"1":100},"metadata":{"keep":true}},"custom":{"a":1}},"requisites":["keep"],"unknown":{}}'; foreach($paths as $id=>$path){file_put_contents($path.'/config.json',$raw);$store->saveTarget(['id'=>$id,'domain'=>$id.'.invalid','type'=>'local','root'=>$path,'enabled'=>true]);$store->verifyTarget($id);} $store->write('utm-notes.json',['theme'=>['text'=>'keep note']]);$notes=file_get_contents($store->path('utm-notes.json')); $read=fn($id)=>(new Transport($store,$store->target($id)))->readConfig();$selection=fn($id)=>(object)['id'=>$id,'version'=>$read($id)['version']]; $copy=new Copy($store);$job=$copy->create(['preset'=>$saved['id'],'targets'=>[$selection('a'),$selection('b')]]); $presets->delete($saved['id']);rejectsPreset(fn()=>$presets->get($saved['id']),'deleted preset unavailable'); file_put_contents($paths['b'].'/config.json',str_replace('keep.invalid','foreign.invalid',$raw));$jobs=new Jobs($store);$jobs->run($job['id']);$done=$jobs->get($job['id']); checkPreset($done['results'][0]['status']==='success'&&$done['results'][1]['status']==='conflict','queued snapshot survives deletion and isolates CAS conflict'); $a=$read('a')['config'];checkPreset($a->utm->part->default==='2'&&$a->utm->part->weights->{'1'}===0&&$a->utm->part->weights->{'2'}===100&&$a->utm->part->metadata->keep===true,'part preserved through independent preset queue, CAS, metadata and exact backup');checkPreset($a->host==='keep.invalid'&&$a->utm->theme->note==='keep'&&$a->utm->reg->default===2&&$a->utm->custom->a===1&&$a->unknown instanceof stdClass,'host, absent groups, metadata and unknown types unchanged'); checkPreset($a->utm->theme->default==='3'&&$a->utm->theme->weights->dark===33.33&&$a->utm->theme->weights->{'3'}===66.67&&$a->utm->cons->weights->{'2'}===0,'manual variants, semantic targets and all-zero percentages copied'); checkPreset($store->backupContent('a',$store->backups('a')[0]['id'])===$raw&&$store->backups('b')===[],'exact backup only for successful recipient'); checkPreset(file_get_contents($store->path('utm-notes.json'))===$notes,'notes stay byte identical'); $retry=$store->read('jobs/'.$job['id'].'.json');$retry['status']='running';$retry['results'][0]['status']='running';$store->write('jobs/'.$job['id'].'.json',$retry);$jobs->run($job['id']);checkPreset(count($store->backups('a'))===1,'desired hash recovery does not repeat write'); $next=$presets->create(['name'=>'После удаления','utm'=>$saved['utm']]);$store->setTargetVisibility('b',true);$store->deleteTarget('b');checkPreset($presets->get($next['id'])['name']==='После удаления','target deletion cannot delete or detach preset'); foreach([['source'=>$selection('a'),'targets'=>[$selection('a')]],['preset'=>$next['id'],'targets'=>[]],['preset'=>$next['id'],'targets'=>[$selection('a'),$selection('a')]],['preset'=>$next['id'],'targets'=>[(object)['id'=>'a','version'=>'bad']]],['preset'=>$next['id'],'targets'=>[$selection('a')],'utm'=>(object)[]]] as $bad)rejectsPreset(fn()=>$copy->create($bad),'strict copy request'); $same=$copy->create(['preset'=>$next['id'],'targets'=>[$selection('a')]]);$jobs->run($same['id']);checkPreset($jobs->get($same['id'])['results'][0]['status']==='success','any domain can receive independent preset'); echo "ALL PRESET CHECKS PASS\n";