Task: #53687 Support instance-specific cfg write targets and add coverage V2 #4

Merged
norb merged 11 commits from 53687/task-support-instance-specific-cfg-write-target-v2 into master 2026-03-30 15:09:21 +00:00
3 changed files with 324 additions and 268 deletions
Showing only changes of commit 227de9ac07 - Show all commits

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)
Alejandro Sosa 2026-03-26 11:28:58 +01:00

166
bin/cfg
View file

@ -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

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

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):

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

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.

@norb wrote in https://code.verua.online/rabe/Util-Settings/pulls/4#issuecomment-3732: > 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 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.
alejandro.sosa marked this conversation as resolved Outdated

can you do this as validators? e.g.

->validator(new Input\Validator(

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' ];
@ -206,17 +199,25 @@ $usage = Cli\manpage( basename(__FILE__), $version,
$collection, Colour::FG_GREEN, Colour::FG_WHITE,
[
'Examples' =>
'cfg write myCEESV \'auth:projectId="218523"\''
'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;
@ -275,49 +276,31 @@ 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

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);
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]));
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);
fwrite(STDERR, 'Option --siteDir is empty.'.PHP_EOL);
exit(1);
alejandro.sosa marked this conversation as resolved Outdated

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 --$optionName: '$siteInput'.".PHP_EOL);
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 --$optionName.".PHP_EOL);
fwrite(STDERR, "Invalid directory name segment '$part' in --siteDir.".PHP_EOL);
exit(1);
}
}
@ -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);
}
$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))
{

View file

@ -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"
);
}
@ -31,69 +31,154 @@ class CfgTest extends TestCase
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:projectId="218523"',
'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',
'myCEESV',
'auth:projectId="218523"',
'Extension',
'module:code="X100"',
]);
$this->assertSame(0, $result['code'], $result['output']);
$this->assertFileExists($this->tmpDir.'/config/myCEESV.conf.php');
$this->assertFileExists($this->tmpDir.'/config/Extension.conf.php');
}
public function testWriteWithDirectoryNameCreatesAndWritesSiteConfig(): void
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',
'Extension',
'--siteDir=owner_xyz',
'-i',
$inFile,
]);
$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']);
$this->assertSame('https://example.invalid/v1/resource', $cfg['feature']['endpoint']);
}
public function testWriteAcceptsJsonStringValueInSetting(): void
{
$result = $this->runCfg([
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:projectId="218523"',
'--directory-name',
'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',
'Extension',
'module:flags:enabled=true',
'--siteDir=owner_xyz',
]);
$this->assertSame(0, $result['code'], $result['output']);
$cfg = require $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
$this->assertTrue($cfg['module']['flags']['enabled']);
}
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/myCEESV.conf.php';
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
$this->assertFileExists($siteFile);
$this->assertFileDoesNotExist($this->tmpDir.'/config/myCEESV.conf.php');
$this->assertFileDoesNotExist($this->tmpDir.'/config/Extension.conf.php');
$cfg = require $siteFile;
$this->assertSame('218523', $cfg['auth']['projectId']);
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteWithDirectoryNameMergesIntoExistingSiteConfig(): void
public function testWriteWithSiteDirMergesIntoExistingSiteConfig(): void
{
$firstWrite = $this->runCfg([
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:projectId="218523"',
'--directory-name',
'Extension',
'module:code="X100"',
'--siteDir',
'owner_xyz',
]);
$this->assertSame(0, $firstWrite['code'], $firstWrite['output']);
@ -102,64 +187,49 @@ class CfgTest extends TestCase
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:clientId="service-9999-qual@myceesv.ch"',
'--directory-name',
'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',
'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',
'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
@ -168,9 +238,9 @@ class CfgTest extends TestCase
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:projectId="first"',
'--directory-name=owner_xyz',
'Extension',
'module:code="first"',
'--siteDir=owner_xyz',
]);
$this->assertSame(0, $firstWrite['code'], $firstWrite['output']);
@ -178,20 +248,20 @@ class CfgTest extends TestCase
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:projectId="second"',
'--directory-name=owner_xyz',
'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
@ -200,85 +270,67 @@ class CfgTest extends TestCase
'-a',
$this->tmpDir,
'write',
'myCEESV',
'auth:projectId="218523"',
'--directory-name=owner_xyz',
'Extension',
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$show = $this->runCfg([
'-a',
$this->tmpDir,
'show',
'myCEESV',
'auth:projectId',
'--site=owner_xyz',
'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',
'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',
'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=',
'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

View file

@ -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()