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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
use App\Framework\Config\Environment;
use App\Framework\Deployment\Ssl\HealthChecks\SslCertificateHealthCheck;
use App\Framework\Deployment\Ssl\Services\SslCertificateService;
use App\Framework\Deployment\Ssl\ValueObjects\CertificateStatus;
use App\Framework\Deployment\Ssl\ValueObjects\DomainName;
use App\Framework\Health\HealthCheckCategory;
use App\Framework\Health\HealthStatus;
describe('SslCertificateHealthCheck', function () {
beforeEach(function () {
$this->sslService = Mockery::mock(SslCertificateService::class);
$this->environment = Mockery::mock(Environment::class);
// Setup environment mock
$this->environment->shouldReceive('get')
->with('DOMAIN_NAME', 'michaelschiemer.de')
->andReturn('example.com');
$this->environment->shouldReceive('get')
->with('SSL_EMAIL', 'mail@michaelschiemer.de')
->andReturn('admin@example.com');
$this->environment->shouldReceive('get')
->with('LETSENCRYPT_STAGING', '0')
->andReturn('0');
$this->healthCheck = new SslCertificateHealthCheck(
$this->sslService,
$this->environment
);
});
afterEach(function () {
Mockery::close();
});
it('returns healthy status for valid certificate', function () {
$status = new CertificateStatus(
exists: true,
isValid: true,
notBefore: new DateTimeImmutable('-30 days'),
notAfter: new DateTimeImmutable('+60 days'),
issuer: 'Let\'s Encrypt Authority X3',
subject: 'example.com',
daysUntilExpiry: 60,
isExpiring: false,
isExpired: false
);
$this->sslService->shouldReceive('getStatus')
->once()
->andReturn($status);
$result = $this->healthCheck->check();
expect($result->status)->toBe(HealthStatus::HEALTHY);
expect($result->operation)->toBe('SSL Certificate Check');
expect($result->details['domain'])->toBe('example.com');
expect($result->details['days_until_expiry'])->toBe(60);
});
it('returns unhealthy status when certificate does not exist', function () {
$status = CertificateStatus::notFound();
$this->sslService->shouldReceive('getStatus')
->once()
->andReturn($status);
$result = $this->healthCheck->check();
expect($result->status)->toBe(HealthStatus::UNHEALTHY);
expect($result->reason)->toBe('Certificate not found');
});
it('returns unhealthy status for expired certificate', function () {
$status = new CertificateStatus(
exists: true,
isValid: false,
notBefore: new DateTimeImmutable('-180 days'),
notAfter: new DateTimeImmutable('-10 days'),
issuer: 'Let\'s Encrypt Authority X3',
subject: 'example.com',
daysUntilExpiry: -10,
isExpiring: false,
isExpired: true
);
$this->sslService->shouldReceive('getStatus')
->once()
->andReturn($status);
$result = $this->healthCheck->check();
expect($result->status)->toBe(HealthStatus::UNHEALTHY);
expect($result->reason)->toBe('Certificate has expired');
expect($result->details['days_since_expiry'])->toBe(10);
});
it('returns warning status for expiring certificate', function () {
$status = new CertificateStatus(
exists: true,
isValid: true,
notBefore: new DateTimeImmutable('-60 days'),
notAfter: new DateTimeImmutable('+20 days'),
issuer: 'Let\'s Encrypt Authority X3',
subject: 'example.com',
daysUntilExpiry: 20,
isExpiring: true,
isExpired: false
);
$this->sslService->shouldReceive('getStatus')
->once()
->andReturn($status);
$result = $this->healthCheck->check();
expect($result->status)->toBe(HealthStatus::WARNING);
expect($result->reason)->toBe('Certificate expires in 20 days');
expect($result->details['days_until_expiry'])->toBe(20);
});
it('returns unhealthy status for invalid certificate', function () {
$status = new CertificateStatus(
exists: true,
isValid: false,
notBefore: new DateTimeImmutable('-30 days'),
notAfter: new DateTimeImmutable('+60 days'),
issuer: null,
subject: null,
daysUntilExpiry: 60,
isExpiring: false,
isExpired: false,
errors: ['Certificate verification failed']
);
$this->sslService->shouldReceive('getStatus')
->once()
->andReturn($status);
$result = $this->healthCheck->check();
expect($result->status)->toBe(HealthStatus::UNHEALTHY);
expect($result->reason)->toBe('Certificate is invalid');
expect($result->details['errors'])->toContain('Certificate verification failed');
});
it('returns unhealthy status on exception', function () {
$this->sslService->shouldReceive('getStatus')
->once()
->andThrow(new \RuntimeException('Connection failed'));
$result = $this->healthCheck->check();
expect($result->status)->toBe(HealthStatus::UNHEALTHY);
expect($result->reason)->toBe('Health check failed');
expect($result->exception)->toBeInstanceOf(\RuntimeException::class);
});
it('has correct name and category', function () {
expect($this->healthCheck->getName())->toBe('SSL Certificate');
expect($this->healthCheck->getCategory())->toBe(HealthCheckCategory::SECURITY);
});
it('has reasonable timeout', function () {
$timeout = $this->healthCheck->getTimeout();
expect($timeout)->toBeInt();
expect($timeout)->toBeGreaterThan(0);
expect($timeout)->toBeLessThanOrEqual(10000); // Max 10 seconds
});
it('measures response time', function () {
$status = new CertificateStatus(
exists: true,
isValid: true,
notBefore: null,
notAfter: null,
issuer: null,
subject: null,
daysUntilExpiry: 60,
isExpiring: false,
isExpired: false
);
$this->sslService->shouldReceive('getStatus')
->once()
->andReturn($status);
$result = $this->healthCheck->check();
expect($result->responseTimeMs)->toBeFloat();
expect($result->responseTimeMs)->toBeGreaterThanOrEqual(0);
});
});