- 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.
221 lines
6.0 KiB
PHP
221 lines
6.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\GraphQL\Attributes\GraphQLField;
|
|
use App\Framework\GraphQL\Attributes\GraphQLQuery;
|
|
use App\Framework\GraphQL\Attributes\GraphQLType;
|
|
use App\Framework\GraphQL\Execution\QueryExecutor;
|
|
use App\Framework\GraphQL\Execution\QueryParser;
|
|
use App\Framework\GraphQL\Schema\Schema;
|
|
use App\Framework\GraphQL\Schema\SchemaBuilder;
|
|
use App\Framework\GraphQL\Schema\TypeResolver;
|
|
|
|
describe('GraphQL System', function () {
|
|
|
|
beforeEach(function () {
|
|
$this->container = new DefaultContainer();
|
|
$this->typeResolver = new TypeResolver();
|
|
$this->schemaBuilder = new SchemaBuilder($this->container, $this->typeResolver);
|
|
});
|
|
|
|
it('builds schema from GraphQL type classes', function () {
|
|
$schema = $this->schemaBuilder->build([TestUserType::class]);
|
|
|
|
expect($schema)->toBeInstanceOf(Schema::class);
|
|
$stats = $schema->getStats();
|
|
expect($stats['types_count'])->toBeGreaterThan(0);
|
|
});
|
|
|
|
it('parses simple GraphQL query', function () {
|
|
$parser = new QueryParser();
|
|
|
|
$query = <<<'GRAPHQL'
|
|
{
|
|
user(id: 1) {
|
|
id
|
|
name
|
|
email
|
|
}
|
|
}
|
|
GRAPHQL;
|
|
|
|
$parsed = $parser->parse($query);
|
|
|
|
expect($parsed->fields)->toHaveCount(1);
|
|
expect($parsed->fields[0]->name)->toBe('user');
|
|
expect($parsed->fields[0]->arguments)->toBe(['id' => 1]);
|
|
expect($parsed->fields[0]->selections)->toHaveCount(3);
|
|
});
|
|
|
|
it('parses query with variables', function () {
|
|
$parser = new QueryParser();
|
|
|
|
$query = <<<'GRAPHQL'
|
|
query GetUser($id: Int!) {
|
|
user(id: $id) {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
GRAPHQL;
|
|
|
|
$parsed = $parser->parse($query);
|
|
|
|
expect($parsed->variables)->toHaveKey('id');
|
|
expect($parsed->fields[0]->arguments)->toBe(['id' => '$id']);
|
|
});
|
|
|
|
it('executes simple query against schema', function () {
|
|
// Build schema with test query
|
|
$schema = $this->schemaBuilder->build([
|
|
TestUserType::class,
|
|
TestUserQueries::class,
|
|
]);
|
|
|
|
// Register test service
|
|
$this->container->singleton(TestUserService::class, new TestUserService());
|
|
|
|
$executor = new QueryExecutor($schema);
|
|
$parser = new QueryParser();
|
|
|
|
$query = <<<'GRAPHQL'
|
|
{
|
|
user(id: 1) {
|
|
id
|
|
name
|
|
email
|
|
}
|
|
}
|
|
GRAPHQL;
|
|
|
|
$parsed = $parser->parse($query);
|
|
$result = $executor->execute($parsed);
|
|
|
|
expect($result->isSuccessful())->toBeTrue();
|
|
expect($result->data)->toHaveKey('user');
|
|
expect($result->data['user']['id'])->toBe(1);
|
|
expect($result->data['user']['name'])->toBe('Test User');
|
|
});
|
|
|
|
it('handles query with variables', function () {
|
|
$schema = $this->schemaBuilder->build([
|
|
TestUserType::class,
|
|
TestUserQueries::class,
|
|
]);
|
|
|
|
$this->container->singleton(TestUserService::class, new TestUserService());
|
|
|
|
$executor = new QueryExecutor($schema);
|
|
$parser = new QueryParser();
|
|
|
|
$query = <<<'GRAPHQL'
|
|
query GetUser($userId: Int!) {
|
|
user(id: $userId) {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
GRAPHQL;
|
|
|
|
$parsed = $parser->parse($query);
|
|
$result = $executor->execute($parsed, ['userId' => 1]);
|
|
|
|
expect($result->isSuccessful())->toBeTrue();
|
|
expect($result->data['user']['id'])->toBe(1);
|
|
});
|
|
|
|
it('generates correct GraphQL SDL', function () {
|
|
$schema = $this->schemaBuilder->build([
|
|
TestUserType::class,
|
|
TestUserQueries::class,
|
|
]);
|
|
|
|
$sdl = $schema->toSDL();
|
|
|
|
expect($sdl)->toContain('type TestUser {');
|
|
expect($sdl)->toContain('id: Int!');
|
|
expect($sdl)->toContain('name: String!');
|
|
expect($sdl)->toContain('type Query {');
|
|
expect($sdl)->toContain('user(id: Int!): TestUser');
|
|
});
|
|
|
|
it('handles errors gracefully', function () {
|
|
$schema = $this->schemaBuilder->build([
|
|
TestUserType::class,
|
|
TestUserQueries::class,
|
|
]);
|
|
|
|
$this->container->singleton(TestUserService::class, new TestUserService());
|
|
|
|
$executor = new QueryExecutor($schema);
|
|
$parser = new QueryParser();
|
|
|
|
// Query non-existent field
|
|
$query = '{ nonExistentField }';
|
|
$parsed = $parser->parse($query);
|
|
$result = $executor->execute($parsed);
|
|
|
|
expect($result->isSuccessful())->toBeFalse();
|
|
expect($result->errors)->not->toBeNull();
|
|
});
|
|
});
|
|
|
|
// Test classes for GraphQL system
|
|
|
|
#[GraphQLType(description: 'Test user type')]
|
|
final readonly class TestUserType
|
|
{
|
|
public function __construct(
|
|
#[GraphQLField(description: 'User ID')]
|
|
public int $id,
|
|
#[GraphQLField(description: 'User name')]
|
|
public string $name,
|
|
#[GraphQLField(description: 'User email')]
|
|
public string $email
|
|
) {
|
|
}
|
|
}
|
|
|
|
#[GraphQLQuery]
|
|
final readonly class TestUserQueries
|
|
{
|
|
public function __construct(
|
|
private TestUserService $userService
|
|
) {
|
|
}
|
|
|
|
#[GraphQLField(description: 'Get user by ID')]
|
|
public function user(int $id): TestUserType
|
|
{
|
|
return $this->userService->findById($id);
|
|
}
|
|
|
|
#[GraphQLField(description: 'Get all users')]
|
|
public function users(int $limit = 10): array
|
|
{
|
|
return $this->userService->findAll($limit);
|
|
}
|
|
}
|
|
|
|
final readonly class TestUserService
|
|
{
|
|
public function findById(int $id): TestUserType
|
|
{
|
|
return new TestUserType(
|
|
id: $id,
|
|
name: 'Test User',
|
|
email: 'test@example.com'
|
|
);
|
|
}
|
|
|
|
public function findAll(int $limit): array
|
|
{
|
|
return [
|
|
new TestUserType(1, 'User 1', 'user1@example.com'),
|
|
new TestUserType(2, 'User 2', 'user2@example.com'),
|
|
];
|
|
}
|
|
}
|