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

@@ -2,10 +2,9 @@
declare(strict_types=1);
use App\Framework\Queue\ValueObjects\JobProgress;
use App\Framework\Queue\ValueObjects\ProgressStep;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Queue\ValueObjects\JobProgress;
describe('JobProgress Value Object', function () {
@@ -23,10 +22,10 @@ describe('JobProgress Value Object', function () {
});
it('rejects empty progress messages', function () {
expect(fn() => JobProgress::withPercentage(Percentage::zero(), ''))
expect(fn () => JobProgress::withPercentage(Percentage::zero(), ''))
->toThrow(\InvalidArgumentException::class, 'Progress message cannot be empty');
expect(fn() => JobProgress::withPercentage(Percentage::zero(), ' '))
expect(fn () => JobProgress::withPercentage(Percentage::zero(), ' '))
->toThrow(\InvalidArgumentException::class, 'Progress message cannot be empty');
});
@@ -285,16 +284,19 @@ describe('JobProgress Value Object', function () {
expect($array['is_starting'])->toBeTrue();
expect($array['is_completed'])->toBeFalse();
expect($array['is_failed'])->toBeFalse();
break;
case 'completed':
expect($array['is_completed'])->toBeTrue();
expect($array['is_starting'])->toBeFalse();
expect($array['is_failed'])->toBeFalse();
break;
case 'failed':
expect($array['is_failed'])->toBeTrue();
expect($array['is_starting'])->toBeFalse();
expect($array['is_completed'])->toBeFalse();
break;
}
}
@@ -306,36 +308,42 @@ describe('Job Progress Tracking System Mock', function () {
beforeEach(function () {
// Create a mock progress tracker for testing
$this->progressTracker = new class {
$this->progressTracker = new class () {
private array $progressEntries = [];
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void {
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
{
$this->progressEntries[$jobId][] = [
'progress' => $progress,
'step_name' => $stepName,
'timestamp' => time(),
'id' => uniqid()
'id' => uniqid(),
];
}
public function getCurrentProgress(string $jobId): ?JobProgress {
if (!isset($this->progressEntries[$jobId]) || empty($this->progressEntries[$jobId])) {
public function getCurrentProgress(string $jobId): ?JobProgress
{
if (! isset($this->progressEntries[$jobId]) || empty($this->progressEntries[$jobId])) {
return null;
}
$entries = $this->progressEntries[$jobId];
return end($entries)['progress'];
}
public function getProgressHistory(string $jobId): array {
public function getProgressHistory(string $jobId): array
{
return $this->progressEntries[$jobId] ?? [];
}
public function markJobCompleted(string $jobId, string $message = 'Job completed successfully'): void {
public function markJobCompleted(string $jobId, string $message = 'Job completed successfully'): void
{
$this->updateProgress($jobId, JobProgress::completed($message));
}
public function markJobFailed(string $jobId, string $message = 'Job failed', ?\Throwable $exception = null): void {
public function markJobFailed(string $jobId, string $message = 'Job failed', ?\Throwable $exception = null): void
{
$metadata = [];
if ($exception) {
$metadata['exception_type'] = get_class($exception);
@@ -346,7 +354,8 @@ describe('Job Progress Tracking System Mock', function () {
$this->updateProgress($jobId, $progress);
}
public function getProgressForJobs(array $jobIds): array {
public function getProgressForJobs(array $jobIds): array
{
$result = [];
foreach ($jobIds as $jobId) {
$current = $this->getCurrentProgress($jobId);
@@ -354,10 +363,12 @@ describe('Job Progress Tracking System Mock', function () {
$result[$jobId] = $current;
}
}
return $result;
}
public function getJobsAboveProgress(float $minPercentage): array {
public function getJobsAboveProgress(float $minPercentage): array
{
$result = [];
foreach ($this->progressEntries as $jobId => $entries) {
$current = end($entries)['progress'];
@@ -365,6 +376,7 @@ describe('Job Progress Tracking System Mock', function () {
$result[] = ['job_id' => $jobId, 'progress' => $current];
}
}
return $result;
}
};
@@ -396,7 +408,7 @@ describe('Job Progress Tracking System Mock', function () {
'validation' => 'Validating input data',
'processing' => 'Processing records',
'notification' => 'Sending notifications',
'cleanup' => 'Cleaning up temporary files'
'cleanup' => 'Cleaning up temporary files',
];
foreach ($steps as $stepName => $message) {
@@ -411,7 +423,7 @@ describe('Job Progress Tracking System Mock', function () {
expect(count($history))->toBe(4);
// Check step names are tracked
$stepNames = array_map(fn($entry) => $entry['step_name'], $history);
$stepNames = array_map(fn ($entry) => $entry['step_name'], $history);
expect($stepNames)->toBe(['validation', 'processing', 'notification', 'cleanup']);
});
@@ -538,14 +550,14 @@ describe('Job Progress Tracking System Mock', function () {
}
$history = $this->progressTracker->getProgressHistory($jobId);
$messages = array_map(fn($entry) => $entry['progress']->message, $history);
$messages = array_map(fn ($entry) => $entry['progress']->message, $history);
expect($messages)->toBe([
'Starting',
'Quarter done',
'Half done',
'Three quarters done',
'Completed'
'Completed',
]);
});
@@ -590,32 +602,36 @@ describe('Job Progress Tracking System Mock', function () {
describe('Job Progress Integration Scenarios', function () {
beforeEach(function () {
$this->emailJob = new class {
$this->emailJob = new class () {
public function __construct(
public array $recipients = ['test@example.com'],
public string $subject = 'Test Email',
public string $template = 'newsletter'
) {}
) {
}
public function getRecipientCount(): int {
public function getRecipientCount(): int
{
return count($this->recipients);
}
};
$this->reportJob = new class {
$this->reportJob = new class () {
public function __construct(
public string $reportType = 'sales',
public array $criteria = ['period' => 'monthly'],
public int $totalSteps = 5
) {}
) {
}
public function getSteps(): array {
public function getSteps(): array
{
return [
'data_collection' => 'Collecting data from database',
'data_processing' => 'Processing and aggregating data',
'chart_generation' => 'Generating charts and graphs',
'pdf_creation' => 'Creating PDF document',
'distribution' => 'Distributing report to stakeholders'
'distribution' => 'Distributing report to stakeholders',
];
}
};
@@ -623,13 +639,20 @@ describe('Job Progress Integration Scenarios', function () {
it('demonstrates email job progress tracking', function () {
$jobId = JobId::generate()->toString();
$progressTracker = new class {
$progressTracker = new class () {
private array $progressEntries = [];
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void {
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
{
$this->progressEntries[$jobId][] = ['progress' => $progress, 'step_name' => $stepName];
}
public function getCurrentProgress(string $jobId): ?JobProgress {
if (!isset($this->progressEntries[$jobId])) return null;
public function getCurrentProgress(string $jobId): ?JobProgress
{
if (! isset($this->progressEntries[$jobId])) {
return null;
}
return end($this->progressEntries[$jobId])['progress'];
}
};
@@ -678,12 +701,16 @@ describe('Job Progress Integration Scenarios', function () {
it('demonstrates report generation progress tracking', function () {
$jobId = JobId::generate()->toString();
$progressTracker = new class {
$progressTracker = new class () {
private array $progressEntries = [];
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void {
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
{
$this->progressEntries[$jobId][] = ['progress' => $progress, 'step_name' => $stepName];
}
public function getProgressHistory(string $jobId): array {
public function getProgressHistory(string $jobId): array
{
return $this->progressEntries[$jobId] ?? [];
}
};
@@ -705,23 +732,30 @@ describe('Job Progress Integration Scenarios', function () {
expect(count($history))->toBe($totalSteps);
// Verify step progression
$stepNames = array_map(fn($entry) => $entry['step_name'], $history);
$stepNames = array_map(fn ($entry) => $entry['step_name'], $history);
expect($stepNames)->toBe(array_keys($steps));
// Verify progress percentages
$percentages = array_map(fn($entry) => $entry['progress']->percentage->getValue(), $history);
$percentages = array_map(fn ($entry) => $entry['progress']->percentage->getValue(), $history);
expect($percentages)->toBe([20.0, 40.0, 60.0, 80.0, 100.0]);
});
it('demonstrates error handling with progress tracking', function () {
$jobId = JobId::generate()->toString();
$progressTracker = new class {
$progressTracker = new class () {
private array $progressEntries = [];
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void {
public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
{
$this->progressEntries[$jobId][] = ['progress' => $progress, 'step_name' => $stepName];
}
public function getCurrentProgress(string $jobId): ?JobProgress {
if (!isset($this->progressEntries[$jobId])) return null;
public function getCurrentProgress(string $jobId): ?JobProgress
{
if (! isset($this->progressEntries[$jobId])) {
return null;
}
return end($this->progressEntries[$jobId])['progress'];
}
};
@@ -747,7 +781,7 @@ describe('Job Progress Integration Scenarios', function () {
'exception_message' => $exception->getMessage(),
'failed_at_step' => 'data_processing',
'items_processed' => 150,
'total_items' => 500
'total_items' => 500,
]);
$progressTracker->updateProgress($jobId, $failedProgress, 'data_processing');
@@ -757,4 +791,4 @@ describe('Job Progress Integration Scenarios', function () {
expect($currentProgress->metadata['items_processed'])->toBe(150);
expect($currentProgress->metadata['exception_type'])->toBe('RuntimeException');
});
});
});