- 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.
157 lines
5.4 KiB
PHP
157 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Application\GraphQL\NotificationSubscriptions;
|
|
use App\Framework\GraphQL\Attributes\GraphQLSubscription;
|
|
use App\Framework\GraphQL\Schema\SchemaBuilder;
|
|
use App\Framework\GraphQL\Subscriptions\SubscriptionId;
|
|
use App\Framework\GraphQL\Subscriptions\SubscriptionRegistry;
|
|
use App\Framework\Http\WebSocketConnectionInterface;
|
|
|
|
describe('GraphQL Subscriptions', function () {
|
|
beforeEach(function () {
|
|
$this->registry = new SubscriptionRegistry();
|
|
|
|
// Create mock WebSocket connection using Mockery
|
|
$this->connection = Mockery::mock(WebSocketConnectionInterface::class);
|
|
$this->connection->shouldReceive('getId')->andReturn('test-connection-1');
|
|
$this->connection->shouldReceive('isConnected')->andReturn(true);
|
|
$this->connection->shouldReceive('sendJson')->andReturnTrue();
|
|
$this->connection->shouldReceive('setAttribute')->andReturnNull();
|
|
$this->connection->shouldReceive('getAttribute')->andReturn(null);
|
|
|
|
$this->sentMessages = [];
|
|
});
|
|
|
|
it('can register and retrieve subscriptions', function () {
|
|
$id = SubscriptionId::generate();
|
|
|
|
$this->registry->register(
|
|
$id,
|
|
$this->connection,
|
|
'notificationReceived',
|
|
['userId' => '123']
|
|
);
|
|
|
|
$subscriptions = $this->registry->getByName('notificationReceived');
|
|
|
|
expect($subscriptions)->toHaveCount(1);
|
|
expect($subscriptions[0]['id']->toString())->toBe($id->toString());
|
|
expect($subscriptions[0]['variables']['userId'])->toBe('123');
|
|
});
|
|
|
|
it('can unregister subscriptions', function () {
|
|
$id = SubscriptionId::generate();
|
|
|
|
$this->registry->register(
|
|
$id,
|
|
$this->connection,
|
|
'notificationReceived',
|
|
['userId' => '123']
|
|
);
|
|
|
|
expect($this->registry->getByName('notificationReceived'))->toHaveCount(1);
|
|
|
|
$this->registry->unregister($id);
|
|
|
|
expect($this->registry->getByName('notificationReceived'))->toHaveCount(0);
|
|
});
|
|
|
|
it('can cleanup subscriptions by connection', function () {
|
|
$id1 = SubscriptionId::generate();
|
|
$id2 = SubscriptionId::generate();
|
|
|
|
$this->registry->register(
|
|
$id1,
|
|
$this->connection,
|
|
'notificationReceived',
|
|
['userId' => '123']
|
|
);
|
|
|
|
$this->registry->register(
|
|
$id2,
|
|
$this->connection,
|
|
'orderStatusChanged',
|
|
['orderId' => 'order-1']
|
|
);
|
|
|
|
expect($this->registry->getByName('notificationReceived'))->toHaveCount(1);
|
|
expect($this->registry->getByName('orderStatusChanged'))->toHaveCount(1);
|
|
|
|
$this->registry->removeByConnection($this->connection);
|
|
|
|
expect($this->registry->getByName('notificationReceived'))->toHaveCount(0);
|
|
expect($this->registry->getByName('orderStatusChanged'))->toHaveCount(0);
|
|
});
|
|
|
|
it('provides subscription statistics', function () {
|
|
$this->registry->register(
|
|
SubscriptionId::generate(),
|
|
$this->connection,
|
|
'notificationReceived',
|
|
['userId' => '123']
|
|
);
|
|
|
|
$this->registry->register(
|
|
SubscriptionId::generate(),
|
|
$this->connection,
|
|
'orderStatusChanged',
|
|
['orderId' => 'order-1']
|
|
);
|
|
|
|
$stats = $this->registry->getStats();
|
|
|
|
expect($stats['total_subscriptions'])->toBe(2);
|
|
expect($stats['unique_connections'])->toBe(1);
|
|
expect($stats['subscriptions_by_type']['notificationReceived'])->toBe(1);
|
|
expect($stats['subscriptions_by_type']['orderStatusChanged'])->toBe(1);
|
|
});
|
|
|
|
it('discovers GraphQLSubscription classes', function () {
|
|
$reflection = new ReflectionClass(NotificationSubscriptions::class);
|
|
$attribute = $reflection->getAttributes(GraphQLSubscription::class)[0] ?? null;
|
|
|
|
expect($attribute)->not->toBeNull();
|
|
|
|
$instance = $attribute->newInstance();
|
|
expect($instance->description)->toBe('Real-time notification subscriptions');
|
|
});
|
|
|
|
it('builds subscription fields in schema', function () {
|
|
// Create simple container for testing
|
|
$container = new \App\Framework\DI\DefaultContainer();
|
|
|
|
$typeResolver = new \App\Framework\GraphQL\Schema\TypeResolver();
|
|
$schemaBuilder = new SchemaBuilder($container, $typeResolver);
|
|
|
|
$schema = $schemaBuilder->build([
|
|
NotificationSubscriptions::class,
|
|
]);
|
|
|
|
expect($schema->subscriptions)->toHaveCount(3);
|
|
expect($schema->getSubscription('notificationReceived'))->not->toBeNull();
|
|
expect($schema->getSubscription('orderStatusChanged'))->not->toBeNull();
|
|
expect($schema->getSubscription('systemAnnouncement'))->not->toBeNull();
|
|
});
|
|
|
|
it('generates correct SDL for subscriptions', function () {
|
|
// Create simple container for testing
|
|
$container = new \App\Framework\DI\DefaultContainer();
|
|
|
|
$typeResolver = new \App\Framework\GraphQL\Schema\TypeResolver();
|
|
$schemaBuilder = new SchemaBuilder($container, $typeResolver);
|
|
|
|
$schema = $schemaBuilder->build([
|
|
NotificationSubscriptions::class,
|
|
]);
|
|
|
|
$sdl = $schema->toSDL();
|
|
|
|
expect($sdl)->toContain('type Subscription {');
|
|
expect($sdl)->toContain('notificationReceived');
|
|
expect($sdl)->toContain('orderStatusChanged');
|
|
expect($sdl)->toContain('systemAnnouncement');
|
|
});
|
|
});
|