refactor(console, id, config): Dialog mode in Console, consolidated id modul, added config support for ini directives

This commit is contained in:
2025-11-04 13:44:27 +01:00
parent 980714f656
commit bfce93ce77
110 changed files with 2828 additions and 774 deletions

View File

@@ -12,7 +12,7 @@ use App\Framework\Http\Exception\NotFound;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\MimeType;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Ulid\Ulid;
use App\Framework\Id\Ulid\Ulid;
beforeEach(function () {
$this->imageRepository = Mockery::mock(ImageRepository::class);

View File

@@ -8,7 +8,7 @@ use App\Framework\Core\ValueObjects\Hash;
use App\Framework\DateTime\SystemClock;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Http\MimeType;
use App\Framework\Ulid\Ulid;
use App\Framework\Id\Ulid\Ulid;
beforeEach(function () {
// Create test directory structure

View File

@@ -13,8 +13,8 @@ use App\Framework\Exception\FrameworkException;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\MimeType;
use App\Framework\Id\Ulid\Ulid;
use App\Framework\Router\Result\FileResult;
use App\Framework\Ulid\Ulid;
beforeEach(function () {
// Create test directory and file

View File

@@ -2,8 +2,8 @@
declare(strict_types=1);
use App\Framework\Id\Ulid\Ulid;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Ulid\Ulid;
describe('JobId Value Object', function () {

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
use App\Framework\Core\System\Ini\Access;
describe('Access', function () {
it('converts INI_USER bitmask to USER enum', function () {
$access = Access::fromBitmask(INI_USER);
expect($access)->toBe(Access::USER);
});
it('converts INI_PERDIR bitmask to PERDIR enum', function () {
$access = Access::fromBitmask(INI_PERDIR);
expect($access)->toBe(Access::PERDIR);
});
it('converts INI_SYSTEM bitmask to SYSTEM enum', function () {
$access = Access::fromBitmask(INI_SYSTEM);
expect($access)->toBe(Access::SYSTEM);
});
it('converts INI_ALL bitmask to ALL enum', function () {
$access = Access::fromBitmask(INI_ALL);
expect($access)->toBe(Access::ALL);
});
it('throws exception for invalid bitmask', function () {
expect(fn () => Access::fromBitmask(999))
->toThrow(InvalidArgumentException::class, 'Invalid bitmask value: 999');
});
it('has correct string values', function () {
expect(Access::USER->value)->toBe('USER');
expect(Access::PERDIR->value)->toBe('Per Directory');
expect(Access::SYSTEM->value)->toBe('System');
expect(Access::ALL->value)->toBe('All');
});
});

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
use App\Framework\Core\System\Ini\Access;
use App\Framework\Core\System\Ini\IniDirective;
describe('IniDirective', function () {
it('returns Access enum from getAccess()', function () {
$directive = new IniDirective(
name: 'test_directive',
value: 'test_value',
global: 'global_value',
accessMask: INI_USER
);
$access = $directive->getAccess();
expect($access)->toBeInstanceOf(Access::class);
expect($access)->toBe(Access::USER);
});
it('returns access mask from getAccessMask()', function () {
$directive = new IniDirective(
name: 'test_directive',
value: 'test_value',
global: 'global_value',
accessMask: INI_USER
);
$mask = $directive->getAccessMask();
expect($mask)->toBeInt();
expect($mask)->toBe(INI_USER);
});
it('correctly converts INI_USER bitmask', function () {
$directive = new IniDirective(
name: 'test_directive',
value: 'test_value',
global: 'global_value',
accessMask: INI_USER
);
expect($directive->getAccess())->toBe(Access::USER);
});
it('correctly converts INI_PERDIR bitmask', function () {
$directive = new IniDirective(
name: 'test_directive',
value: 'test_value',
global: 'global_value',
accessMask: INI_PERDIR
);
expect($directive->getAccess())->toBe(Access::PERDIR);
});
it('correctly converts INI_SYSTEM bitmask', function () {
$directive = new IniDirective(
name: 'test_directive',
value: 'test_value',
global: 'global_value',
accessMask: INI_SYSTEM
);
expect($directive->getAccess())->toBe(Access::SYSTEM);
});
it('correctly converts INI_ALL bitmask', function () {
$directive = new IniDirective(
name: 'test_directive',
value: 'test_value',
global: 'global_value',
accessMask: INI_ALL
);
expect($directive->getAccess())->toBe(Access::ALL);
});
it('stores and returns all properties correctly', function () {
$directive = new IniDirective(
name: 'my_directive',
value: 'my_value',
global: 'global_value',
accessMask: INI_USER
);
expect($directive->name)->toBe('my_directive');
expect($directive->value)->toBe('my_value');
expect($directive->global)->toBe('global_value');
expect($directive->getAccessMask())->toBe(INI_USER);
});
});

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
use App\Framework\Core\System\Ini\IniKey;
describe('IniKey', function () {
it('has correct string value for MEMORY_LIMIT', function () {
expect(IniKey::MEMORY_LIMIT->value)->toBe('memory_limit');
});
it('has correct string value for MAX_EXECUTION_TIME', function () {
expect(IniKey::MAX_EXECUTION_TIME->value)->toBe('max_execution_time');
});
it('has correct string value for UPLOAD_MAX_FILESIZE', function () {
expect(IniKey::UPLOAD_MAX_FILESIZE->value)->toBe('upload_max_filesize');
});
it('has correct string value for POST_MAX_SIZE', function () {
expect(IniKey::POST_MAX_SIZE->value)->toBe('post_max_size');
});
it('has correct string value for DISPLAY_ERRORS', function () {
expect(IniKey::DISPLAY_ERRORS->value)->toBe('display_errors');
});
it('has correct string value for LOG_ERRORS', function () {
expect(IniKey::LOG_ERRORS->value)->toBe('log_errors');
});
it('has correct string value for DATE_TIMEZONE', function () {
expect(IniKey::DATE_TIMEZONE->value)->toBe('date.timezone');
});
it('has correct string value for ERROR_REPORTING', function () {
expect(IniKey::ERROR_REPORTING->value)->toBe('error_reporting');
});
it('has correct string value for ERROR_LOG', function () {
expect(IniKey::ERROR_LOG->value)->toBe('error_log');
});
it('has all expected commonly used keys', function () {
$expectedKeys = [
'memory_limit',
'max_execution_time',
'max_input_time',
'upload_max_filesize',
'post_max_size',
'max_file_uploads',
'display_errors',
'display_startup_errors',
'error_log',
'date.timezone',
'log_errors',
'error_reporting',
];
$actualValues = array_map(
fn (IniKey $key) => $key->value,
IniKey::cases()
);
foreach ($expectedKeys as $expectedKey) {
expect($actualValues)->toContain($expectedKey);
}
});
});

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
use App\Framework\Core\System\Ini\Access;
use App\Framework\Core\System\Ini\IniDirective;
use App\Framework\Core\System\Ini\IniKey;
use App\Framework\Core\System\Ini\IniManager;
describe('IniManager', function () {
it('gets ini value by IniKey enum', function () {
$manager = new IniManager();
$value = $manager->get(IniKey::MEMORY_LIMIT);
expect($value)->not->toBeNull();
expect($value)->toBeString();
});
it('gets ini value by string key', function () {
$manager = new IniManager();
$value = $manager->get('memory_limit');
expect($value)->not->toBeNull();
expect($value)->toBeString();
});
it('returns null for non-existent key', function () {
$manager = new IniManager();
$value = $manager->get('non_existent_ini_key_12345');
expect($value)->toBeNull();
});
it('sets ini value for modifiable key', function () {
$manager = new IniManager();
// Get original value
$original = $manager->get(IniKey::DISPLAY_ERRORS);
// Set a new value
$result = $manager->set(IniKey::DISPLAY_ERRORS, '1');
// Restore original value
if ($original !== null) {
$manager->set(IniKey::DISPLAY_ERRORS, $original);
}
expect($result)->toBeTrue();
});
it('gets directive object with access information', function () {
$manager = new IniManager();
$directive = $manager->getDirective(IniKey::MEMORY_LIMIT);
expect($directive)->toBeInstanceOf(IniDirective::class);
expect($directive->name)->toBe('memory_limit');
expect($directive->value)->not->toBeEmpty();
expect($directive->getAccess())->toBeInstanceOf(Access::class);
});
it('returns null for non-existent directive', function () {
$manager = new IniManager();
// Using a key that likely doesn't exist
$directive = $manager->getDirective(IniKey::ALLOW_URL_INCLUDE);
// This might return null if the directive is not available
// or return an IniDirective if it exists
if ($directive === null) {
expect($directive)->toBeNull();
} else {
expect($directive)->toBeInstanceOf(IniDirective::class);
}
});
it('gets all ini directives', function () {
$manager = new IniManager();
$all = $manager->getAll();
expect($all)->toBeArray();
expect($all)->not->toBeEmpty();
// Check that all values are IniDirective objects
foreach ($all as $name => $directive) {
expect($name)->toBeString();
expect($directive)->toBeInstanceOf(IniDirective::class);
}
});
it('filters directives by access level', function () {
$manager = new IniManager();
$userDirectives = $manager->getAllByAccess(Access::USER);
expect($userDirectives)->toBeArray();
// Verify all returned directives have the correct access level
foreach ($userDirectives as $directive) {
expect($directive->getAccess())->toBe(Access::USER);
}
});
it('checks if directive is modifiable', function () {
$manager = new IniManager();
// DISPLAY_ERRORS is typically INI_USER and should be modifiable
$isModifiable = $manager->isModifiable(IniKey::DISPLAY_ERRORS);
expect($isModifiable)->toBeBool();
});
it('returns false for non-modifiable directive', function () {
$manager = new IniManager();
// Some directives like ENGINE are typically INI_SYSTEM and not modifiable
// MEMORY_LIMIT might be INI_SYSTEM depending on configuration
$directive = $manager->getDirective(IniKey::MEMORY_LIMIT);
if ($directive !== null) {
$isModifiable = $manager->isModifiable(IniKey::MEMORY_LIMIT);
// If it's SYSTEM or PERDIR, it should not be modifiable
if ($directive->getAccess() === Access::SYSTEM || $directive->getAccess() === Access::PERDIR) {
expect($isModifiable)->toBeFalse();
}
}
});
it('handles enum and string keys consistently', function () {
$manager = new IniManager();
$valueFromEnum = $manager->get(IniKey::MEMORY_LIMIT);
$valueFromString = $manager->get('memory_limit');
expect($valueFromEnum)->toBe($valueFromString);
});
it('updates cached directive after set', function () {
$manager = new IniManager();
// Get original value
$original = $manager->get(IniKey::DISPLAY_ERRORS);
$originalDirective = $manager->getDirective(IniKey::DISPLAY_ERRORS);
if ($originalDirective === null || $original === null) {
return; // Skip if directive doesn't exist
}
// Set a new value
$newValue = '1';
$result = $manager->set(IniKey::DISPLAY_ERRORS, $newValue);
expect($result)->toBeTrue();
// Verify cached value is updated
$updatedValue = $manager->get(IniKey::DISPLAY_ERRORS);
$updatedDirective = $manager->getDirective(IniKey::DISPLAY_ERRORS);
expect($updatedValue)->toBe($newValue);
expect($updatedDirective)->not->toBeNull();
expect($updatedDirective->value)->toBe($newValue);
// Restore original value
$manager->set(IniKey::DISPLAY_ERRORS, $original);
});
});

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
use App\Framework\Config\Environment;
use App\Framework\Core\System\Ini\IniKey;
use App\Framework\Core\System\Ini\IniManager;
use App\Framework\Core\System\SystemConfig;
describe('SystemConfig', function () {
it('provides access to ini manager via property', function () {
$iniManager = new IniManager();
$environment = new Environment();
$config = new SystemConfig($iniManager, $environment);
expect($config->ini)->toBeInstanceOf(IniManager::class);
expect($config->ini)->toBe($iniManager);
});
it('provides access to environment via property', function () {
$iniManager = new IniManager();
$environment = new Environment();
$config = new SystemConfig($iniManager, $environment);
expect($config->environment)->toBeInstanceOf(Environment::class);
expect($config->environment)->toBe($environment);
});
it('allows accessing ini values through ini property', function () {
$iniManager = new IniManager();
$environment = new Environment();
$config = new SystemConfig($iniManager, $environment);
$value = $config->ini->get(IniKey::MEMORY_LIMIT);
expect($value)->not->toBeNull();
expect($value)->toBeString();
});
it('allows accessing ini values by string key', function () {
$iniManager = new IniManager();
$environment = new Environment();
$config = new SystemConfig($iniManager, $environment);
$value = $config->ini->get('memory_limit');
expect($value)->not->toBeNull();
expect($value)->toBeString();
});
it('allows accessing environment variables through environment property', function () {
$iniManager = new IniManager();
$environment = new Environment(['TEST_KEY' => 'test_value']);
$config = new SystemConfig($iniManager, $environment);
$value = $config->environment->get('TEST_KEY');
expect($value)->toBe('test_value');
});
it('allows accessing all ini manager methods', function () {
$iniManager = new IniManager();
$environment = new Environment();
$config = new SystemConfig($iniManager, $environment);
// Test getDirective
$directive = $config->ini->getDirective(IniKey::MEMORY_LIMIT);
expect($directive)->not->toBeNull();
// Test getAll
$all = $config->ini->getAll();
expect($all)->toBeArray();
expect($all)->not->toBeEmpty();
// Test isModifiable
$isModifiable = $config->ini->isModifiable(IniKey::DISPLAY_ERRORS);
expect($isModifiable)->toBeBool();
});
it('allows accessing all environment methods', function () {
$iniManager = new IniManager();
$environment = new Environment([
'TEST_STRING' => 'hello',
'TEST_INT' => '42',
'TEST_BOOL' => 'true',
]);
$config = new SystemConfig($iniManager, $environment);
// Test getString
expect($config->environment->getString('TEST_STRING'))->toBe('hello');
// Test getInt
expect($config->environment->getInt('TEST_INT'))->toBe(42);
// Test getBool
expect($config->environment->getBool('TEST_BOOL'))->toBeTrue();
// Test has
expect($config->environment->has('TEST_STRING'))->toBeTrue();
expect($config->environment->has('NON_EXISTENT'))->toBeFalse();
});
it('properties are readonly', function () {
$iniManager = new IniManager();
$environment = new Environment();
$config = new SystemConfig($iniManager, $environment);
// Properties should be accessible but not modifiable (readonly class)
expect($config->ini)->toBe($iniManager);
expect($config->environment)->toBe($environment);
});
});

View File

@@ -2,8 +2,8 @@
declare(strict_types=1);
use App\Framework\Cuid\Cuid;
use App\Framework\Cuid\CuidGenerator;
use App\Framework\Id\Cuid\Cuid;
use App\Framework\Id\Cuid\CuidGenerator;
use App\Framework\Random\SecureRandomGenerator;
it('generates Cuid with current timestamp', function () {

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
use App\Framework\Cuid\Cuid;
use App\Framework\Id\Cuid\Cuid;
it('creates Cuid from string', function () {
$value = 'cjld2cjxh0000qzrmn831i7rn';

View File

@@ -2,8 +2,8 @@
declare(strict_types=1);
use App\Framework\Ksuid\Ksuid;
use App\Framework\Ksuid\KsuidGenerator;
use App\Framework\Id\Ksuid\Ksuid;
use App\Framework\Id\Ksuid\KsuidGenerator;
use App\Framework\Random\SecureRandomGenerator;
it('generates KSUID with current timestamp', function () {

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
use App\Framework\Ksuid\Ksuid;
use App\Framework\Id\Ksuid\Ksuid;
it('creates KSUID from string', function () {
$value = '2SwcbqZrBNGd67ZJYmPKx42wKZj';

View File

@@ -159,7 +159,7 @@ final class CorrelationIdTest extends TestCase
public function test_creates_from_ulid(): void
{
$ulid = \App\Framework\Ulid\Ulid::generate();
$ulid = \App\Framework\Id\Ulid\Ulid::generate();
$id = CorrelationId::fromUlid($ulid);
$this->assertEquals((string) $ulid, $id->toString());

View File

@@ -3,11 +3,11 @@
declare(strict_types=1);
use App\Framework\DateTime\SystemClock;
use App\Framework\Id\Ulid\UlidGenerator;
use App\Framework\MagicLinks\MagicLinkToken;
use App\Framework\MagicLinks\Services\InMemoryMagicLinkService;
use App\Framework\MagicLinks\TokenAction;
use App\Framework\MagicLinks\TokenConfig;
use App\Framework\Ulid\UlidGenerator;
beforeEach(function () {
$this->clock = new SystemClock();

View File

@@ -2,8 +2,8 @@
declare(strict_types=1);
use App\Framework\NanoId\NanoId;
use App\Framework\NanoId\NanoIdGenerator;
use App\Framework\Id\NanoId\NanoId;
use App\Framework\Id\NanoId\NanoIdGenerator;
use App\Framework\Random\SecureRandomGenerator;
it('creates generator with default settings', function () {

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
use App\Framework\NanoId\NanoId;
use App\Framework\Id\NanoId\NanoId;
it('creates NanoId from string', function () {
$value = 'test123ABC';

View File

@@ -4,19 +4,17 @@ declare(strict_types=1);
require __DIR__ . '/../../vendor/autoload.php';
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\ErrorAggregation\ErrorEvent;
use App\Framework\ErrorAggregation\ErrorPattern;
use App\Framework\ErrorAggregation\Storage\InMemoryErrorStorage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\RequestContext;
use App\Framework\Exception\SystemContext;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Ulid\Ulid;
// Create test clock
$clock = new class implements Clock {

View File

@@ -15,17 +15,17 @@ declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use App\Framework\Core\AppBootstrapper;
use App\Framework\Queue\MachineLearning\JobAnomalyDetector;
use App\Framework\Queue\MachineLearning\QueueJobFeatureExtractor;
use App\Framework\Queue\MachineLearning\QueueAnomalyMonitor;
use App\Framework\Queue\Services\JobMetricsManager;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Queue\ValueObjects\JobMetadata;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Core\ValueObjects\Score;
use App\Framework\Ulid\Ulid;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\SystemClock;
use App\Framework\Id\Ulid\Ulid;
use App\Framework\Queue\MachineLearning\JobAnomalyDetector;
use App\Framework\Queue\MachineLearning\QueueAnomalyMonitor;
use App\Framework\Queue\MachineLearning\QueueJobFeatureExtractor;
use App\Framework\Queue\Services\JobMetricsManager;
use App\Framework\Queue\ValueObjects\JobMetadata;
use App\Framework\Queue\ValueObjects\JobMetrics;
echo "=== Queue Anomaly Detection Integration Test ===\n\n";