- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
158 lines
5.6 KiB
PHP
158 lines
5.6 KiB
PHP
<?php
|
|
|
|
use App\Application\GraphQL\NotificationPublisher;
|
|
use App\Application\GraphQL\NotificationSubscriptions;
|
|
use App\Framework\GraphQL\Attributes\GraphQLSubscription;
|
|
use App\Framework\GraphQL\Execution\QueryParser;
|
|
use App\Framework\GraphQL\Schema\SchemaBuilder;
|
|
use App\Framework\GraphQL\Subscriptions\SubscriptionId;
|
|
use App\Framework\GraphQL\Subscriptions\SubscriptionManager;
|
|
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');
|
|
});
|
|
});
|