View source  
  <?php
namespace Drupal\Tests\graphql\Traits;
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Plugin\Factory\FactoryInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\graphql\Annotation\GraphQLEnum;
use Drupal\graphql\Annotation\GraphQLField;
use Drupal\graphql\Annotation\GraphQLInputType;
use Drupal\graphql\Annotation\GraphQLInterface;
use Drupal\graphql\Annotation\GraphQLMutation;
use Drupal\graphql\Annotation\GraphQLType;
use Drupal\graphql\Annotation\GraphQLUnionType;
use Drupal\graphql\Plugin\GraphQL\Enums\EnumPluginBase;
use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
use Drupal\graphql\Plugin\GraphQL\InputTypes\InputTypePluginBase;
use Drupal\graphql\Plugin\GraphQL\Interfaces\InterfacePluginBase;
use Drupal\graphql\Plugin\GraphQL\Mutations\MutationPluginBase;
use Drupal\graphql\Plugin\GraphQL\Scalars\ScalarPluginBase;
use Drupal\graphql\Plugin\GraphQL\Schemas\SchemaPluginBase;
use Drupal\graphql\Plugin\GraphQL\Subscriptions\SubscriptionPluginBase;
use Drupal\graphql\Plugin\GraphQL\Types\TypePluginBase;
use Drupal\graphql\Plugin\GraphQL\Unions\UnionTypePluginBase;
trait MockGraphQLPluginTrait {
  
  protected $graphQLPlugins = [];
  protected $graphQLPluginManagers = [];
  protected $graphqlPluginDecorators = [];
  
  protected function resetStaticCaches() {
    $definitionsProperty = new \ReflectionProperty(DefaultPluginManager::class, 'definitions');
    $definitionsProperty
      ->setAccessible(TRUE);
    foreach ($this->graphQLPluginManagers as $manager) {
      $definitionsProperty
        ->setValue($manager, NULL);
    }
    $deriversProperty = new \ReflectionProperty(DerivativeDiscoveryDecorator::class, 'derivers');
    $deriversProperty
      ->setAccessible(TRUE);
    foreach ($this->graphqlPluginDecorators as $decorator) {
      $deriversProperty
        ->setValue($decorator, NULL);
    }
  }
  
  protected $graphQLPluginClassMap = [
    'plugin.manager.graphql.schema' => SchemaPluginBase::class,
    'plugin.manager.graphql.field' => FieldPluginBase::class,
    'plugin.manager.graphql.mutation' => MutationPluginBase::class,
    'plugin.manager.graphql.subscription' => SubscriptionPluginBase::class,
    'plugin.manager.graphql.union' => UnionTypePluginBase::class,
    'plugin.manager.graphql.interface' => InterfacePluginBase::class,
    'plugin.manager.graphql.type' => TypePluginBase::class,
    'plugin.manager.graphql.input' => InputTypePluginBase::class,
    'plugin.manager.graphql.scalar' => ScalarPluginBase::class,
    'plugin.manager.graphql.enum' => EnumPluginBase::class,
  ];
  
  protected function injectTypeSystemPluginManagers(ContainerBuilder $container) {
    foreach ($this->graphQLPluginClassMap as $id => $class) {
      $this->graphQLPlugins[$class] = [];
      
      $manager = $container
        ->get($id);
      $this->graphQLPluginManagers[$id] = $manager;
      
      $factoryMethod = new \ReflectionMethod($manager, 'getFactory');
      $factoryMethod
        ->setAccessible(TRUE);
      $factoryProp = new \ReflectionProperty($manager, 'factory');
      $factoryProp
        ->setAccessible(TRUE);
      $discoveryMethod = new \ReflectionMethod($manager, 'getDiscovery');
      $discoveryMethod
        ->setAccessible(TRUE);
      $discoveryProp = new \ReflectionProperty($manager, 'discovery');
      $discoveryProp
        ->setAccessible(TRUE);
      
      $factory = $factoryMethod
        ->invoke($manager);
      
      $discovery = $discoveryMethod
        ->invoke($manager);
      $decoratedProp = new \ReflectionProperty(DerivativeDiscoveryDecorator::class, 'decorated');
      $decoratedProp
        ->setAccessible(TRUE);
      $unwrappedDiscovery = $decoratedProp
        ->getValue($discovery);
      $this->graphQLPlugins[$class] = [];
      $mockFactory = $this
        ->getMockBuilder(FactoryInterface::class)
        ->setMethods([
        'createInstance',
      ])
        ->getMock();
      $mockDiscovery = $this
        ->getMockBuilder(DiscoveryInterface::class)
        ->setMethods([
        'hasDefinition',
        'getDefinitions',
        'getDefinition',
      ])
        ->getMock();
      $decoratedDiscovery = new ContainerDerivativeDiscoveryDecorator($mockDiscovery);
      $this->graphqlPluginDecorators[$id] = $decoratedDiscovery;
      $mockDiscovery
        ->expects(static::any())
        ->method('getDefinitions')
        ->willReturnCallback(function () use ($class, $unwrappedDiscovery) {
        $mockDefinitions = array_map(function ($plugin) {
          return $plugin['definition'];
        }, $this->graphQLPlugins[$class]);
        $realDefinitions = $unwrappedDiscovery
          ->getDefinitions();
        return array_merge($mockDefinitions, $realDefinitions);
      });
      $mockDiscovery
        ->expects(static::any())
        ->method('hasDefinition')
        ->with(static::anything())
        ->willReturnCallback(function ($pluginId) use ($class, $discovery) {
        $basePluginId = $this
          ->getBasePluginId($pluginId);
        return isset($this->graphQLPlugins[$class][$basePluginId]) || $discovery
          ->hasDefinition($pluginId);
      });
      $mockDiscovery
        ->expects(static::any())
        ->method('getDefinition')
        ->with(static::anything(), static::anything())
        ->willReturnCallback(function ($pluginId, $except) use ($class, $discovery) {
        $basePluginId = $this
          ->getBasePluginId($pluginId);
        if (array_key_exists($basePluginId, $this->graphQLPlugins[$class])) {
          return $this->graphQLPlugins[$class][$basePluginId]['definition'];
        }
        return $discovery
          ->getDefinition($pluginId, $except);
      });
      $discoveryProp
        ->setValue($manager, $decoratedDiscovery);
      $mockFactory
        ->expects(static::any())
        ->method('createInstance')
        ->with(static::anything(), static::anything())
        ->willReturnCallback(function ($pluginId, $configuration) use ($class, $factory, $decoratedDiscovery) {
        $basePluginId = $this
          ->getBasePluginId($pluginId);
        if (array_key_exists($basePluginId, $this->graphQLPlugins[$class])) {
          $definition = $decoratedDiscovery
            ->getDefinition($pluginId);
          $args = $this->graphQLPlugins[$class][$basePluginId];
          $args['definition'] = $definition;
          return call_user_func_array([
            $this,
            $definition['mock_factory'],
          ], $args);
        }
        return $factory
          ->createInstance($pluginId, $configuration);
      });
      $factoryProp
        ->setValue($manager, $mockFactory);
    }
  }
  
  private function getBasePluginId($pluginId) {
    return strpos($pluginId, ':') ? explode(':', $pluginId)[0] : $pluginId;
  }
  
  protected function getTypeSystemPluginDefinition($annotationClass, array $definition) {
    return (new $annotationClass($definition))
      ->get();
  }
  
  protected function addTypeSystemPlugin(PluginInspectionInterface $plugin) {
    foreach ($this->graphQLPluginClassMap as $id => $class) {
      if ($plugin instanceof $class) {
        $this->graphQLPlugins[$id][$plugin
          ->getPluginId()] = $plugin;
      }
    }
  }
  
  protected function toPromise($value) {
    return $this
      ->returnCallback(is_callable($value) ? $value : function () use ($value) {
      (yield $value);
    });
  }
  
  protected function toBoundPromise($value, $scope) {
    return $this
      ->toPromise(is_callable($value) ? \Closure::bind($value, $scope, $scope) : $value);
  }
  
  protected function mockSchema($id, $builder = NULL) {
    $this->graphQLPlugins[SchemaPluginBase::class][$id] = [
      'definition' => $this
        ->getSchemaDefinitions()[$id] + [
        'mock_factory' => 'mockSchemaFactory',
      ],
      'builder' => $builder,
    ];
  }
  protected function mockSchemaFactory($definition, $builder) {
    $schema = $this
      ->getMockForAbstractClass(SchemaPluginBase::class, [
      [],
      $definition['id'],
      $definition,
      $this->container
        ->get('plugin.manager.graphql.field'),
      $this->container
        ->get('plugin.manager.graphql.mutation'),
      $this->container
        ->get('plugin.manager.graphql.subscription'),
      $this->container
        ->get('graphql.type_manager_aggregator'),
      $this->container
        ->get('graphql.query_provider'),
      $this->container
        ->get('current_user'),
      $this->container
        ->get('logger.channel.graphql'),
      $this->container
        ->get('language_manager'),
      $this->container
        ->getParameter('graphql.config'),
    ]);
    if (is_callable($builder)) {
      $builder($schema);
    }
    return $schema;
  }
  
  protected function mockField($id, $definition, $result = NULL, $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLField::class, $definition + [
      'secure' => TRUE,
      'id' => $id,
      'class' => FieldPluginBase::class,
      'mock_factory' => 'mockFieldFactory',
    ]);
    $this->graphQLPlugins[FieldPluginBase::class][$id] = [
      'definition' => $definition,
      'result' => $result,
      'builder' => $builder,
    ];
  }
  protected function mockFieldFactory($definition, $result = NULL, $builder = NULL) {
    $field = $this
      ->getMockBuilder(FieldPluginBase::class)
      ->setConstructorArgs([
      [],
      $definition['id'],
      $definition,
    ])
      ->setMethods([
      'resolveValues',
    ])
      ->getMock();
    if (isset($result)) {
      $field
        ->expects(static::any())
        ->method('resolveValues')
        ->with(static::anything(), static::anything(), static::anything(), static::anything())
        ->will($this
        ->toBoundPromise($result, $field));
    }
    if (is_callable($builder)) {
      $builder($field);
    }
    return $field;
  }
  
  protected function mockType($id, array $definition, $applies = TRUE, $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLType::class, $definition + [
      'id' => $id,
      'class' => TypePluginBase::class,
      'mock_factory' => 'mockTypeFactory',
    ]);
    $this->graphQLPlugins[TypePluginBase::class][$id] = [
      'definition' => $definition,
      'applies' => $applies,
      'builder' => $builder,
    ];
  }
  protected function mockTypeFactory($definition, $applies = TRUE, $builder = NULL) {
    $type = $this
      ->getMockBuilder(TypePluginBase::class)
      ->setConstructorArgs([
      [],
      $definition['id'],
      $definition,
    ])
      ->setMethods([
      'applies',
    ])
      ->getMock();
    $type
      ->expects(static::any())
      ->method('applies')
      ->with($this
      ->anything(), $this
      ->anything())
      ->will($this
      ->toBoundPromise($applies, $type));
    if (is_callable($builder)) {
      $builder($type);
    }
    return $type;
  }
  
  protected function mockInputType($id, array $definition, $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLInputType::class, $definition + [
      'id' => $id,
      'class' => InputTypePluginBase::class,
      'mock_factory' => 'mockInputTypeFactory',
    ]);
    $this->graphQLPlugins[InputTypePluginBase::class][$id] = [
      'definition' => $definition,
      'builder' => $builder,
    ];
  }
  protected function mockInputTypeFactory($definition, $builder) {
    $input = $this
      ->getMockForAbstractClass(InputTypePluginBase::class, [
      [],
      $definition['id'],
      $definition,
    ]);
    if (is_callable($builder)) {
      $builder($input);
    }
    return $input;
  }
  
  protected function mockMutation($id, array $definition, $result = NULL, $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLMutation::class, $definition + [
      'id' => $id,
      'class' => MutationPluginBase::class,
      'mock_factory' => 'mockMutationFactory',
    ]);
    $this->graphQLPlugins[MutationPluginBase::class][$id] = [
      'definition' => $definition,
      'result' => $result,
      'builder' => $builder,
    ];
  }
  protected function mockMutationFactory($definition, $result = NULL, $builder = NULL) {
    $mutation = $this
      ->getMockBuilder(MutationPluginBase::class)
      ->setConstructorArgs([
      [],
      $definition['id'],
      $definition,
    ])
      ->setMethods([
      'resolve',
    ])
      ->getMock();
    if (isset($result)) {
      $mutation
        ->expects(static::any())
        ->method('resolve')
        ->with(static::anything(), static::anything(), static::anything(), static::anything())
        ->will($this
        ->toBoundPromise($result, $mutation));
    }
    if (is_callable($builder)) {
      $builder($mutation);
    }
    return $mutation;
  }
  
  protected function mockInterface($id, array $definition, $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLInterface::class, $definition + [
      'id' => $id,
      'class' => InterfacePluginBase::class,
      'mock_factory' => 'mockInterfaceFactory',
    ]);
    $this->graphQLPlugins[InputTypePluginBase::class][$id] = [
      'definition' => $definition,
      'builder' => $builder,
    ];
  }
  protected function mockInterfaceFactory($definition, $builder = NULL) {
    $interface = $this
      ->getMockForAbstractClass(InterfacePluginBase::class, [
      [],
      $definition['id'],
      $definition,
    ]);
    if (is_callable($builder)) {
      $builder($interface);
    }
    return $interface;
  }
  
  protected function mockUnion($id, array $definition, $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLUnionType::class, $definition + [
      'id' => $id,
      'class' => UnionTypePluginBase::class,
      'mock_factory' => 'mockUnionFactory',
    ]);
    $this->graphQLPlugins[UnionTypePluginBase::class][$id] = [
      'definition' => $definition,
      'builder' => $builder,
    ];
  }
  protected function mockUnionFactory($definition, $builder) {
    $union = $this
      ->getMockForAbstractClass(UnionTypePluginBase::class, [
      [],
      $definition['id'],
      $definition,
    ]);
    if (is_callable($union)) {
      $builder($union);
    }
    return $union;
  }
  
  protected function mockEnum($id, array $definition, $values = [], $builder = NULL) {
    $definition = $this
      ->getTypeSystemPluginDefinition(GraphQLEnum::class, $definition + [
      'id' => $id,
      'class' => EnumPluginBase::class,
      'mock_factory' => 'mockEnumFactory',
    ]);
    $this->graphQLPlugins[EnumPluginBase::class][$id] = [
      'definition' => $definition,
      'values' => $values,
      'builder' => $builder,
    ];
  }
  protected function mockEnumFactory($definition, $values = [], $builder = NULL) {
    $enum = $this
      ->getMockBuilder(EnumPluginBase::class)
      ->setConstructorArgs([
      [],
      $definition['id'],
      $definition,
    ])
      ->setMethods([
      'buildEnumValues',
    ])
      ->getMock();
    $enum
      ->expects(static::any())
      ->method('buildEnumValues')
      ->with($this
      ->anything())
      ->will($this
      ->toBoundPromise($values, $enum));
    if (is_callable($builder)) {
      $builder($enum);
    }
    return $enum;
  }
}