- 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.
191 lines
5.4 KiB
PHP
191 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Framework\Async;
|
|
|
|
use App\Framework\Async\AsyncPromise;
|
|
use App\Framework\Async\FiberManager;
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\DateTime\SystemTimer;
|
|
|
|
describe('AsyncPromise', function () {
|
|
beforeEach(function () {
|
|
$this->clock = new SystemClock();
|
|
$this->timer = new SystemTimer($this->clock);
|
|
$this->fiberManager = new FiberManager($this->clock, $this->timer);
|
|
});
|
|
|
|
it('creates a resolved promise', function () {
|
|
$promise = AsyncPromise::resolve('test-value', $this->fiberManager);
|
|
|
|
expect($promise->isResolved())->toBeTrue();
|
|
expect($promise->await())->toBe('test-value');
|
|
});
|
|
|
|
it('creates a rejected promise', function () {
|
|
$exception = new \RuntimeException('test error');
|
|
$promise = AsyncPromise::reject($exception, $this->fiberManager);
|
|
|
|
expect($promise->isResolved())->toBeTrue();
|
|
|
|
try {
|
|
$promise->await();
|
|
throw new \Exception('Should have thrown');
|
|
} catch (\RuntimeException $e) {
|
|
expect($e->getMessage())->toBe('test error');
|
|
}
|
|
});
|
|
|
|
it('creates a promise from callable', function () {
|
|
$promise = AsyncPromise::create(
|
|
fn() => 'computed-value',
|
|
$this->fiberManager
|
|
);
|
|
|
|
// Give fiber time to execute
|
|
usleep(10000); // 10ms
|
|
|
|
expect($promise->await())->toBe('computed-value');
|
|
});
|
|
|
|
it('chains then callbacks', function () {
|
|
$promise = AsyncPromise::resolve(5, $this->fiberManager)
|
|
->then(fn($value) => $value * 2)
|
|
->then(fn($value) => $value + 3);
|
|
|
|
usleep(10000); // Give time for chaining
|
|
|
|
expect($promise->await())->toBe(13);
|
|
});
|
|
|
|
it('catches exceptions', function () {
|
|
$promise = AsyncPromise::create(
|
|
fn() => throw new \RuntimeException('error'),
|
|
$this->fiberManager
|
|
)->catch(fn($e) => 'caught: ' . $e->getMessage());
|
|
|
|
usleep(10000);
|
|
|
|
expect($promise->await())->toBe('caught: error');
|
|
});
|
|
|
|
it('executes finally callback', function () {
|
|
$finallyCalled = false;
|
|
|
|
$promise = AsyncPromise::resolve('value', $this->fiberManager)
|
|
->finally(function () use (&$finallyCalled) {
|
|
$finallyCalled = true;
|
|
});
|
|
|
|
usleep(10000);
|
|
$promise->await();
|
|
|
|
expect($finallyCalled)->toBeTrue();
|
|
});
|
|
|
|
it('waits for all promises', function () {
|
|
$promise1 = AsyncPromise::resolve(1, $this->fiberManager);
|
|
$promise2 = AsyncPromise::resolve(2, $this->fiberManager);
|
|
$promise3 = AsyncPromise::resolve(3, $this->fiberManager);
|
|
|
|
$allPromise = AsyncPromise::all(
|
|
[$promise1, $promise2, $promise3],
|
|
$this->fiberManager
|
|
);
|
|
|
|
usleep(10000);
|
|
|
|
$results = $allPromise->await();
|
|
expect($results)->toBe([1, 2, 3]);
|
|
});
|
|
|
|
it('races promises and returns first', function () {
|
|
$slowPromise = AsyncPromise::create(function () {
|
|
usleep(100000); // 100ms
|
|
return 'slow';
|
|
}, $this->fiberManager);
|
|
|
|
$fastPromise = AsyncPromise::create(function () {
|
|
// No sleep - immediate return
|
|
return 'fast';
|
|
}, $this->fiberManager);
|
|
|
|
$racePromise = AsyncPromise::race(
|
|
[$slowPromise, $fastPromise],
|
|
$this->fiberManager
|
|
);
|
|
|
|
usleep(20000); // Wait a bit for fast to complete
|
|
|
|
$result = $racePromise->await();
|
|
// Either fast wins or first to complete
|
|
expect(['fast', 'slow'])->toContain($result);
|
|
});
|
|
|
|
it('handles promise rejection in all', function () {
|
|
$promise1 = AsyncPromise::resolve(1, $this->fiberManager);
|
|
$promise2 = AsyncPromise::reject(
|
|
new \RuntimeException('failed'),
|
|
$this->fiberManager
|
|
);
|
|
|
|
$allPromise = AsyncPromise::all(
|
|
[$promise1, $promise2],
|
|
$this->fiberManager
|
|
);
|
|
|
|
usleep(10000);
|
|
|
|
try {
|
|
$allPromise->await();
|
|
throw new \Exception('Should have thrown');
|
|
} catch (\RuntimeException $e) {
|
|
expect($e->getMessage())->toBe('failed');
|
|
}
|
|
});
|
|
|
|
it('provides promise statistics', function () {
|
|
$promise = AsyncPromise::create(
|
|
fn() => 'value',
|
|
$this->fiberManager
|
|
);
|
|
|
|
usleep(10000);
|
|
|
|
$stats = $promise->getStats();
|
|
|
|
expect($stats)->toHaveKey('resolved');
|
|
expect($stats)->toHaveKey('has_result');
|
|
expect($stats)->toHaveKey('has_exception');
|
|
});
|
|
|
|
it('chains multiple then callbacks with different return types', function () {
|
|
$promise = AsyncPromise::resolve(10, $this->fiberManager)
|
|
->then(fn($x) => $x * 2) // 20
|
|
->then(fn($x) => (string) $x) // "20"
|
|
->then(fn($x) => $x . '!') // "20!"
|
|
->then(fn($x) => strlen($x)); // 3
|
|
|
|
usleep(10000);
|
|
|
|
expect($promise->await())->toBe(3);
|
|
});
|
|
|
|
it('handles nested promises', function () {
|
|
$innerPromise = AsyncPromise::create(
|
|
fn() => 'inner',
|
|
$this->fiberManager
|
|
);
|
|
|
|
$outerPromise = AsyncPromise::create(
|
|
fn() => $innerPromise->await() . '-outer',
|
|
$this->fiberManager
|
|
);
|
|
|
|
usleep(20000);
|
|
|
|
expect($outerPromise->await())->toBe('inner-outer');
|
|
});
|
|
});
|