Files
michaelschiemer/tests/Unit/Framework/Exception/FrameworkExceptionTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

359 lines
14 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\ExceptionMetadata;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\Core\ValidationErrorCode;
use App\Framework\Exception\Core\ErrorSeverity;
describe('FrameworkException', function () {
describe('Factory Methods', function () {
it('creates simple exception without error code', function () {
$exception = FrameworkException::simple('Test error');
expect($exception->getMessage())->toBe('Test error');
expect($exception->getErrorCode())->toBeNull();
expect($exception->getContext())->toBeInstanceOf(ExceptionContext::class);
});
it('creates exception with error code', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::CONNECTION_FAILED,
'Database connection failed'
);
expect($exception->getMessage())->toBe('Database connection failed');
expect($exception->getErrorCode())->toBe(DatabaseErrorCode::CONNECTION_FAILED);
expect($exception->getSeverity())->toBe(ErrorSeverity::CRITICAL);
});
it('uses error code description as default message', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::QUERY_FAILED
);
expect($exception->getMessage())->toBe('Database query execution failed');
});
it('creates exception for operation', function () {
$exception = FrameworkException::forOperation(
'database.query',
'UserRepository',
'Query execution failed',
DatabaseErrorCode::QUERY_FAILED
);
expect($exception->getMessage())->toBe('Query execution failed');
expect($exception->getContext()->operation)->toBe('database.query');
expect($exception->getContext()->component)->toBe('UserRepository');
});
it('creates exception from context', function () {
$context = ExceptionContext::forOperation('test.operation', 'TestComponent')
->withData(['key' => 'value']);
$exception = FrameworkException::fromContext(
'Test message',
$context,
ValidationErrorCode::INVALID_INPUT
);
expect($exception->getMessage())->toBe('Test message');
expect($exception->getContext())->toBe($context);
expect($exception->getErrorCode())->toBe(ValidationErrorCode::INVALID_INPUT);
});
});
describe('ErrorCode Integration', function () {
it('provides severity from error code', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::CONNECTION_FAILED
);
expect($exception->getSeverity())->toBe(ErrorSeverity::CRITICAL);
});
it('provides recovery hint from error code', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::CONNECTION_FAILED
);
expect($exception->getRecoveryHint())
->toBe('Check database server status and connection settings');
});
it('identifies recoverable exceptions', function () {
$recoverable = FrameworkException::create(
DatabaseErrorCode::TIMEOUT
);
$nonRecoverable = FrameworkException::create(
DatabaseErrorCode::MIGRATION_ROLLBACK_FAILED
);
expect($recoverable->isRecoverable())->toBeTrue();
expect($nonRecoverable->isRecoverable())->toBeFalse();
});
it('provides retry after seconds from error code', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::CONNECTION_FAILED
);
expect($exception->getRetryAfter())->toBe(30);
});
it('checks error code identity', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::QUERY_FAILED
);
expect($exception->isErrorCode(DatabaseErrorCode::QUERY_FAILED))->toBeTrue();
expect($exception->isErrorCode(DatabaseErrorCode::CONNECTION_FAILED))->toBeFalse();
});
it('checks error code category', function () {
$dbException = FrameworkException::create(DatabaseErrorCode::QUERY_FAILED);
$authException = FrameworkException::create(AuthErrorCode::TOKEN_EXPIRED);
expect($dbException->isCategory('DB'))->toBeTrue();
expect($dbException->isCategory('AUTH'))->toBeFalse();
expect($authException->isCategory('AUTH'))->toBeTrue();
});
});
describe('ExceptionMetadata Integration', function () {
it('initializes with default metadata', function () {
$exception = FrameworkException::simple('Test');
expect($exception->getMetadata())->toBeInstanceOf(ExceptionMetadata::class);
expect($exception->shouldAggregate())->toBeTrue();
expect($exception->shouldReport())->toBeTrue();
expect($exception->wasReported())->toBeFalse();
});
it('uses retry after from error code', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::CONNECTION_FAILED
);
expect($exception->getRetryAfter())->toBe(30);
});
it('allows custom retry after override', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::QUERY_FAILED
)->withRetryAfter(60);
expect($exception->getRetryAfter())->toBe(60);
});
it('can skip aggregation', function () {
$exception = FrameworkException::simple('Test')
->skipAggregation();
expect($exception->shouldAggregate())->toBeFalse();
expect($exception->shouldReport())->toBeTrue();
});
it('can skip reporting', function () {
$exception = FrameworkException::simple('Test')
->skipReporting();
expect($exception->shouldAggregate())->toBeTrue();
expect($exception->shouldReport())->toBeFalse();
});
it('can skip both aggregation and reporting', function () {
$exception = FrameworkException::simple('Test')
->skipAll();
expect($exception->shouldAggregate())->toBeFalse();
expect($exception->shouldReport())->toBeFalse();
});
it('tracks reported status', function () {
$exception = FrameworkException::simple('Test');
expect($exception->wasReported())->toBeFalse();
$exception->markAsReported();
expect($exception->wasReported())->toBeTrue();
expect($exception->shouldReport())->toBeFalse();
});
});
describe('Context Enrichment', function () {
it('enriches exception with operation context', function () {
$exception = FrameworkException::simple('Test')
->withOperation('database.query', 'UserRepository');
expect($exception->getContext()->operation)->toBe('database.query');
expect($exception->getContext()->component)->toBe('UserRepository');
});
it('enriches exception with data', function () {
$exception = FrameworkException::simple('Test')
->withData(['user_id' => 123, 'action' => 'login']);
expect($exception->getContext()->data)->toBe(['user_id' => 123, 'action' => 'login']);
});
it('enriches exception with debug info', function () {
$exception = FrameworkException::simple('Test')
->withDebug(['query' => 'SELECT * FROM users', 'params' => [1]]);
expect($exception->getContext()->debug)->toHaveKey('query');
});
it('enriches exception with context metadata', function () {
$exception = FrameworkException::simple('Test')
->withContextMetadata(['request_id' => 'abc123']);
expect($exception->getContext()->metadata)->toBe(['request_id' => 'abc123']);
});
it('chains context enrichment methods', function () {
$exception = FrameworkException::simple('Test')
->withOperation('auth.login', 'AuthService')
->withData(['username' => 'john'])
->withDebug(['ip' => '127.0.0.1'])
->skipAggregation();
expect($exception->getContext()->operation)->toBe('auth.login');
expect($exception->getContext()->component)->toBe('AuthService');
expect($exception->getContext()->data['username'])->toBe('john');
expect($exception->shouldAggregate())->toBeFalse();
});
});
describe('Immutability', function () {
it('creates new instance when modifying context', function () {
$original = FrameworkException::simple('Test');
$modified = $original->withData(['key' => 'value']);
expect($original)->not->toBe($modified);
expect($original->getContext()->data)->toBe([]);
expect($modified->getContext()->data)->toBe(['key' => 'value']);
});
it('creates new instance when modifying metadata', function () {
$original = FrameworkException::simple('Test');
$modified = $original->skipAggregation();
expect($original)->not->toBe($modified);
expect($original->shouldAggregate())->toBeTrue();
expect($modified->shouldAggregate())->toBeFalse();
});
it('creates new instance when setting error code', function () {
$original = FrameworkException::simple('Test');
$modified = $original->withErrorCode(DatabaseErrorCode::QUERY_FAILED);
expect($original)->not->toBe($modified);
expect($original->getErrorCode())->toBeNull();
expect($modified->getErrorCode())->toBe(DatabaseErrorCode::QUERY_FAILED);
});
});
describe('Array Serialization', function () {
it('serializes to array with error code info', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::QUERY_FAILED,
'Test query failed'
)->withData(['table' => 'users']);
$array = $exception->toArray();
expect($array)->toHaveKey('class');
expect($array)->toHaveKey('message');
expect($array)->toHaveKey('error_code');
expect($array)->toHaveKey('error_category');
expect($array)->toHaveKey('description');
expect($array)->toHaveKey('recovery_hint');
expect($array)->toHaveKey('is_recoverable');
expect($array['error_code'])->toBe('DB002');
expect($array['error_category'])->toBe('DB');
});
it('serializes without error code', function () {
$exception = FrameworkException::simple('Test error');
$array = $exception->toArray();
expect($array)->toHaveKey('message');
expect($array)->not->toHaveKey('error_code');
});
});
describe('String Representation', function () {
it('includes error code in string representation', function () {
$exception = FrameworkException::create(
DatabaseErrorCode::QUERY_FAILED,
'Test query failed'
);
$string = (string) $exception;
expect($string)->toContain('[DB002]');
expect($string)->toContain('Test query failed');
});
it('works without error code', function () {
$exception = FrameworkException::simple('Test error');
$string = (string) $exception;
expect($string)->toContain('Test error');
expect($string)->not->toContain('[');
});
});
describe('Previous Exception Chain', function () {
it('wraps previous exception', function () {
$previous = new \RuntimeException('Original error');
$exception = FrameworkException::simple('Wrapped error', $previous);
expect($exception->getPrevious())->toBe($previous);
});
it('chains with error code', function () {
$previous = new \PDOException('Connection refused');
$exception = FrameworkException::create(
DatabaseErrorCode::CONNECTION_FAILED,
'Database unavailable',
previous: $previous
);
expect($exception->getPrevious())->toBe($previous);
expect($exception->getErrorCode())->toBe(DatabaseErrorCode::CONNECTION_FAILED);
});
});
describe('Multiple Error Code Categories', function () {
it('handles database errors', function () {
$exception = FrameworkException::create(DatabaseErrorCode::TIMEOUT);
expect($exception->isCategory('DB'))->toBeTrue();
expect($exception->getSeverity())->toBe(ErrorSeverity::ERROR);
expect($exception->getRetryAfter())->toBe(60);
});
it('handles authentication errors', function () {
$exception = FrameworkException::create(AuthErrorCode::TOKEN_EXPIRED);
expect($exception->isCategory('AUTH'))->toBeTrue();
expect($exception->getSeverity())->toBe(ErrorSeverity::WARNING);
expect($exception->getRetryAfter())->toBe(0);
});
it('handles validation errors', function () {
$exception = FrameworkException::create(ValidationErrorCode::BUSINESS_RULE_VIOLATION);
expect($exception->isCategory('VAL'))->toBeTrue();
expect($exception->getSeverity())->toBe(ErrorSeverity::ERROR);
expect($exception->isRecoverable())->toBeTrue();
});
});
});