Task: #53687 Support instance-specific cfg write targets and add coverage V2 #4
3 changed files with 324 additions and 268 deletions
Task: #53687 Replace --site/--directory-name with --siteDir and add JSON batch input
- Changed site targeting from --site, --directory-name, --directoryName to --siteDir - Added write -i <json> for multi-setting input - Added support for inline JSON string values in write - Updated help/examples to generic placeholders - Extended tests for new arguments and validations (all passing)
commit
227de9ac07
220
bin/cfg
220
bin/cfg
|
|
@ -22,51 +22,50 @@ foreach ($autoloadFiles as $autoloadFile) {
|
|||||
}
|
||||||
}
|
||||||
|
||||||
// Pre-parse site options so they can be passed after positional args.
|
||||||
function readJsonInputFile(string $path): array
|
||||||
|
alejandro.sosa marked this conversation as resolved
Outdated
alejandro.sosa marked this conversation as resolved
Outdated
norb
commented
can you do this as validators? e.g.
can you do this as validators? e.g. https://code.verua.online/rabe/helpers-cli-input/src/commit/c6fe64321ef06633c89bc055252f22d6ff676d52/example/example.php#L35
|
||||||
{
|
||||||
if (!is_readable($path)) {
|
||||||
throw new \RuntimeException("Input file is not readable: $path");
|
||||||
}
|
||||||
|
||||||
$content = file_get_contents($path);
|
||||||
if ($content === false) {
|
||||||
throw new \RuntimeException("Can not read input file: $path");
|
||||||
}
|
||||||
|
||||||
try {
|
||||||
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
||||||
} catch (\JsonException $ex) {
|
||||||
throw new \RuntimeException(sprintf(
|
||||||
'Input JSON is invalid (%s): %s',
|
||||||
(string)$ex->getCode(),
|
||||||
$ex->getMessage()
|
||||||
));
|
||||||
}
|
||||||
|
||||||
if (!is_array($data)) {
|
||||||
throw new \RuntimeException('Input JSON must decode to an object/array');
|
||||||
}
|
||||||
|
||||||
return $data;
|
||||||
}
|
||||||
|
||||||
// Pre-parse siteDir so it can be passed after positional args.
|
||||||
// The input helper library does not handle this case reliably.
|
||||||
$preparsedSiteOptions = [];
|
||||||
$preparsedSiteDir = null;
|
||||||
$rawArgv = $_SERVER['argv'] ?? $GLOBALS['argv'] ?? null;
|
||||||
if (is_array($rawArgv) && !empty($rawArgv)) {
|
||||||
$filteredArgv = [$rawArgv[0]];
|
||||||
for ($idx = 1; $idx < count($rawArgv); $idx++) {
|
||||||
$arg = $rawArgv[$idx];
|
||||||
|
||||||
if ($arg === '-s' || $arg === '--site') {
|
||||||
$preparsedSiteOptions[] = [
|
||||||
'name' => 'site',
|
||||||
'value' => $rawArgv[$idx + 1] ?? '',
|
||||||
];
|
||||||
if ($arg === '--siteDir') {
|
||||||
$preparsedSiteDir = $rawArgv[$idx + 1] ?? '';
|
||||||
if (isset($rawArgv[$idx + 1])) $idx++;
|
||||||
continue;
|
||||||
}
|
||||||
if (str_starts_with($arg, '--site=')) {
|
||||||
$preparsedSiteOptions[] = [
|
||||||
'name' => 'site',
|
||||||
'value' => substr($arg, strlen('--site=')),
|
||||||
];
|
||||||
continue;
|
||||||
}
|
||||||
|
||||||
if ($arg === '--directory-name' || $arg === '--directoryName') {
|
||||||
$preparsedSiteOptions[] = [
|
||||||
'name' => 'directoryName',
|
||||||
'value' => $rawArgv[$idx + 1] ?? '',
|
||||||
];
|
||||||
if (isset($rawArgv[$idx + 1])) $idx++;
|
||||||
continue;
|
||||||
}
|
||||||
if (str_starts_with($arg, '--directory-name=')) {
|
||||||
$preparsedSiteOptions[] = [
|
||||||
'name' => 'directoryName',
|
||||||
'value' => substr($arg, strlen('--directory-name=')),
|
||||||
];
|
||||||
continue;
|
||||||
}
|
||||||
if (str_starts_with($arg, '--directoryName=')) {
|
||||||
$preparsedSiteOptions[] = [
|
||||||
'name' => 'directoryName',
|
||||||
'value' => substr($arg, strlen('--directoryName=')),
|
||||||
];
|
||||||
if (str_starts_with($arg, '--siteDir=')) {
|
||||||
$preparsedSiteDir = substr($arg, strlen('--siteDir='));
|
||||||
continue;
|
||||||
}
|
||||||
|
||||||
|
|
@ -92,12 +91,7 @@ $collection = (new Input\InputCollection())
|
|||||
|
||||||
->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{
|
||||||
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
|
||||||
->description('Path to a json data file to to read')
|
||||||
) // }}}
|
||||||
|
||||||
->add( Input\InputTypeFactory::build('LongOption')->name('out')->short('o') // {{{
|
||||||
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
|
||||||
->description('Path to a json file to to write to')
|
||||||
->description('Path to a JSON data file to read (for write)')
|
||||||
) // }}}
|
||||||
|
||||||
->add( Input\InputTypeFactory::build('LongOption')->name('mode')->short('m') // {{{
|
||||||
|
|
@ -115,14 +109,9 @@ $collection = (new Input\InputCollection())
|
|||||
->description('Path where the config/ directory of the package conf files is located, defaults to the working dir')
|
||||||
) // }}}
|
||||||
|
||||||
->add( Input\InputTypeFactory::build('LongOption')->name('site')->short('s') // {{{
|
||||||
->add( Input\InputTypeFactory::build('LongOption')->name('siteDir') // {{{
|
||||||
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
|
||||||
->description('Site/instance directory below config/ (target: config/<site>/<prefix>.conf.php), e.g. owner_xyz')
|
||||||
) // }}}
|
||||||
|
||||||
->add( Input\InputTypeFactory::build('LongOption')->name('directoryName') // {{{
|
||||||
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
|
||||||
->description('Alias for --site. Accepts owner_xyz or config/owner_xyz (also supports --directory-name)')
|
||||||
->description('Site/instance directory below config/. Accepts owner_xyz or config/owner_xyz')
|
||||||
) // }}}
|
||||||
|
||||||
->add( Input\InputTypeFactory::build('Argument')->name('action') // {{{
|
||||||
|
|
@ -173,12 +162,16 @@ $collection = (new Input\InputCollection())
|
|||||
$setting = $context->find('setting');
|
||||||
$action = $context->find('action');
|
||||||
|
||||||
if ($setting === null || $setting === '') {
|
||||||
return ['key' => '', 'value' => null];
|
||||||
}
|
||||||
|
||||||
$setting = explode('=', $setting);
|
||||||
$settings['key'] = $setting[0];
|
||||||
if ($action === 'write')
|
||||||
{
|
||||||
$value = $setting[1];
|
||||||
if (! (isset($value) || $context->find('data'))) {
|
||||||
$value = $setting[1] ?? null;
|
||||||
if (! (isset($value) || $context->find('in'))) {
|
||||||
throw new \Exception('You need a value to write');
|
||||||
}
|
||||||
$specialValues = [ 'true', 'false', 'null' ];
|
||||||
|
|
@ -204,21 +197,29 @@ $collection = (new Input\InputCollection())
|
|||||
$usage = Cli\manpage( basename(__FILE__), $version,
|
||||||
'read write settings',
|
||||||
$collection, Colour::FG_GREEN, Colour::FG_WHITE,
|
||||||
[
|
||||||
'Examples' =>
|
||||||
'cfg write myCEESV \'auth:projectId="218523"\''
|
||||||
[
|
||||||
'Examples' =>
|
||||||
'cfg write extension_name \'module:enabled=true\''
|
||||||
.PHP_EOL
|
||||||
.'# writes local config: config/myCEESV.conf.php'
|
||||||
.'# writes local config: config/extension_name.conf.php'
|
||||||
.PHP_EOL
|
||||||
.'cfg write myCEESV \'auth:projectId="218523"\' --directory-name=owner_xyz'
|
||||||
.'cfg write extension_name -i /tmp/extension.json --siteDir=owner_xyz'
|
||||||
.PHP_EOL
|
||||||
.'# writes instance config: config/owner_xyz/myCEESV.conf.php'
|
||||||
.'# writes multiple settings from JSON to config/owner_xyz/extension_name.conf.php'
|
||||||
.PHP_EOL
|
||||||
.'cfg show myCEESV auth:projectId --site=owner_xyz'
|
||||||
.'cfg write extension_name \'feature_example:enabled=true\' --siteDir=owner_xyz'
|
||||||
.PHP_EOL
|
||||||
.'# reads merged config including config/owner_xyz/myCEESV.conf.php'
|
||||||
.'# writes instance config: config/owner_xyz/extension_name.conf.php'
|
||||||
.PHP_EOL
|
||||||
]
|
||||||
.'cfg write extension_name \'feature_example={"enabled":true,"timeout":30,"label":"example"}\' --siteDir=owner_xyz'
|
||||||
.PHP_EOL
|
||||||
.'# writes multiple keys from one JSON string'
|
||||||
.PHP_EOL
|
||||||
.'cfg show extension_name module:enabled --siteDir=owner_xyz'
|
||||||
.PHP_EOL
|
||||||
.'# reads merged config including config/owner_xyz/extension_name.conf.php'
|
||||||
.PHP_EOL
|
||||||
]
|
||||||
).PHP_EOL;
|
||||||
|
||||||
// Get the supplied input. Passing the collection will make the handler bind values
|
||||||
|
|
@ -275,56 +276,38 @@ if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath(rtrim($pkgPath, '/').'/');
|
|||||
|
||||||
$site = null;
|
||||||
$siteFlag = 0x01;
|
||||||
// Only one site selector may be provided to avoid ambiguous targets.
|
||||||
$providedSiteOptions = [];
|
||||||
foreach ($preparsedSiteOptions as $option) {
|
||||||
$providedSiteOptions[$option['name']][] = $option['value'] ?? '';
|
||||||
}
|
||||||
foreach (['site', 'directoryName'] as $optionName) {
|
||||||
$parsedValue = $argv->find($optionName);
|
||||||
if ($parsedValue !== null && $parsedValue !== '' && $parsedValue !== false) {
|
||||||
$providedSiteOptions[$optionName][] = $parsedValue;
|
||||||
$siteInput = ($preparsedSiteDir !== null) ? $preparsedSiteDir : $argv->find('siteDir');
|
||||||
if ($siteInput !== null && $siteInput !== false) {
|
||||||
|
alejandro.sosa marked this conversation as resolved
Outdated
norb
commented
our coding style would always put the opening curly bracket on a newline. I retain it specially important if the following codeblock is longer than a couple of lines our coding style would always put the opening curly bracket on a newline. I retain it specially important if the following codeblock is longer than a couple of lines
|
||||||
$siteInput = trim((string)$siteInput);
|
||||||
|
||||||
// Accept both "owner_xyz" and "config/owner_xyz" and normalize to site key.
|
||||||
if (str_starts_with($siteInput, $appPath.'config/')) {
|
||||||
$siteInput = substr($siteInput, strlen($appPath.'config/'));
|
||||||
} elseif (str_starts_with($siteInput, 'config/')) {
|
||||||
$siteInput = substr($siteInput, strlen('config/'));
|
||||||
}
|
||||||
}
|
||||||
|
||||||
if (count($providedSiteOptions) > 1) {
|
||||||
fwrite(STDERR, 'Please use only one of --site or --directoryName (alias: --directory-name).'.PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
if (!empty($providedSiteOptions)) {
|
||||||
$optionName = array_key_first($providedSiteOptions);
|
||||||
$siteInput = trim((string)end($providedSiteOptions[$optionName]));
|
||||||
$siteInput = trim($siteInput, '/');
|
||||||
if ($siteInput === '') {
|
||||||
fwrite(STDERR, 'Option --siteDir is empty.'.PHP_EOL);
|
||||||
exit(1);
|
||||||
|
alejandro.sosa marked this conversation as resolved
Outdated
norb
commented
probably most of this should be done in the validator as well probably most of this should be done in the validator as well
|
||||||
}
|
||||||
// Block directory traversal and hidden-dot segments.
|
||||||
if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput)) {
|
||||||
fwrite(STDERR, "Invalid directory in --siteDir: '$siteInput'.".PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
// Allow only predictable path segments for instance directories.
|
||||||
foreach (explode('/', $siteInput) as $part) {
|
||||||
if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part)) {
|
||||||
fwrite(STDERR, "Invalid directory name segment '$part' in --siteDir.".PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
}
|
||||||
|
||||||
if ($optionName === 'directoryName') {
|
||||||
// Accept both "owner_xyz" and "config/owner_xyz" and normalize to site key.
|
||||||
if (str_starts_with($siteInput, $appPath.'config/')) {
|
||||||
$siteInput = substr($siteInput, strlen($appPath.'config/'));
|
||||||
} elseif (str_starts_with($siteInput, 'config/')) {
|
||||||
$siteInput = substr($siteInput, strlen('config/'));
|
||||||
}
|
||||||
}
|
||||||
|
||||||
$siteInput = trim($siteInput, '/');
|
||||||
if ($siteInput === '') {
|
||||||
fwrite(STDERR, "Option --$optionName is empty.".PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
// Block directory traversal and hidden-dot segments.
|
||||||
if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput)) {
|
||||||
fwrite(STDERR, "Invalid directory in --$optionName: '$siteInput'.".PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
// Allow only predictable path segments for instance directories.
|
||||||
foreach (explode('/', $siteInput) as $part) {
|
||||||
if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part)) {
|
||||||
fwrite(STDERR, "Invalid directory name segment '$part' in --$optionName.".PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
}
|
||||||
|
||||||
$site = $siteInput;
|
||||||
// Reuse util-settings site resolution: config/<site>/<prefix>.conf.php
|
||||||
$cfg->site($site);
|
||||||
$site = $siteInput;
|
||||||
// Reuse util-settings site resolution: config/<site>/<prefix>.conf.php
|
||||||
$cfg->site($site);
|
||||||
}
|
||||||
|
||||||
try {
|
||||||
|
|
@ -368,9 +351,30 @@ case 'show':
|
|||||
break;
|
||||||
|
||||||
case 'write':
|
||||||
$path = ($settings['key'] !== '') ? explode(':', $settings['key']) : [];
|
||||||
$inputFile = $argv->find('in');
|
||||||
// Write needs exactly one payload source:
|
||||||
// either SETTING (key=value) or --in (JSON file).
|
||||||
// This avoids ambiguous input precedence and empty writes.
|
||||||
if ($inputFile && $settings['key'] !== '') {
|
||||||
fwrite(STDERR, 'Please use either SETTING or --in, not both.'.PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
if (!$inputFile && $settings['key'] === '') {
|
||||||
fwrite(STDERR, 'Nothing to write: provide SETTING or --in.'.PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
|
||||||
$setting2write = $settings['value'];
|
||||||
$path = ($settings['key'] !== '') ? explode(':', $settings['key']) : [];
|
||||||
if ($inputFile) {
|
||||||
try {
|
||||||
$setting2write = readJsonInputFile($inputFile);
|
||||||
} catch (\Throwable $e) {
|
||||||
fwrite(STDERR, $e->getMessage().PHP_EOL);
|
||||||
exit(1);
|
||||||
}
|
||||||
} else {
|
||||||
$setting2write = $settings['value'];
|
||||||
}
|
||||||
|
||||||
while ( ! empty($path))
|
||||||
{
|
||||||
|
|
|
|||||
|
|
@ -15,8 +15,8 @@ class CfgTest extends TestCase
|
|||
mkdir($this->tmpDir.'/config', 0775, true);
|
||||
|
||||
file_put_contents(
|
||||
$this->tmpDir.'/config/myCEESV.default.conf.php',
|
||||
"<?php return [\n\t'mode' => 'prod',\n\t'auth' => [\n\t\t'projectId' => '',\n\t\t'clientId' => '',\n\t],\n];\n"
|
||||
$this->tmpDir.'/config/Extension.default.conf.php',
|
||||
"<?php return [\n\t'mode' => 'prod',\n\t'module' => [\n\t\t'code' => '',\n\t\t'label' => '',\n\t\t'flags' => [\n\t\t\t'enabled' => false,\n\t\t],\n\t],\n\t'feature' => [\n\t\t'endpoint' => '',\n\t],\n];\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -28,257 +28,309 @@ class CfgTest extends TestCase
|
|||
public function testWriteWithoutDirectoryNameUsesLocalConfigFile(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
|
||||
$localFile = $this->tmpDir.'/config/myCEESV.conf.php';
|
||||
$localFile = $this->tmpDir.'/config/Extension.conf.php';
|
||||
$this->assertFileExists($localFile);
|
||||
|
||||
$cfg = require $localFile;
|
||||
$this->assertSame('218523', $cfg['auth']['projectId']);
|
||||
$this->assertSame('X100', $cfg['module']['code']);
|
||||
}
|
||||
|
||||
public function testWriteWithoutModeFallsBackToDefaultMode(): void
|
||||
{
|
||||
file_put_contents(
|
||||
$this->tmpDir.'/config/myCEESV.default.conf.php',
|
||||
"<?php return [\n\t'auth' => [\n\t\t'projectId' => '',\n\t],\n];\n"
|
||||
$this->tmpDir.'/config/Extension.default.conf.php',
|
||||
"<?php return [\n\t'module' => [\n\t\t'code' => '',\n\t],\n];\n"
|
||||
);
|
||||
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
$this->assertFileExists($this->tmpDir.'/config/Extension.conf.php');
|
||||
}
|
||||
|
||||
public function testWriteWithInputFileWritesMultipleSettings(): void
|
||||
{
|
||||
$inFile = $this->tmpDir.'/extension-in.json';
|
||||
file_put_contents(
|
||||
$inFile,
|
||||
json_encode([
|
||||
'module' => [
|
||||
'code' => 'X100',
|
||||
'label' => 'demo-module',
|
||||
],
|
||||
'feature' => [
|
||||
'endpoint' => 'https://example.invalid/v1/resource',
|
||||
],
|
||||
], JSON_PRETTY_PRINT)
|
||||
);
|
||||
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'Extension',
|
||||
'--siteDir=owner_xyz',
|
||||
'-i',
|
||||
$inFile,
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
$this->assertFileExists($this->tmpDir.'/config/myCEESV.conf.php');
|
||||
$cfg = require $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
|
||||
$this->assertSame('X100', $cfg['module']['code']);
|
||||
$this->assertSame('demo-module', $cfg['module']['label']);
|
||||
$this->assertSame('https://example.invalid/v1/resource', $cfg['feature']['endpoint']);
|
||||
}
|
||||
|
||||
public function testWriteWithDirectoryNameCreatesAndWritesSiteConfig(): void
|
||||
public function testWriteAcceptsJsonStringValueInSetting(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module={"code":"X100","label":"demo-module"}',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
$cfg = require $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
|
||||
$this->assertSame('X100', $cfg['module']['code']);
|
||||
$this->assertSame('demo-module', $cfg['module']['label']);
|
||||
}
|
||||
|
||||
public function testWriteSupportsNestedPathWithColonNotation(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name',
|
||||
'owner_xyz',
|
||||
'Extension',
|
||||
'module:flags:enabled=true',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
|
||||
$siteFile = $this->tmpDir.'/config/owner_xyz/myCEESV.conf.php';
|
||||
$this->assertFileExists($siteFile);
|
||||
$this->assertFileDoesNotExist($this->tmpDir.'/config/myCEESV.conf.php');
|
||||
|
||||
$cfg = require $siteFile;
|
||||
$this->assertSame('218523', $cfg['auth']['projectId']);
|
||||
$cfg = require $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
|
||||
$this->assertTrue($cfg['module']['flags']['enabled']);
|
||||
}
|
||||
|
||||
public function testWriteWithDirectoryNameMergesIntoExistingSiteConfig(): void
|
||||
public function testWriteWithInputFileAndSettingReturnsError(): void
|
||||
{
|
||||
$inFile = $this->tmpDir.'/extension-in.json';
|
||||
file_put_contents($inFile, json_encode(['module' => ['code' => 'X100']]));
|
||||
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="x"',
|
||||
'-i',
|
||||
$inFile,
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['code']);
|
||||
$this->assertStringContainsString('Please use either SETTING or --in, not both.', $result['output']);
|
||||
}
|
||||
|
||||
public function testWriteWithSiteDirCreatesAndWritesSiteConfig(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir',
|
||||
'owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
|
||||
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
|
||||
$this->assertFileExists($siteFile);
|
||||
$this->assertFileDoesNotExist($this->tmpDir.'/config/Extension.conf.php');
|
||||
|
||||
$cfg = require $siteFile;
|
||||
$this->assertSame('X100', $cfg['module']['code']);
|
||||
}
|
||||
|
||||
public function testWriteWithSiteDirMergesIntoExistingSiteConfig(): void
|
||||
{
|
||||
$firstWrite = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name',
|
||||
'owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir',
|
||||
'owner_xyz',
|
||||
]);
|
||||
$this->assertSame(0, $firstWrite['code'], $firstWrite['output']);
|
||||
|
||||
$secondWrite = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:clientId="service-9999-qual@myceesv.ch"',
|
||||
'--directory-name',
|
||||
'owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:label="demo-module"',
|
||||
'--siteDir',
|
||||
'owner_xyz',
|
||||
]);
|
||||
$this->assertSame(0, $secondWrite['code'], $secondWrite['output']);
|
||||
|
||||
$siteFile = $this->tmpDir.'/config/owner_xyz/myCEESV.conf.php';
|
||||
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
|
||||
$cfg = require $siteFile;
|
||||
|
||||
$this->assertSame('218523', $cfg['auth']['projectId']);
|
||||
$this->assertSame('service-9999-qual@myceesv.ch', $cfg['auth']['clientId']);
|
||||
$this->assertSame('X100', $cfg['module']['code']);
|
||||
$this->assertSame('demo-module', $cfg['module']['label']);
|
||||
}
|
||||
|
||||
public function testWriteWithDirectoryNameUsingConfigPrefixIsNormalized(): void
|
||||
public function testWriteWithSiteDirUsingConfigPrefixIsNormalized(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name=config/owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir=config/owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/myCEESV.conf.php');
|
||||
$this->assertFileDoesNotExist($this->tmpDir.'/config/config/owner_xyz/myCEESV.conf.php');
|
||||
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
|
||||
$this->assertFileDoesNotExist($this->tmpDir.'/config/config/owner_xyz/Extension.conf.php');
|
||||
}
|
||||
|
||||
public function testWriteWithSiteOptionWritesToSiteConfig(): void
|
||||
public function testWriteWithSiteDirWritesToSiteConfig(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--site=owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/myCEESV.conf.php');
|
||||
}
|
||||
|
||||
public function testWriteWithDirectoryNameCamelCaseOptionWritesToSiteConfig(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directoryName=owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $result['code'], $result['output']);
|
||||
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/myCEESV.conf.php');
|
||||
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
|
||||
}
|
||||
|
||||
public function testWriteToExistingSiteConfigCreatesBackup(): void
|
||||
{
|
||||
$firstWrite = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="first"',
|
||||
'--directory-name=owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="first"',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
$this->assertSame(0, $firstWrite['code'], $firstWrite['output']);
|
||||
|
||||
$secondWrite = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="second"',
|
||||
'--directory-name=owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="second"',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
$this->assertSame(0, $secondWrite['code'], $secondWrite['output']);
|
||||
|
||||
$siteFile = $this->tmpDir.'/config/owner_xyz/myCEESV.conf.php';
|
||||
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
|
||||
$backupFile = $siteFile.'.bak';
|
||||
$this->assertFileExists($backupFile);
|
||||
|
||||
$current = require $siteFile;
|
||||
$backup = require $backupFile;
|
||||
$this->assertSame('second', $current['auth']['projectId']);
|
||||
$this->assertSame('first', $backup['auth']['projectId']);
|
||||
$this->assertSame('second', $current['module']['code']);
|
||||
$this->assertSame('first', $backup['module']['code']);
|
||||
}
|
||||
|
||||
public function testShowWithSiteReturnsSiteSpecificValue(): void
|
||||
{
|
||||
$this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name=owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
|
||||
$show = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'show',
|
||||
'myCEESV',
|
||||
'auth:projectId',
|
||||
'--site=owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'show',
|
||||
'Extension',
|
||||
'module:code',
|
||||
'--siteDir=owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(0, $show['code'], $show['output']);
|
||||
$this->assertStringContainsString('218523', $show['output']);
|
||||
$this->assertStringContainsString('X100', $show['output']);
|
||||
}
|
||||
|
||||
public function testDirectoryNameRejectsTraversal(): void
|
||||
public function testSiteDirRejectsTraversal(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name=../owner_xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir=../owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['code']);
|
||||
$this->assertStringContainsString('Invalid directory in --directoryName', $result['output']);
|
||||
$this->assertStringContainsString('Invalid directory in --siteDir', $result['output']);
|
||||
}
|
||||
|
||||
public function testDirectoryNameRejectsInvalidSegment(): void
|
||||
public function testSiteDirRejectsInvalidSegment(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name=owner xyz',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir=owner xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['code']);
|
||||
$this->assertStringContainsString("Invalid directory name segment 'owner xyz' in --directoryName.", $result['output']);
|
||||
$this->assertStringContainsString("Invalid directory name segment 'owner xyz' in --siteDir.", $result['output']);
|
||||
}
|
||||
|
||||
public function testDirectoryNameRejectsEmptyValue(): void
|
||||
public function testSiteDirRejectsEmptyValue(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--directory-name=',
|
||||
]);
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'Extension',
|
||||
'module:code="X100"',
|
||||
'--siteDir=',
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['code']);
|
||||
$this->assertStringContainsString('Option --directoryName is empty.', $result['output']);
|
||||
}
|
||||
|
||||
public function testUsingSiteAndDirectoryNameTogetherReturnsError(): void
|
||||
{
|
||||
$result = $this->runCfg([
|
||||
'-a',
|
||||
$this->tmpDir,
|
||||
'write',
|
||||
'myCEESV',
|
||||
'auth:projectId="218523"',
|
||||
'--site',
|
||||
'owner_xyz',
|
||||
'--directory-name',
|
||||
'owner_xyz',
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['code']);
|
||||
$this->assertStringContainsString('Please use only one of --site or --directoryName (alias: --directory-name).', $result['output']);
|
||||
$this->assertStringContainsString('Option --siteDir is empty.', $result['output']);
|
||||
}
|
||||
|
||||
private function runCfg(array $args): array
|
||||
|
|
|
|||
|
|
@ -224,17 +224,17 @@ class SettingsTest extends TestCase
|
|||
public function testBuildFileNameWithPrefixAndSite(): void
|
||||
{
|
||||
$cfg = new Settings();
|
||||
$cfg->appPath('./')->prefix('myCEESV')->site('owner_xyz');
|
||||
$cfg->appPath('./')->prefix('Extension')->site('owner_xyz');
|
||||
|
||||
$this->assertEquals('./config/owner_xyz/myCEESV.conf.php', $cfg->buildFileName(0x01));
|
||||
$this->assertEquals('./config/owner_xyz/Extension.conf.php', $cfg->buildFileName(0x01));
|
||||
}
|
||||
|
||||
public function testBuildFileNameWithPrefixAndNestedSite(): void
|
||||
{
|
||||
$cfg = new Settings();
|
||||
$cfg->appPath('./')->prefix('myCEESV')->site('owner_xyz/sub_a');
|
||||
$cfg->appPath('./')->prefix('Extension')->site('owner_xyz/sub_a');
|
||||
|
||||
$this->assertEquals('./config/owner_xyz/sub_a/myCEESV.conf.php', $cfg->buildFileName(0x01));
|
||||
$this->assertEquals('./config/owner_xyz/sub_a/Extension.conf.php', $cfg->buildFileName(0x01));
|
||||
}
|
||||
|
||||
public function fileNameData()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue
I don't really like this .. seems over complicating the code. It is just for beging able to have different order of arguments?
not that important to me .. and probably better put inside the input helper library or replace it altogether .. IIRC the one used is not maintained anymore
@norb wrote in #4 (comment):
You're right. After reviewing it again with the alias cleanup in place, I realized we can keep this much simpler. The pre-parse block was added as a defensive workaround while multiple option variants were still in play, but with --siteDir now unified and a couple of small follow-up fixes, the standard parser flow is sufficient. I removed that extra layer to keep the code cleaner and easier to maintain.