<?php

namespace Oro\Bundle\ApiBundle\Tests\Unit\Processor\Subresource\Shared;

use Oro\Bundle\ApiBundle\Config\EntityDefinitionConfig;
use Oro\Bundle\ApiBundle\Metadata\EntityMetadata;
use Oro\Bundle\ApiBundle\Metadata\FieldMetadata;
use Oro\Bundle\ApiBundle\Processor\Subresource\Shared\AddParentEntityIdToQuery;
use Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity;
use Oro\Bundle\ApiBundle\Tests\Unit\Processor\Subresource\GetSubresourceProcessorOrmRelatedTestCase;
use Oro\Bundle\ApiBundle\Util\ConfigUtil;
use Oro\Bundle\ApiBundle\Util\EntityIdHelper;

/**
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
 */
class AddParentEntityIdToQueryTest extends GetSubresourceProcessorOrmRelatedTestCase
{
    /** @var AddParentEntityIdToQuery */
    private $processor;

    protected function setUp(): void
    {
        parent::setUp();

        $this->processor = new AddParentEntityIdToQuery(
            $this->doctrineHelper,
            new EntityIdHelper()
        );
    }

    public function testProcessWhenQueryDoesNotExist()
    {
        $this->processor->process($this->context);
        self::assertNull($this->context->getQuery());
    }

    public function testProcessForUnsupportedQuery()
    {
        $query = new \stdClass();

        $this->context->setQuery($query);
        $this->processor->process($this->context);
        self::assertSame($query, $this->context->getQuery());
    }

    public function testProcessForQueryWithSeveralRootAliases()
    {
        $query = $this->doctrineHelper
            ->getEntityManagerForClass(Entity\Product::class)
            ->createQueryBuilder()
            ->from(Entity\User::class, 'root1')
            ->from(Entity\Product::class, 'root2');

        $this->context->setParentClassName(Entity\User::class);
        $this->context->setParentId(123);
        $this->context->setAssociationName('products');
        $this->context->setQuery($query);
        $this->processor->process($this->context);
        self::assertEquals(
            'SELECT FROM '
            . 'Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User root1, '
            . 'Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Product root2',
            $this->context->getQuery()->getDQL()
        );
    }

    public function testProcessForSubresourceThatDoesNotAssociatedWithAnyFieldInParentEntityConfig()
    {
        $associationName = 'association';

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper
            ->getEntityRepositoryForClass(Entity\User::class)
            ->createQueryBuilder('e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId(-1);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e',
            $this->context->getQuery()->getDQL()
        );
        self::assertCount(0, $this->context->getQuery()->getParameters());
    }

    public function testProcessForComputedAssociationWhenQueryForItIsPreparedByAnotherProcessor()
    {
        $associationName = 'owner';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)
            ->setPropertyPath(ConfigUtil::IGNORE_PROPERTY_PATH);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper
            ->getEntityRepositoryForClass(Entity\User::class)
            ->createQueryBuilder('e')
            ->where('e.owner = :parent_entity_id')
            ->setParameter('parent_entity_id', $parentId);

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' WHERE e.owner = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForComputedAssociationWhenQueryForItIsNotPreparedByAnotherProcessor()
    {
        $this->expectException(\Oro\Bundle\ApiBundle\Exception\RuntimeException::class);
        $this->expectExceptionMessage('The query is not valid. Reason: the parent entity ID is not set.');

        $associationName = 'owner';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)
            ->setPropertyPath(ConfigUtil::IGNORE_PROPERTY_PATH);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper
            ->getEntityRepositoryForClass(Entity\User::class)
            ->createQueryBuilder('e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);
    }

    public function testProcessForComputedAssociationAndCompositeParentIdWhenQueryForItIsPreparedByAnotherProcessor()
    {
        $associationName = 'owner';
        $parentId = ['id' => 123, 'title' => 'test'];

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id', 'title']);
        $parentConfig->addField('id');
        $parentConfig->addField('title');
        $parentConfig->addField($associationName)
            ->setPropertyPath(ConfigUtil::IGNORE_PROPERTY_PATH);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));
        $parentMetadata->addField(new FieldMetadata('title'));

        $query = $this->doctrineHelper
            ->getEntityRepositoryForClass(Entity\User::class)
            ->createQueryBuilder('e')
            ->innerJoin('e.owner', 'o')
            ->where('o.id = :parent_entity_id1 AND o.name = :parent_entity_id2')
            ->setParameter('parent_entity_id1', $parentId['id'])
            ->setParameter('parent_entity_id2', $parentId['title']);

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN e.owner o'
            . ' WHERE o.id = :parent_entity_id1 AND o.name = :parent_entity_id2',
            $this->context->getQuery()->getDQL()
        );
        self::assertSame(
            $parentId['id'],
            $this->context->getQuery()->getParameter('parent_entity_id1')->getValue()
        );
        self::assertSame(
            $parentId['title'],
            $this->context->getQuery()->getParameter('parent_entity_id2')->getValue()
        );
    }

    public function testProcessForComputedAssociationAndCompositeParentIdWhenQueryForItIsNotPreparedByAnotherProcessor()
    {
        $this->expectException(\Oro\Bundle\ApiBundle\Exception\RuntimeException::class);
        $this->expectExceptionMessage('The query is not valid. Reason: the parent entity ID is not set.');

        $associationName = 'owner';
        $parentId = ['id' => 123, 'title' => 'test'];

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id', 'title']);
        $parentConfig->addField('id');
        $parentConfig->addField('title');
        $parentConfig->addField($associationName)
            ->setPropertyPath(ConfigUtil::IGNORE_PROPERTY_PATH);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));
        $parentMetadata->addField(new FieldMetadata('title'));

        $query = $this->doctrineHelper
            ->getEntityRepositoryForClass(Entity\User::class)
            ->createQueryBuilder('e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);
    }

    public function testProcessForToManyBidirectionalAssociation()
    {
        $associationName = 'products';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\Product::class, 'e');

        $this->context->setIsCollection(true);
        $this->context->setParentClassName(Entity\User::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Product e'
            . ' INNER JOIN e.owner parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToManyUnidirectionalAssociation()
    {
        $associationName = 'users';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setIsCollection(true);
        $this->context->setParentClassName(Entity\Origin::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Origin parent_entity1'
            . ' WITH e MEMBER OF parent_entity1.users'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToManyRenamedBidirectionalAssociation()
    {
        $associationName = 'renamedProducts';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)->setPropertyPath('products');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\Product::class, 'e');

        $this->context->setIsCollection(true);
        $this->context->setParentClassName(Entity\User::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Product e'
            . ' INNER JOIN e.owner parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToManyRenamedUnidirectionalAssociation()
    {
        $associationName = 'renamedUsers';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)->setPropertyPath('users');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setIsCollection(true);
        $this->context->setParentClassName(Entity\Origin::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Origin parent_entity1'
            . ' WITH e MEMBER OF parent_entity1.users'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToOneBidirectionalAssociation()
    {
        $associationName = 'owner';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN e.products parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToOneUnidirectionalAssociation()
    {
        $associationName = 'user';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setParentClassName(Entity\Origin::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Origin parent_entity1'
            . ' WITH parent_entity1.user = e'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToOneRenamedBidirectionalAssociation()
    {
        $associationName = 'renamedOwner';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)->setPropertyPath('owner');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN e.products parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToOneRenamedUnidirectionalAssociation()
    {
        $associationName = 'renamedUser';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)->setPropertyPath('user');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setParentClassName(Entity\Origin::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Origin parent_entity1'
            . ' WITH parent_entity1.user = e'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToOneInverseSideBidirectionalAssociation()
    {
        $associationName = 'origin';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\Origin::class, 'e');

        $this->context->setParentClassName(Entity\Mailbox::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Origin e'
            . ' INNER JOIN e.mailbox parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForToOneRenamedInverseSideBidirectionalAssociation()
    {
        $associationName = 'renamedOrigin';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)->setPropertyPath('origin');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\Origin::class, 'e');

        $this->context->setParentClassName(Entity\Mailbox::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Origin e'
            . ' INNER JOIN e.mailbox parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForRenamedParentIdentifierField()
    {
        $associationName = 'owner';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['renamedId']);
        $parentConfig->addField('renamedId')->setPropertyPath('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('renamedId'))->setPropertyPath('id');

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN e.products parent_entity1'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForCompositeParentIdentifier()
    {
        $associationName = 'children';
        $parentId = ['id' => 123, 'title' => 'test'];

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id', 'title']);
        $parentConfig->addField('id');
        $parentConfig->addField('title');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));
        $parentMetadata->addField(new FieldMetadata('title'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\CompositeKeyEntity::class, 'e');

        $this->context->setParentClassName(Entity\CompositeKeyEntity::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\CompositeKeyEntity e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\CompositeKeyEntity parent_entity1'
            . ' WITH parent_entity1.children = e'
            . ' WHERE parent_entity1.id = :parent_entity_id1 AND parent_entity1.title = :parent_entity_id2',
            $this->context->getQuery()->getDQL()
        );
        self::assertSame(
            $parentId['id'],
            $this->context->getQuery()->getParameter('parent_entity_id1')->getValue()
        );
        self::assertSame(
            $parentId['title'],
            $this->context->getQuery()->getParameter('parent_entity_id2')->getValue()
        );
    }

    public function testProcessForRenamedCompositeParentIdentifier()
    {
        $associationName = 'children';
        $parentId = ['renamedId' => 123, 'renamedTitle' => 'test'];

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['renamedId', 'renamedTitle']);
        $parentConfig->addField('renamedId')->setPropertyPath('id');
        $parentConfig->addField('renamedTitle')->setPropertyPath('title');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('renamedId'))->setPropertyPath('id');
        $parentMetadata->addField(new FieldMetadata('renamedTitle'))->setPropertyPath('title');

        $query = $this->doctrineHelper->createQueryBuilder(Entity\CompositeKeyEntity::class, 'e');

        $this->context->setParentClassName(Entity\CompositeKeyEntity::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\CompositeKeyEntity e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\CompositeKeyEntity parent_entity1'
            . ' WITH parent_entity1.children = e'
            . ' WHERE parent_entity1.id = :parent_entity_id1 AND parent_entity1.title = :parent_entity_id2',
            $this->context->getQuery()->getDQL()
        );
        self::assertSame(
            $parentId['renamedId'],
            $this->context->getQuery()->getParameter('parent_entity_id1')->getValue()
        );
        self::assertSame(
            $parentId['renamedTitle'],
            $this->context->getQuery()->getParameter('parent_entity_id2')->getValue()
        );
    }

    public function testProcessForAssociationWithDeepPropertyPath()
    {
        $associationName = 'category';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName)->setPropertyPath('owner.category');
        $ownerFieldConfig = $parentConfig->addField('owner');
        $ownerFieldConfig->setTargetClass(Entity\Category::class);
        $ownerTargetConfig = $ownerFieldConfig->createAndSetTargetEntity();
        $ownerTargetConfig->addField('category');
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\User::class, 'e');

        $this->context->setParentClassName(Entity\Product::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Category parent_entity1'
            . ' WITH parent_entity1.category = e'
            . ' INNER JOIN e.products parent_entity2'
            . ' WHERE parent_entity2.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }

    public function testProcessForApiResourceBasedOnManageableEntity()
    {
        $this->notManageableClassNames[] = Entity\UserProfile::class;

        $associationName = 'category';
        $parentId = 123;

        $parentConfig = new EntityDefinitionConfig();
        $parentConfig->setParentResourceClass(Entity\User::class);
        $parentConfig->setIdentifierFieldNames(['id']);
        $parentConfig->addField('id');
        $parentConfig->addField($associationName);
        $parentMetadata = new EntityMetadata();
        $parentMetadata->setIdentifierFieldNames($parentConfig->getIdentifierFieldNames());
        $parentMetadata->addField(new FieldMetadata('id'));

        $query = $this->doctrineHelper->createQueryBuilder(Entity\Category::class, 'e');

        $this->context->setParentClassName(Entity\UserProfile::class);
        $this->context->setParentId($parentId);
        $this->context->setAssociationName($associationName);
        $this->context->setParentConfig($parentConfig);
        $this->context->setParentMetadata($parentMetadata);
        $this->context->setQuery($query);
        $this->processor->process($this->context);

        self::assertEquals(
            'SELECT e'
            . ' FROM Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\Category e'
            . ' INNER JOIN Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Entity\User parent_entity1'
            . ' WITH parent_entity1.category = e'
            . ' WHERE parent_entity1.id = :parent_entity_id',
            $this->context->getQuery()->getDQL()
        );
        self::assertEquals(
            $parentId,
            $this->context->getQuery()->getParameter('parent_entity_id')->getValue()
        );
    }
}
