- 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.
272 lines
8.2 KiB
PHP
272 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\OutputBuffer\OutputBuffer;
|
|
use App\Framework\OutputBuffer\OutputBufferConfig;
|
|
use App\Framework\OutputBuffer\OutputBufferException;
|
|
use App\Framework\OutputBuffer\OutputBufferFlag;
|
|
use App\Framework\OutputBuffer\OutputBufferFlags;
|
|
use App\Framework\OutputBuffer\CapturedOutput;
|
|
|
|
describe('OutputBuffer', function () {
|
|
beforeEach(function () {
|
|
// Ensure clean state
|
|
while (ob_get_level() > 0) {
|
|
ob_end_clean();
|
|
}
|
|
});
|
|
|
|
afterEach(function () {
|
|
// Cleanup after tests
|
|
while (ob_get_level() > 0) {
|
|
ob_end_clean();
|
|
}
|
|
});
|
|
|
|
describe('capture()', function () {
|
|
it('captures output from callback', function () {
|
|
$captured = OutputBuffer::capture(function () {
|
|
echo "Hello World";
|
|
return 42;
|
|
});
|
|
|
|
expect($captured)->toBeInstanceOf(CapturedOutput::class);
|
|
expect($captured->content)->toBe("Hello World");
|
|
expect($captured->result)->toBe(42);
|
|
});
|
|
|
|
it('captures empty output', function () {
|
|
$captured = OutputBuffer::capture(function () {
|
|
return "result";
|
|
});
|
|
|
|
expect($captured->content)->toBe("");
|
|
expect($captured->result)->toBe("result");
|
|
expect($captured->isEmpty())->toBeTrue();
|
|
});
|
|
|
|
it('captures multiline output', function () {
|
|
$captured = OutputBuffer::capture(function () {
|
|
echo "Line 1\n";
|
|
echo "Line 2\n";
|
|
echo "Line 3";
|
|
});
|
|
|
|
expect($captured->content)->toBe("Line 1\nLine 2\nLine 3");
|
|
expect($captured->lineCount())->toBe(3);
|
|
expect($captured->lines())->toBe(['Line 1', 'Line 2', 'Line 3']);
|
|
});
|
|
|
|
it('cleans up buffer on success', function () {
|
|
$levelBefore = ob_get_level();
|
|
|
|
OutputBuffer::capture(function () {
|
|
echo "test";
|
|
});
|
|
|
|
expect(ob_get_level())->toBe($levelBefore);
|
|
});
|
|
|
|
it('cleans up buffer on exception', function () {
|
|
$levelBefore = ob_get_level();
|
|
|
|
try {
|
|
OutputBuffer::capture(function () {
|
|
echo "test";
|
|
throw new RuntimeException('Test exception');
|
|
});
|
|
} catch (RuntimeException $e) {
|
|
// Expected exception
|
|
}
|
|
|
|
expect(ob_get_level())->toBe($levelBefore);
|
|
});
|
|
|
|
it('supports custom configuration', function () {
|
|
$callbackExecuted = false;
|
|
|
|
$config = OutputBufferConfig::withCallback(function (string $buffer) use (&$callbackExecuted) {
|
|
$callbackExecuted = true;
|
|
return strtoupper($buffer);
|
|
});
|
|
|
|
$captured = OutputBuffer::capture(function () {
|
|
echo "hello";
|
|
}, $config);
|
|
|
|
expect($callbackExecuted)->toBeTrue();
|
|
expect($captured->content)->toBe("HELLO");
|
|
});
|
|
|
|
it('handles nested captures', function () {
|
|
$captured1 = OutputBuffer::capture(function () {
|
|
echo "Outer Start\n";
|
|
|
|
$captured2 = OutputBuffer::capture(function () {
|
|
echo "Inner";
|
|
return "inner-result";
|
|
});
|
|
|
|
echo $captured2->content . " processed\n";
|
|
echo "Outer End";
|
|
|
|
return "outer-result";
|
|
});
|
|
|
|
expect($captured1->content)->toBe("Outer Start\nInner processed\nOuter End");
|
|
expect($captured1->result)->toBe("outer-result");
|
|
});
|
|
});
|
|
|
|
describe('start()', function () {
|
|
it('starts new buffer and returns handle', function () {
|
|
$levelBefore = ob_get_level();
|
|
$handle = OutputBuffer::start();
|
|
|
|
expect(ob_get_level())->toBe($levelBefore + 1);
|
|
expect($handle->level)->toBe($levelBefore + 1);
|
|
expect($handle->isActive())->toBeTrue();
|
|
|
|
$handle->end();
|
|
});
|
|
|
|
it('allows manual buffer management', function () {
|
|
$handle = OutputBuffer::start();
|
|
|
|
echo "Test output";
|
|
$content = $handle->clean();
|
|
|
|
expect($content)->toBe("Test output");
|
|
expect(ob_get_level())->toBe(0);
|
|
});
|
|
|
|
it('supports custom configuration', function () {
|
|
$config = OutputBufferConfig::forCapture();
|
|
$handle = OutputBuffer::start($config);
|
|
|
|
expect($handle->config)->toBe($config);
|
|
|
|
$handle->end();
|
|
});
|
|
});
|
|
|
|
describe('getCurrentLevel()', function () {
|
|
it('returns current buffer level', function () {
|
|
expect(OutputBuffer::getCurrentLevel())->toBe(0);
|
|
|
|
ob_start();
|
|
expect(OutputBuffer::getCurrentLevel())->toBe(1);
|
|
|
|
ob_start();
|
|
expect(OutputBuffer::getCurrentLevel())->toBe(2);
|
|
|
|
ob_end_clean();
|
|
ob_end_clean();
|
|
});
|
|
});
|
|
|
|
describe('isActive()', function () {
|
|
it('returns false when no buffers active', function () {
|
|
expect(OutputBuffer::isActive())->toBeFalse();
|
|
});
|
|
|
|
it('returns true when buffer is active', function () {
|
|
ob_start();
|
|
expect(OutputBuffer::isActive())->toBeTrue();
|
|
ob_end_clean();
|
|
});
|
|
});
|
|
|
|
describe('cleanAll()', function () {
|
|
it('cleans all active buffers', function () {
|
|
ob_start();
|
|
ob_start();
|
|
ob_start();
|
|
|
|
expect(ob_get_level())->toBe(3);
|
|
|
|
$count = OutputBuffer::cleanAll();
|
|
|
|
expect($count)->toBe(3);
|
|
expect(ob_get_level())->toBe(0);
|
|
});
|
|
|
|
it('returns 0 when no buffers active', function () {
|
|
$count = OutputBuffer::cleanAll();
|
|
expect($count)->toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getStatus()', function () {
|
|
it('returns status of all buffers', function () {
|
|
$status = OutputBuffer::getStatus();
|
|
expect($status)->toBeArray();
|
|
});
|
|
|
|
it('shows buffer information when active', function () {
|
|
ob_start();
|
|
$status = OutputBuffer::getStatus();
|
|
|
|
expect($status)->toBeArray();
|
|
expect($status)->toHaveCount(1);
|
|
|
|
ob_end_clean();
|
|
});
|
|
});
|
|
|
|
describe('withoutBuffering()', function () {
|
|
it('executes callback without buffering', function () {
|
|
$executedWithoutBuffer = false;
|
|
|
|
OutputBuffer::capture(function () use (&$executedWithoutBuffer) {
|
|
OutputBuffer::withoutBuffering(function () use (&$executedWithoutBuffer) {
|
|
$executedWithoutBuffer = ob_get_level() === 0;
|
|
});
|
|
});
|
|
|
|
expect($executedWithoutBuffer)->toBeTrue();
|
|
});
|
|
|
|
it('restores buffers after callback', function () {
|
|
$captured = OutputBuffer::capture(function () {
|
|
echo "Before";
|
|
|
|
OutputBuffer::withoutBuffering(function () {
|
|
// This output should not be captured
|
|
echo "During";
|
|
});
|
|
|
|
echo "After";
|
|
});
|
|
|
|
expect($captured->content)->toBe("BeforeAfter");
|
|
});
|
|
|
|
it('returns callback result', function () {
|
|
$result = OutputBuffer::withoutBuffering(function () {
|
|
return 42;
|
|
});
|
|
|
|
expect($result)->toBe(42);
|
|
});
|
|
});
|
|
|
|
describe('exception handling', function () {
|
|
it('throws exception when capture fails', function () {
|
|
// This is hard to test as ob_start rarely fails
|
|
// but we test the exception exists
|
|
expect(OutputBufferException::failedToCapture())
|
|
->toBeInstanceOf(OutputBufferException::class);
|
|
});
|
|
|
|
it('propagates callback exceptions', function () {
|
|
expect(function () {
|
|
OutputBuffer::capture(function () {
|
|
throw new RuntimeException('Test');
|
|
});
|
|
})->toThrow(RuntimeException::class);
|
|
});
|
|
});
|
|
});
|