<commit_before><?php
/**
 * Copyright (c) Qobo Ltd. (https://www.qobo.biz)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Qobo Ltd. (https://www.qobo.biz)
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Search\Test\TestCase\Service;

use Cake\Datasource\EntityInterface;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
use Search\Filter\EndsWith;
use Search\Filter\Equal;
use Search\Filter\StartsWith;
use Search\Service\Criteria;
use Search\Service\Search;
use Webmozart\Assert\Assert;

class SearchTest extends TestCase
{
    public $fixtures = [
        'plugin.Search.articles',
        'plugin.Search.articles_tags',
        'plugin.Search.authors',
        'plugin.Search.tags'
    ];

    private $table;

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

        $this->table = TableRegistry::getTableLocator()->get('Articles');
    }

    public function tearDown() : void
    {
        unset($this->table);

        parent::tearDown();
    }

    /**
     * @doesNotPerformAssertions
     */
    public function testAddCriteria() : void
    {
        $this->doesNotPerformAssertions();

        $search = new Search($this->table->find(), $this->table);

        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => StartsWith::class, 'value' => 't']));
    }
    public function testAddCriteriaInvalid() : void
    {
        $this->expectException(\RuntimeException::class);

        $search = new Search($this->table->find(), $this->table);

        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => TestCase::class, 'value' => 't']));
    }

    /**
     * @doesNotPerformAssertions
     */
    public function testSetConjunction() : void
    {
        $this->doesNotPerformAssertions();

        $search = new Search($this->table->find(), $this->table);

        $search->setConjunction(Search::DEFAULT_CONJUNCTION);
    }
    public function testSetConjunctionInvalid() : void
    {
        $this->expectException(\RuntimeException::class);

        $search = new Search($this->table->find(), $this->table);

        $search->setConjunction('invalid conjunction');
    }

    public function testExecute() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla'],
                ['title' => 'two', 'content' => 'bla bla'],
                ['title' => 'three', 'content' => 'bla bla'],
                ['title' => 'four', 'content' => 'bla bla'],
            ])
        );

        $search = new Search($this->table->find(), $this->table);
        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => StartsWith::class, 'value' => 't']));

        $query = $search->execute();

        $this->assertCount(2, $query);
    }

    public function testExecuteWithGroupBy() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla'],
                ['title' => 'two', 'content' => 'bla bla'],
                ['title' => 'three', 'content' => 'bla bla'],
                ['title' => 'four', 'content' => 'bla bla'],
            ])
        );

        $query = $this->table->find();
        $query->group('content');

        $search = new Search($query, $this->table);
        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => StartsWith::class, 'value' => 't']));

        $query = $search->execute()
            ->enableHydration(true);

        $result = $query->firstOrFail();

        Assert::isInstanceOf($result, EntityInterface::class);

        $this->assertEquals(2, $result->get('total'));
    }

    public function testExecuteWithGroupByAssociated() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'two', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'three', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'four', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000002'],
            ])
        );

        $query = $this->table->find();
        $query->group('Authors.name');

        $search = new Search($query, $this->table);

        $query = $search->execute();
        $query->order(['total' => 'DESC']);
        $entities = $query->toArray();

        $this->assertCount(2, $entities);

        $this->assertSame(
            [3, '<NAME>'],
            [$entities[0]->get('total'), $entities[0]->get('_matchingData')['Authors']->get('name')]
        );
        $this->assertSame(
            [1, '<NAME>'],
            [$entities[1]->get('total'), $entities[1]->get('_matchingData')['Authors']->get('name')]
        );
    }

    public function testExecuteWithAssociatedManyToOne() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'two', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000002'],
                ['title' => 'three', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001']
            ])<commit_msg>Extend group by assertions for M:M and 1:M associations (task #12481)<commit_after><?php
/**
 * Copyright (c) Qobo Ltd. (https://www.qobo.biz)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Qobo Ltd. (https://www.qobo.biz)
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Search\Test\TestCase\Service;

use Cake\Datasource\EntityInterface;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
use Search\Filter\EndsWith;
use Search\Filter\Equal;
use Search\Filter\StartsWith;
use Search\Service\Criteria;
use Search\Service\Search;
use Webmozart\Assert\Assert;

class SearchTest extends TestCase
{
    public $fixtures = [
        'plugin.Search.articles',
        'plugin.Search.articles_tags',
        'plugin.Search.authors',
        'plugin.Search.tags'
    ];

    private $table;

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

        $this->table = TableRegistry::getTableLocator()->get('Articles');
    }

    public function tearDown() : void
    {
        unset($this->table);

        parent::tearDown();
    }

    /**
     * @doesNotPerformAssertions
     */
    public function testAddCriteria() : void
    {
        $this->doesNotPerformAssertions();

        $search = new Search($this->table->find(), $this->table);

        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => StartsWith::class, 'value' => 't']));
    }
    public function testAddCriteriaInvalid() : void
    {
        $this->expectException(\RuntimeException::class);

        $search = new Search($this->table->find(), $this->table);

        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => TestCase::class, 'value' => 't']));
    }

    /**
     * @doesNotPerformAssertions
     */
    public function testSetConjunction() : void
    {
        $this->doesNotPerformAssertions();

        $search = new Search($this->table->find(), $this->table);

        $search->setConjunction(Search::DEFAULT_CONJUNCTION);
    }
    public function testSetConjunctionInvalid() : void
    {
        $this->expectException(\RuntimeException::class);

        $search = new Search($this->table->find(), $this->table);

        $search->setConjunction('invalid conjunction');
    }

    public function testExecute() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla'],
                ['title' => 'two', 'content' => 'bla bla'],
                ['title' => 'three', 'content' => 'bla bla'],
                ['title' => 'four', 'content' => 'bla bla'],
            ])
        );

        $search = new Search($this->table->find(), $this->table);
        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => StartsWith::class, 'value' => 't']));

        $query = $search->execute();

        $this->assertCount(2, $query);
    }

    public function testExecuteWithGroupBy() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla'],
                ['title' => 'two', 'content' => 'bla bla'],
                ['title' => 'three', 'content' => 'bla bla'],
                ['title' => 'four', 'content' => 'bla bla'],
            ])
        );

        $query = $this->table->find();
        $query->group('content');

        $search = new Search($query, $this->table);
        $search->addCriteria(new Criteria(['field' => 'title', 'operator' => StartsWith::class, 'value' => 't']));

        $query = $search->execute()
            ->enableHydration(true);

        $result = $query->firstOrFail();

        Assert::isInstanceOf($result, EntityInterface::class);

        $this->assertEquals(2, $result->get('total'));
    }

    public function testExecuteWithGroupByAssociatedManyToOne() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'two', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'three', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'four', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000002'],
            ])
        );

        $query = $this->table->find();
        $query->group('Authors.name');

        $search = new Search($query, $this->table);

        $query = $search->execute();
        $query->order(['total' => 'DESC']);
        $entities = $query->toArray();

        $this->assertCount(2, $entities);

        $this->assertSame(
            [3, '<NAME>'],
            [$entities[0]->get('total'), $entities[0]->get('_matchingData')['Authors']->get('name')]
        );
        $this->assertSame(
            [1, '<NAME>'],
            [$entities[1]->get('total'), $entities[1]->get('_matchingData')['Authors']->get('name')]
        );
    }

    public function testExecuteWithGroupByAssociatedManyToMany() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla', 'tags' => ['_ids' => ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000002']]],
                ['title' => 'two', 'content' => 'bla bla', 'tags' => ['_ids' => ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000002']]],
                ['title' => 'three', 'content' => 'bla bla', 'tags' => ['_ids' => ['00000000-0000-0000-0000-000000000001']]],
                ['title' => 'four', 'content' => 'bla bla', 'tags' => ['_ids' => ['00000000-0000-0000-0000-000000000003']]],
            ])
        );

        $query = $this->table->find();
        $query->group('Tags.id');

        $search = new Search($query, $this->table);

        $query = $search->execute();
        $query->order(['total' => 'DESC']);
        $entities = $query->toArray();

        $this->assertCount(3, $entities);

        $this->assertSame(
            [3, '00000000-0000-0000-0000-000000000001'],
            [$entities[0]->get('total'), $entities[0]->get('_matchingData')['Tags']->get('id')]
        );
        $this->assertSame(
            [2, '00000000-0000-0000-0000-000000000002'],
            [$entities[1]->get('total'), $entities[1]->get('_matchingData')['Tags']->get('id')]
        );
        $this->assertSame(
            [1, '00000000-0000-0000-0000-000000000003'],
            [$entities[2]->get('total'), $entities[2]->get('_matchingData')['Tags']->get('id')]
        );
    }

    public function testExecuteWithGroupByAssociatedOneToMany() : void
    {
        $table = TableRegistry::getTableLocator()->get('Authors');

        $table->deleteAll([]);
        /**
         * @var \Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet
         */
        $newEntities = $table->newEntities([
            ['name' => 'Author 1', 'articles' => ['_ids' => ['00000000-0000-0000-0000-000000000003']]],
            ['name' => 'Author 2', 'articles' => ['_ids' => ['00000000-0000-0000-0000-000000000001']]],
            ['name' => 'Author 3', 'articles' => ['_ids' => ['00000000-0000-0000-0000-000000000002']]]
        ]);

        $table->saveMany($newEntities);

        $query = $table->find();
        $query->group('Articles.published');

        $search = new Search($query, $table);

        $query = $search->execute();
        $query->order(['total' => 'DESC']);
        $entities = $query->toArray();

        $this->assertCount(2, $entities);

        $this->assertSame(
            [2, true],
            [$entities[0]->get('total'), $entities[0]->get('_matchingData')['Articles']->get('published')]
        );
        $this->assertSame(
            [1, false],
            [$entities[1]->get('total'), $entities[1]->get('_matchingData')['Articles']->get('published')]
        );
    }

    public function testExecuteWithAssociatedManyToOne() : void
    {
        $this->table->deleteAll([]);
        $this->table->saveMany(
            $this->table->newEntities([
                ['title' => 'one', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001'],
                ['title' => 'two', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000002'],
                ['title' => 'three', 'content' => 'bla bla', 'author_id' => '00000000-0000-0000-0000-000000000001']
            ])
{'fraction_non_alphanumeric': 0.19844728956687552, 'fraction_numerical': 0.05965676927267775, 'mean_word_length': 2.544532947139754, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 336, 'https://': 6, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 52, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6329325', 'n_tokens_mistral': 5158, 'n_tokens_neox': 4314, 'n_words': 776}