Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\ClassName;
describe('ClassName Edge Cases and Security', function () {
it('handles potential security issues gracefully', function () {
// Test with potentially problematic class names that could cause issues
$dangerousCases = [
'../../etc/passwd', // Path traversal attempt
'<script>alert(1)</script>', // XSS attempt
'DROP TABLE users', // SQL injection style
"\0null\0byte", // Null byte injection
'very.long.dotted.name', // Invalid dots instead of backslashes
];
foreach ($dangerousCases as $dangerous) {
expect(function () use ($dangerous) {
ClassName::create($dangerous);
})->toThrow(InvalidArgumentException::class);
}
});
it('handles unicode and special characters', function () {
// Some unicode characters are valid in PHP class names (within \x80-\xff range)
$validUnicodeCase = 'Ñame'; // Accented characters in \x80-\xff range
$className = ClassName::create($validUnicodeCase);
expect($className->exists())->toBeFalse(); // Won't exist but should be valid name
// These should be definitely invalid
$definitivelyInvalidCases = [
'Name Space', // Space (definitely invalid)
'Name-Dash', // Dash (definitely invalid)
'Name.Dot', // Dot (definitely invalid)
];
foreach ($definitivelyInvalidCases as $invalid) {
expect(function () use ($invalid) {
ClassName::create($invalid);
})->toThrow(InvalidArgumentException::class);
}
// Test that we can create valid unicode names without errors
$potentiallyValidCases = ['Ñame', 'Tëst', 'Clâss'];
foreach ($potentiallyValidCases as $case) {
$className = ClassName::create($case);
expect($className->getFullyQualified())->toBe($case);
expect($className->exists())->toBeFalse(); // They won't exist but names should be valid
}
});
it('handles very long class names', function () {
// Test with extremely long but valid class name
$longNamespace = str_repeat('VeryLongNamespace\\', 50);
$longClassName = $longNamespace . 'VeryLongClassName';
$className = ClassName::create($longClassName);
expect($className->exists())->toBeFalse(); // Should not exist but should not error
expect($className->getFullyQualified())->toBe($longClassName);
});
it('handles constructor edge cases', function () {
// Test various whitespace and formatting issues
expect(function () {
ClassName::create('');
})->toThrow(InvalidArgumentException::class, 'Class name cannot be empty');
expect(function () {
ClassName::create(" \t\n ");
})->toThrow(InvalidArgumentException::class);
// Leading backslashes should be handled correctly (but trailing ones are preserved)
$className = ClassName::create('\\\\\\App\\Test');
expect($className->getFullyQualified())->toBe('App\\Test'); // Leading backslashes removed
});
it('stress test - handles many rapid exists() calls without issues', function () {
$startTime = microtime(true);
$startMemory = memory_get_usage();
// Rapid fire test
for ($i = 0; $i < 5000; $i++) {
$className = ClassName::create('TestClass' . ($i % 100));
$className->exists();
}
$endTime = microtime(true);
$endMemory = memory_get_usage();
$duration = $endTime - $startTime;
$memoryIncrease = $endMemory - $startMemory;
// Should complete within reasonable time (less than 1 second)
expect($duration)->toBeLessThan(1.0);
// Should not consume excessive memory (less than 5MB)
expect($memoryIncrease)->toBeLessThan(5 * 1024 * 1024);
});
it('validates that exists() is consistent across calls', function () {
// Test that multiple calls to exists() on the same instance return consistent results
$existingClass = ClassName::create('stdClass');
$nonExistingClass = ClassName::create('NonExistentClass123');
// Multiple calls should return the same result
for ($i = 0; $i < 10; $i++) {
expect($existingClass->exists())->toBeTrue();
expect($nonExistingClass->exists())->toBeFalse();
}
// Different instances of the same class name should also be consistent
for ($i = 0; $i < 5; $i++) {
$newInstance = ClassName::create('stdClass');
expect($newInstance->exists())->toBeTrue();
$newNonExisting = ClassName::create('NonExistentClass123');
expect($newNonExisting->exists())->toBeFalse();
}
});
it('handles the exact scenario from the bug report', function () {
// This test recreates the exact conditions that caused the original bug:
// 1. Container compilation process
// 2. Dependency resolution with potentially empty class names
// 3. Multiple rapid exists() checks
$problematicCases = [
'App\\Framework\\Filesystem\\AtomicStorage', // The interface that caused issues
'App\\Framework\\Filesystem\\Storage', // Parent interface
'App\\Framework\\Filesystem\\FileStorage', // Implementation
'App\\Framework\\DI\\Container', // Container interface
'App\\Framework\\DI\\DefaultContainer', // Container implementation
];
// Simulate rapid container compilation checks
foreach ($problematicCases as $case) {
$className = ClassName::create($case);
$exists = $className->exists();
// All these should exist and not cause warnings
expect($exists)->toBeTrue();
expect($className->getFullyQualified())->toBe($case);
}
// Also test with non-existent classes that might be checked during compilation
$nonExistentCases = [
'App\\NonExistent\\Interface',
'App\\Framework\\NonExistent\\Class',
'Some\\Random\\ClassName\\That\\DoesNot\\Exist',
];
foreach ($nonExistentCases as $case) {
$className = ClassName::create($case);
expect($className->exists())->toBeFalse();
}
});
});

View File

@@ -0,0 +1,264 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\ClassName;
describe('ClassName::exists()', function () {
it('returns false for empty strings', function () {
// This should be caught by constructor validation, but let's test the edge case
expect(function () {
ClassName::create('');
})->toThrow(InvalidArgumentException::class, 'Class name cannot be empty');
});
it('returns false for whitespace-only strings', function () {
expect(function () {
ClassName::create(' ');
})->toThrow(InvalidArgumentException::class, 'Invalid class name: ');
expect(function () {
ClassName::create("\t");
})->toThrow(InvalidArgumentException::class, 'Invalid class name: ');
expect(function () {
ClassName::create("\n");
})->toThrow(InvalidArgumentException::class, 'Invalid class name:
');
});
it('returns true for existing classes', function () {
$className = ClassName::create('stdClass');
expect($className->exists())->toBeTrue();
$className = ClassName::create('Exception');
expect($className->exists())->toBeTrue();
$className = ClassName::create('DateTime');
expect($className->exists())->toBeTrue();
});
it('returns true for existing interfaces', function () {
$className = ClassName::create('Countable');
expect($className->exists())->toBeTrue();
$className = ClassName::create('Traversable');
expect($className->exists())->toBeTrue();
$className = ClassName::create('JsonSerializable');
expect($className->exists())->toBeTrue();
});
it('returns true for existing traits', function () {
// PHP doesn't have many built-in traits, but let's test with a custom one
// We'll create a simple trait for testing
eval('trait TestTrait { public function testMethod() { return "test"; } }');
$className = ClassName::create('TestTrait');
expect($className->exists())->toBeTrue();
});
it('returns false for non-existent classes', function () {
$className = ClassName::create('NonExistentClass');
expect($className->exists())->toBeFalse();
$className = ClassName::create('App\\NonExistent\\Class\\Name');
expect($className->exists())->toBeFalse();
$className = ClassName::create('Some\\Random\\ClassName');
expect($className->exists())->toBeFalse();
});
it('handles fully qualified class names correctly', function () {
$className = ClassName::create('\\stdClass');
expect($className->exists())->toBeTrue();
$className = ClassName::create('\\Exception');
expect($className->exists())->toBeTrue();
$className = ClassName::create('\\NonExistentClass');
expect($className->exists())->toBeFalse();
});
it('handles framework classes correctly', function () {
$className = ClassName::create('App\\Framework\\Core\\ValueObjects\\ClassName');
expect($className->exists())->toBeTrue();
$className = ClassName::create('App\\Framework\\DI\\Container');
expect($className->exists())->toBeTrue();
$className = ClassName::create('App\\Framework\\NonExistent\\Class');
expect($className->exists())->toBeFalse();
});
it('handles different case variations (PHP classes are case-insensitive)', function () {
$className = ClassName::create('stdclass'); // lowercase
expect($className->exists())->toBeTrue(); // PHP classes are case-insensitive
$className = ClassName::create('STDCLASS'); // uppercase
expect($className->exists())->toBeTrue(); // PHP classes are case-insensitive
$className = ClassName::create('stdClass'); // correct case
expect($className->exists())->toBeTrue();
});
it('handles invalid class name formats gracefully', function () {
// These should be caught by constructor validation
expect(function () {
ClassName::create('123InvalidName');
})->toThrow(InvalidArgumentException::class);
expect(function () {
ClassName::create('Invalid-Name');
})->toThrow(InvalidArgumentException::class);
expect(function () {
ClassName::create('Invalid Name');
})->toThrow(InvalidArgumentException::class);
});
it('performance test - does not cause memory leaks with many calls', function () {
$memoryBefore = memory_get_usage();
// Test with many exists() calls
for ($i = 0; $i < 1000; $i++) {
$className = ClassName::create('stdClass');
$className->exists();
$className = ClassName::create('NonExistentClass' . $i);
$className->exists();
}
$memoryAfter = memory_get_usage();
$memoryIncrease = $memoryAfter - $memoryBefore;
// Memory increase should be reasonable (less than 1MB for 2000 calls)
expect($memoryIncrease)->toBeLessThan(1024 * 1024);
});
it('works correctly with autoloader edge cases', function () {
// Test that exists() doesn't trigger warnings or errors with edge cases
$className = ClassName::create('App\\NonExistent\\VeryLongClassNameThatDefinitelyDoesNotExist\\WithMultipleNamespaces\\AndMore\\Classes');
expect($className->exists())->toBeFalse();
// Test with special characters that are valid in namespaces
$className = ClassName::create('App\\Test\\ClassName123');
expect($className->exists())->toBeFalse();
});
it('handles concurrent access correctly', function () {
// Simulate concurrent access to exists() method
$results = [];
for ($i = 0; $i < 10; $i++) {
$className = ClassName::create('stdClass');
$results[] = $className->exists();
}
// All results should be true
foreach ($results as $result) {
expect($result)->toBeTrue();
}
// Test with non-existent class
$results = [];
for ($i = 0; $i < 10; $i++) {
$className = ClassName::create('NonExistentClass');
$results[] = $className->exists();
}
// All results should be false
foreach ($results as $result) {
expect($result)->toBeFalse();
}
});
});
describe('ClassName::exists() integration with container', function () {
it('prevents empty string issues in DI container context', function () {
// This test ensures that ClassName::exists() works correctly when used
// in the context that was causing the original "Uninitialized string offset 0" issue
// Test the exact scenario that was failing
$testCases = [
'stdClass', // Should exist
'Exception', // Should exist
'NonExistentClass', // Should not exist
'App\\Framework\\DI\\Container', // Should exist (interface)
'App\\NonExistent\\Interface', // Should not exist
];
foreach ($testCases as $testClass) {
$className = ClassName::create($testClass);
$result = $className->exists();
// The important thing is that no warnings or errors are generated
expect($result)->toBeIn([true, false]);
}
});
it('handles the AtomicStorage interface case that was failing', function () {
// Test the specific case that was causing issues
$className = ClassName::create('App\\Framework\\Filesystem\\AtomicStorage');
expect($className->exists())->toBeTrue(); // Interface should exist
$className = ClassName::create('App\\Framework\\Filesystem\\Storage');
expect($className->exists())->toBeTrue(); // Interface should exist
$className = ClassName::create('App\\Framework\\Filesystem\\FileStorage');
expect($className->exists())->toBeTrue(); // Implementation should exist
});
it('regression test - ensures no "Uninitialized string offset 0" warnings', function () {
// This is a regression test for the specific bug we fixed
// The bug occurred when empty strings were passed to class_exists()
// Capture any warnings or errors
$errorLevel = error_reporting(E_ALL);
$errors = [];
set_error_handler(function ($severity, $message, $file, $line) use (&$errors) {
$errors[] = [
'severity' => $severity,
'message' => $message,
'file' => $file,
'line' => $line,
];
});
try {
// Test various scenarios that could potentially cause the issue
$testCases = [
'stdClass',
'Exception',
'NonExistentClass',
'App\\Framework\\Filesystem\\AtomicStorage',
'App\\Framework\\Filesystem\\Storage',
'App\\Framework\\DI\\Container',
'App\\NonExistent\\Class\\Name',
'Very\\Long\\NonExistent\\Namespace\\ClassName',
];
foreach ($testCases as $testCase) {
$className = ClassName::create($testCase);
$result = $className->exists();
// Result should be boolean, no errors should occur
expect($result)->toBeBool();
}
// Ensure no "Uninitialized string offset" or similar warnings occurred
foreach ($errors as $error) {
expect($error['message'])->not->toContain('Uninitialized string offset');
expect($error['message'])->not->toContain('ClassLoader.php');
}
// Should have no errors at all
expect($errors)->toBeEmpty();
} finally {
restore_error_handler();
error_reporting($errorLevel);
}
});
});

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Score;
use App\Framework\Core\ValueObjects\ScoreLevel;
describe('Score Value Object', function () {
it('creates score with valid value', function () {
$score = new Score(0.75);
expect($score->value())->toBe(0.75);
expect($score->toLevel())->toBe(ScoreLevel::HIGH);
});
it('validates score bounds', function () {
expect(fn () => new Score(-0.1))->toThrow(InvalidArgumentException::class);
expect(fn () => new Score(1.1))->toThrow(InvalidArgumentException::class);
});
it('creates score from percentage', function () {
$percentage = Percentage::from(80.0); // 80%
$score = Score::fromPercentage($percentage);
expect($score->value())->toBe(0.8);
});
it('converts to percentage', function () {
$score = new Score(0.6);
$percentage = $score->toPercentage();
expect($percentage->getValue())->toBe(60.0); // 60%
});
it('determines correct levels', function () {
expect((new Score(0.95))->toLevel())->toBe(ScoreLevel::CRITICAL);
expect((new Score(0.8))->toLevel())->toBe(ScoreLevel::HIGH);
expect((new Score(0.5))->toLevel())->toBe(ScoreLevel::MEDIUM);
expect((new Score(0.1))->toLevel())->toBe(ScoreLevel::LOW);
});
it('combines scores correctly', function () {
$score1 = new Score(0.8);
$score2 = new Score(0.4);
$combined = $score1->combine($score2, 0.6);
expect($combined->value())->toBe(0.64); // 0.8 * 0.6 + 0.4 * 0.4
});
it('calculates weighted average', function () {
$scores = [
new Score(0.8),
new Score(0.6),
new Score(0.4),
];
$weights = [0.5, 0.3, 0.2];
$average = Score::weightedAverage($scores, $weights);
expect($average->value())->toEqualWithDelta(0.66, 0.01); // 0.8*0.5 + 0.6*0.3 + 0.4*0.2
});
it('serializes and deserializes correctly', function () {
$score = new Score(0.75);
$array = $score->toArray();
$restored = Score::fromArray($array);
expect($restored->value())->toBe($score->value());
expect($restored->toLevel())->toBe($score->toLevel());
});
});