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,195 @@
<?php
declare(strict_types=1);
use App\Framework\Http\Status;
describe('Batch Endpoint Integration', function () {
it('handles valid batch request', function () {
$requestData = [
'operations' => [
[
'componentId' => 'counter:demo',
'method' => 'increment',
'params' => ['amount' => 5],
'operationId' => 'op-1',
],
[
'componentId' => 'stats:user',
'method' => 'refresh',
'operationId' => 'op-2',
],
],
];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::OK);
$data = $response->jsonData;
expect($data)->toHaveKey('results');
expect($data)->toHaveKey('total_operations');
expect($data)->toHaveKey('success_count');
expect($data)->toHaveKey('failure_count');
expect($data['total_operations'])->toBe(2);
expect($data['results'])->toHaveCount(2);
});
it('returns error for invalid batch request format', function () {
$requestData = [
'invalid' => 'format',
];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::BAD_REQUEST);
$data = $response->jsonData;
expect($data)->toHaveKey('error');
expect($data)->toHaveKey('error_code');
expect($data['error_code'])->toBe('INVALID_BATCH_REQUEST');
});
it('returns error for empty operations', function () {
$requestData = [
'operations' => [],
];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::BAD_REQUEST);
expect($response->jsonData['error_code'])->toBe('INVALID_BATCH_REQUEST');
});
it('returns error for too many operations', function () {
$operations = [];
for ($i = 0; $i < 51; $i++) {
$operations[] = [
'componentId' => "component:$i",
'method' => 'action',
];
}
$requestData = ['operations' => $operations];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::BAD_REQUEST);
expect($response->jsonData['error'])->toContain('cannot exceed 50 operations');
});
it('handles partial failures gracefully', function () {
$requestData = [
'operations' => [
[
'componentId' => 'counter:demo',
'method' => 'increment',
'operationId' => 'op-1',
],
[
'componentId' => 'invalid:component',
'method' => 'action',
'operationId' => 'op-2',
],
[
'componentId' => 'stats:user',
'method' => 'refresh',
'operationId' => 'op-3',
],
],
];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::OK);
$data = $response->jsonData;
expect($data['total_operations'])->toBe(3);
expect($data['success_count'])->toBeGreaterThanOrEqual(1);
expect($data['failure_count'])->toBeGreaterThanOrEqual(1);
// Check that results preserve operation IDs
$operationIds = array_column($data['results'], 'operationId');
expect($operationIds)->toContain('op-1');
expect($operationIds)->toContain('op-2');
expect($operationIds)->toContain('op-3');
});
it('supports fragment-based updates in batch', function () {
$requestData = [
'operations' => [
[
'componentId' => 'counter:demo',
'method' => 'increment',
'fragments' => ['counter-display'],
'operationId' => 'op-1',
],
],
];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::OK);
$data = $response->jsonData;
$result = $data['results'][0];
if ($result['success']) {
// If fragments available, should have fragments instead of html
if (isset($result['fragments'])) {
expect($result['fragments'])->toBeArray();
expect($result)->not->toHaveKey('html');
}
}
});
it('preserves state and events in batch response', function () {
$requestData = [
'operations' => [
[
'componentId' => 'counter:demo',
'method' => 'increment',
'params' => ['amount' => 5],
'operationId' => 'op-1',
],
],
];
$response = $this->post('/live-component/batch', $requestData);
expect($response->status)->toBe(Status::OK);
$result = $response->jsonData['results'][0];
if ($result['success']) {
expect($result)->toHaveKey('state');
expect($result)->toHaveKey('events');
expect($result['state'])->toBeArray();
expect($result['events'])->toBeArray();
}
});
it('handles concurrent batch requests independently', function () {
$request1 = [
'operations' => [
['componentId' => 'counter:demo', 'method' => 'increment', 'operationId' => 'req1-op1'],
],
];
$request2 = [
'operations' => [
['componentId' => 'stats:user', 'method' => 'refresh', 'operationId' => 'req2-op1'],
],
];
$response1 = $this->post('/live-component/batch', $request1);
$response2 = $this->post('/live-component/batch', $request2);
expect($response1->status)->toBe(Status::OK);
expect($response2->status)->toBe(Status::OK);
expect($response1->jsonData['results'][0]['operationId'])->toBe('req1-op1');
expect($response2->jsonData['results'][0]['operationId'])->toBe('req2-op1');
});
});