- 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.
244 lines
7.0 KiB
PHP
244 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Console\ExitCode;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Process\Exceptions\ProcessException;
|
|
use App\Framework\Process\SystemProcess;
|
|
use App\Framework\Process\ValueObjects\Command;
|
|
use App\Framework\Process\ValueObjects\EnvironmentVariables;
|
|
|
|
beforeEach(function () {
|
|
$this->logger = Mockery::mock(Logger::class);
|
|
$this->logger->shouldReceive('debug')->andReturn(null);
|
|
|
|
$this->process = new SystemProcess($this->logger);
|
|
});
|
|
|
|
afterEach(function () {
|
|
Mockery::close();
|
|
});
|
|
|
|
describe('SystemProcess - Basic Execution', function () {
|
|
it('can execute simple command successfully', function () {
|
|
$result = $this->process->run(
|
|
Command::fromArray(['echo', 'hello'])
|
|
);
|
|
|
|
expect($result->isSuccess())->toBeTrue()
|
|
->and($result->exitCode)->toBe(ExitCode::SUCCESS)
|
|
->and($result->getOutput())->toContain('hello')
|
|
->and($result->hasOutput())->toBeTrue();
|
|
});
|
|
|
|
it('captures stdout correctly', function () {
|
|
$result = $this->process->run(
|
|
Command::fromArray(['echo', 'test output'])
|
|
);
|
|
|
|
expect($result->stdout)->toContain('test output')
|
|
->and($result->getOutput())->toContain('test output');
|
|
});
|
|
|
|
it('captures stderr correctly', function () {
|
|
$result = $this->process->run(
|
|
Command::fromString('>&2 echo "error message"')
|
|
);
|
|
|
|
expect($result->stderr)->toContain('error message')
|
|
->and($result->getErrorOutput())->toContain('error message')
|
|
->and($result->hasErrors())->toBeTrue();
|
|
});
|
|
|
|
it('handles failed commands', function () {
|
|
$result = $this->process->run(
|
|
Command::fromString('exit 1')
|
|
);
|
|
|
|
expect($result->isFailed())->toBeTrue()
|
|
->and($result->exitCode)->toBe(ExitCode::GENERAL_ERROR);
|
|
});
|
|
|
|
it('tracks execution time', function () {
|
|
$result = $this->process->run(
|
|
Command::fromArray(['sleep', '0.1'])
|
|
);
|
|
|
|
expect($result->runtime->toMilliseconds())->toBeGreaterThan(50);
|
|
});
|
|
});
|
|
|
|
describe('SystemProcess - Working Directory', function () {
|
|
it('can execute command in specific directory', function () {
|
|
$tempDir = sys_get_temp_dir();
|
|
$workingDir = FilePath::create($tempDir);
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromString('pwd'),
|
|
workingDirectory: $workingDir
|
|
);
|
|
|
|
expect($result->isSuccess())->toBeTrue()
|
|
->and($result->stdout)->toContain($tempDir);
|
|
});
|
|
|
|
it('throws exception for invalid working directory', function () {
|
|
$invalidDir = FilePath::create('/nonexistent/directory/path');
|
|
|
|
$this->process->run(
|
|
command: Command::fromArray(['echo', 'test']),
|
|
workingDirectory: $invalidDir
|
|
);
|
|
})->throws(ProcessException::class);
|
|
});
|
|
|
|
describe('SystemProcess - Environment Variables', function () {
|
|
it('can pass environment variables', function () {
|
|
$env = EnvironmentVariables::fromArray([
|
|
'TEST_VAR' => 'test_value',
|
|
]);
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromString('echo $TEST_VAR'),
|
|
env: $env
|
|
);
|
|
|
|
expect($result->stdout)->toContain('test_value');
|
|
});
|
|
|
|
it('merges with existing environment', function () {
|
|
$env = EnvironmentVariables::fromArray([
|
|
'CUSTOM_VAR' => 'custom_value',
|
|
]);
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromString('echo $PATH'),
|
|
env: $env
|
|
);
|
|
|
|
// PATH should still be available
|
|
expect($result->stdout)->not->toBeEmpty();
|
|
});
|
|
});
|
|
|
|
describe('SystemProcess - Timeout', function () {
|
|
it('respects timeout for long-running commands', function () {
|
|
$timeout = Duration::fromSeconds(1);
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['sleep', '0.5']),
|
|
timeout: $timeout
|
|
);
|
|
|
|
expect($result->isSuccess())->toBeTrue()
|
|
->and($result->runtime->toSeconds())->toBeLessThan(1.0);
|
|
});
|
|
|
|
it('handles commands that complete within timeout', function () {
|
|
$timeout = Duration::fromSeconds(5);
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['echo', 'fast']),
|
|
timeout: $timeout
|
|
);
|
|
|
|
expect($result->isSuccess())->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('SystemProcess - Command Existence', function () {
|
|
it('detects existing commands', function () {
|
|
$exists = $this->process->commandExists('echo');
|
|
|
|
expect($exists)->toBeTrue();
|
|
});
|
|
|
|
it('detects non-existing commands', function () {
|
|
$exists = $this->process->commandExists('nonexistent_command_xyz');
|
|
|
|
expect($exists)->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('SystemProcess - Async Execution', function () {
|
|
it('can start process asynchronously', function () {
|
|
$running = $this->process->start(
|
|
Command::fromArray(['sleep', '0.1'])
|
|
);
|
|
|
|
expect($running->isRunning())->toBeTrue()
|
|
->and($running->getPid())->toBeGreaterThan(0);
|
|
|
|
$result = $running->wait();
|
|
|
|
expect($result->isSuccess())->toBeTrue()
|
|
->and($running->isRunning())->toBeFalse();
|
|
});
|
|
|
|
it('can terminate running process', function () {
|
|
$running = $this->process->start(
|
|
Command::fromArray(['sleep', '10'])
|
|
);
|
|
|
|
expect($running->isRunning())->toBeTrue();
|
|
|
|
$running->terminate();
|
|
|
|
// Give it a moment to terminate
|
|
usleep(100000);
|
|
|
|
expect($running->isRunning())->toBeFalse();
|
|
});
|
|
|
|
it('can kill running process', function () {
|
|
$running = $this->process->start(
|
|
Command::fromArray(['sleep', '10'])
|
|
);
|
|
|
|
expect($running->isRunning())->toBeTrue();
|
|
|
|
$running->kill();
|
|
|
|
// Give it a moment to be killed
|
|
usleep(100000);
|
|
|
|
expect($running->isRunning())->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('SystemProcess - ProcessResult', function () {
|
|
it('provides combined output', function () {
|
|
$result = $this->process->run(
|
|
Command::fromString('echo "stdout" && >&2 echo "stderr"')
|
|
);
|
|
|
|
$combined = $result->getCombinedOutput();
|
|
|
|
expect($combined)->toContain('stdout')
|
|
->and($combined)->toContain('stderr');
|
|
});
|
|
|
|
it('converts result to array', function () {
|
|
$result = $this->process->run(
|
|
Command::fromArray(['echo', 'test'])
|
|
);
|
|
|
|
$array = $result->toArray();
|
|
|
|
expect($array)->toHaveKeys([
|
|
'command',
|
|
'stdout',
|
|
'stderr',
|
|
'exit_code',
|
|
'exit_description',
|
|
'runtime_ms',
|
|
'success',
|
|
])
|
|
->and($array['success'])->toBeTrue()
|
|
->and($array['exit_code'])->toBe(0);
|
|
});
|
|
});
|