Files
preland/admin/tests/backend.php
T
2026-10-25 11:33:56 +03:00

36 lines
2.9 KiB
PHP

<?php
declare(strict_types=1);
require dirname(__DIR__) . '/bootstrap.php';
use PrelandAdmin\Config;
use PrelandAdmin\Store;
use PrelandAdmin\Transport;
use PrelandAdmin\Jobs;
function check(bool $value, string $message): void { if (!$value) throw new RuntimeException($message); echo "PASS $message\n"; }
$root = sys_get_temp_dir() . '/preland-admin-' . bin2hex(random_bytes(6));
mkdir($root, 0700, true); mkdir($root . '/site',0700); mkdir($root . '/state',0700);
putenv('ADMIN_STATE_DIR=' . $root . '/state'); putenv('ADMIN_LOCAL_ROOTS=' . $root . '/site');
$initial = (object)['host'=>'zoomzilla.ru','requisites'=>['one'],'custom'=>(object)['flag'=>true,'zero'=>0,'empty'=>(object)[]],'utm'=>(object)[]];
file_put_contents($root . '/site/config.json', json_encode($initial));
$store = new Store($root . '/state');
$target = $store->saveTarget(['id'=>'test','name'=>'Тест','domain'=>'test.invalid','environment'=>'stage','type'=>'local','root'=>$root . '/site','enabled'=>true]);
$store->verifyTarget('test');$target=$store->target('test');
$transport = new Transport($store, $target);
$before = $transport->readConfig();
$next = Config::patch($before['config'], [['path'=>['host'],'value'=>'example.org']]);
check($next->custom->flag === true && $next->custom->zero === 0 && $next->custom->empty instanceof stdClass, 'selected patch preserves unrelated values and types');
$transport->writeConfig($before['version'], Config::encode($next));
$after = $transport->readConfig(); check($after['config']->host === 'example.org','atomic save and reread');
try { $transport->writeConfig($before['version'], Config::encode($initial)); throw new RuntimeException('missed conflict'); } catch (PrelandAdmin\Conflict $e) { check(true,'stale version conflicts'); }
check(count($store->backups('test')) === 1,'private backup saved');
$jobs = new Jobs($store);
$job = $jobs->create('apply', ['targets'=>[['id'=>'test','version'=>$after['version']],['id'=>'missing','version'=>'x']], 'changes'=>[['path'=>['custom','flag'],'value'=>false]]]);
$jobs->run($job['id']); $job=$jobs->get($job['id']);
check($job['results'][0]['status']==='success' && $job['results'][1]['status']==='error','one target failure does not undo successful target');
$restored=$transport->readConfig();
$backup=$store->backups('test')[0];
$job=$jobs->create('restore',['target'=>'test','id'=>$backup['id'],'version'=>$restored['version']]);$jobs->run($job['id']);
check($jobs->get($job['id'])['results'][0]['status']==='success','backup restores with version protection');
try { $store->saveTarget(['id'=>'escape','type'=>'local','root'=>'/etc','domain'=>'a.invalid']); throw new RuntimeException('unsafe root accepted'); } catch (PrelandAdmin\Problem $e) {check(true,'local root confined to explicit mounts');}
try { Config::patch($initial,[['path'=>['__proto__'],'value'=>1]]); throw new RuntimeException('unsafe key accepted'); } catch(PrelandAdmin\Problem $e) {check(true,'unsafe patch path rejected');}
echo "Backend contract complete\n";