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,233 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\LiveComponents\ValueObjects\CacheConfig;
describe('CacheConfig', function () {
it('creates basic cache config', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(10)
);
expect($config->enabled)->toBeTrue();
expect($config->ttl->toSeconds())->toBe(600);
expect($config->varyBy)->toBe([]);
expect($config->staleWhileRevalidate)->toBeFalse();
expect($config->staleWhileRevalidateTtl)->toBeNull();
});
it('creates config with varyBy parameters', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
varyBy: ['user_id', 'category', 'sort']
);
expect($config->varyBy)->toBe(['user_id', 'category', 'sort']);
});
it('creates config with stale-while-revalidate', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
staleWhileRevalidate: true,
staleWhileRevalidateTtl: Duration::fromMinutes(60)
);
expect($config->staleWhileRevalidate)->toBeTrue();
expect($config->staleWhileRevalidateTtl->toMinutes())->toBe(60);
});
it('creates disabled cache config', function () {
$config = new CacheConfig(
enabled: false,
ttl: Duration::fromMinutes(10)
);
expect($config->enabled)->toBeFalse();
});
it('creates from array', function () {
$data = [
'enabled' => true,
'ttl' => 600, // seconds
'varyBy' => ['filter', 'page'],
'staleWhileRevalidate' => true,
'staleWhileRevalidateTtl' => 3600,
];
$config = CacheConfig::fromArray($data);
expect($config->enabled)->toBeTrue();
expect($config->ttl->toSeconds())->toBe(600);
expect($config->varyBy)->toBe(['filter', 'page']);
expect($config->staleWhileRevalidate)->toBeTrue();
expect($config->staleWhileRevalidateTtl->toSeconds())->toBe(3600);
});
it('creates disabled config', function () {
$config = CacheConfig::disabled();
expect($config->enabled)->toBeFalse();
expect($config->ttl->toSeconds())->toBe(0);
expect($config->varyBy)->toBe([]);
expect($config->staleWhileRevalidate)->toBeFalse();
});
it('creates default config', function () {
$config = CacheConfig::default();
expect($config->enabled)->toBeTrue();
expect($config->ttl->toMinutes())->toBe(5); // Default 5 minutes
expect($config->varyBy)->toBe([]);
expect($config->staleWhileRevalidate)->toBeFalse();
});
it('checks if cache is enabled', function () {
$enabled = new CacheConfig(enabled: true, ttl: Duration::fromMinutes(5));
expect($enabled->isEnabled())->toBeTrue();
$disabled = new CacheConfig(enabled: false, ttl: Duration::fromMinutes(5));
expect($disabled->isEnabled())->toBeFalse();
});
it('checks if has variation', function () {
$withVariation = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
varyBy: ['user_id']
);
expect($withVariation->hasVariation())->toBeTrue();
$withoutVariation = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
varyBy: []
);
expect($withoutVariation->hasVariation())->toBeFalse();
});
it('checks if uses stale-while-revalidate', function () {
$withSWR = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
staleWhileRevalidate: true,
staleWhileRevalidateTtl: Duration::fromMinutes(60)
);
expect($withSWR->usesStaleWhileRevalidate())->toBeTrue();
$withoutSWR = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
staleWhileRevalidate: false
);
expect($withoutSWR->usesStaleWhileRevalidate())->toBeFalse();
});
it('gets effective TTL with SWR', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(5),
staleWhileRevalidate: true,
staleWhileRevalidateTtl: Duration::fromMinutes(60)
);
$effectiveTtl = $config->getEffectiveTtl();
// With SWR, effective TTL should be the SWR TTL
expect($effectiveTtl->toMinutes())->toBe(60);
});
it('gets effective TTL without SWR', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(10),
staleWhileRevalidate: false
);
$effectiveTtl = $config->getEffectiveTtl();
// Without SWR, effective TTL should be the regular TTL
expect($effectiveTtl->toMinutes())->toBe(10);
});
it('handles various TTL durations', function () {
$configs = [
Duration::fromSeconds(30),
Duration::fromMinutes(5),
Duration::fromMinutes(15),
Duration::fromHours(1),
Duration::fromHours(24),
Duration::fromDays(7),
];
foreach ($configs as $duration) {
$config = new CacheConfig(enabled: true, ttl: $duration);
expect($config->ttl)->toBe($duration);
}
});
it('supports multiple varyBy parameters', function () {
$varyByParams = [
'user_id',
'team_id',
'filter_category',
'filter_price_min',
'filter_price_max',
'sort_by',
'sort_order',
'page',
'per_page',
];
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(10),
varyBy: $varyByParams
);
expect($config->varyBy)->toBe($varyByParams);
expect($config->hasVariation())->toBeTrue();
});
it('converts to array', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromMinutes(10),
varyBy: ['filter', 'page'],
staleWhileRevalidate: true,
staleWhileRevalidateTtl: Duration::fromMinutes(60)
);
$array = $config->toArray();
expect($array)->toBe([
'enabled' => true,
'ttl' => 600,
'varyBy' => ['filter', 'page'],
'staleWhileRevalidate' => true,
'staleWhileRevalidateTtl' => 3600,
]);
});
it('creates config for short-lived cache', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromSeconds(10)
);
expect($config->ttl->toSeconds())->toBe(10);
});
it('creates config for long-lived cache', function () {
$config = new CacheConfig(
enabled: true,
ttl: Duration::fromDays(7)
);
expect($config->ttl->toDays())->toBe(7);
});
});

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
use App\Framework\LiveComponents\ValueObjects\ComponentId;
describe('ComponentId Value Object', function () {
it('creates from valid string format', function () {
$id = ComponentId::fromString('counter:demo');
expect($id->name)->toBe('counter');
expect($id->instanceId)->toBe('demo');
expect($id->toString())->toBe('counter:demo');
});
it('generates new component ID with unique instance', function () {
$id1 = ComponentId::generate('search');
$id2 = ComponentId::generate('search');
expect($id1->name)->toBe('search');
expect($id2->name)->toBe('search');
expect($id1->instanceId !== $id2->instanceId)->toBeTrue();
});
it('creates with specific instance ID', function () {
$id = ComponentId::create('modal', 'main-dialog');
expect($id->name)->toBe('modal');
expect($id->instanceId)->toBe('main-dialog');
expect($id->toString())->toBe('modal:main-dialog');
});
it('converts to string with __toString', function () {
$id = ComponentId::fromString('tabs:settings');
expect((string) $id)->toBe('tabs:settings');
});
it('checks equality correctly', function () {
$id1 = ComponentId::fromString('counter:demo');
$id2 = ComponentId::fromString('counter:demo');
$id3 = ComponentId::fromString('counter:other');
expect($id1->equals($id2))->toBeTrue();
expect($id1->equals($id3))->toBeFalse();
});
it('checks component name', function () {
$id = ComponentId::fromString('search:main-form');
expect($id->hasName('search'))->toBeTrue();
expect($id->hasName('counter'))->toBeFalse();
});
it('throws exception for empty ID', function () {
ComponentId::fromString('');
})->throws(InvalidArgumentException::class, 'Component ID cannot be empty');
it('throws exception for invalid format', function () {
ComponentId::fromString('invalid-format');
})->throws(InvalidArgumentException::class, 'Invalid component ID format');
it('throws exception for empty name', function () {
ComponentId::fromString(':instance');
})->throws(InvalidArgumentException::class, 'Component name cannot be empty');
it('throws exception for empty instance ID', function () {
ComponentId::fromString('component:');
})->throws(InvalidArgumentException::class, 'Instance ID cannot be empty');
it('throws exception for invalid name characters', function () {
ComponentId::create('invalid/name', 'instance');
})->throws(InvalidArgumentException::class, 'Invalid component name format');
it('accepts valid name formats', function (string $name) {
$id = ComponentId::create($name, 'test');
expect($id->name)->toBe($name);
})->with([
'lowercase' => 'counter',
'with-hyphens' => 'search-component',
'with_underscores' => 'data_table',
'with-numbers' => 'tab1',
'mixed' => 'live-component_v2',
]);
it('handles complex instance IDs', function () {
$instanceId = 'user-123_session-abc.def';
$id = ComponentId::create('profile', $instanceId);
expect($id->instanceId)->toBe($instanceId);
expect($id->toString())->toBe("profile:{$instanceId}");
});
it('parses component ID with multiple colons correctly', function () {
// Should only split on first colon
$id = ComponentId::fromString('component:instance:with:colons');
expect($id->name)->toBe('component');
expect($id->instanceId)->toBe('instance:with:colons');
});
});