From ebd30f48614858a150825947012f628ee0d064aa Mon Sep 17 00:00:00 2001 From: Sobuno Date: Thu, 2 Jan 2025 22:18:12 +0100 Subject: [PATCH] Move tests to match rearranged structure + standardize tests --- ...ractQueryParserInterfaceParseQueryTest.php | 308 ------------------ ...ractQueryParserInterfaceParseQueryTest.php | 278 ++++++++++++++++ .../GdbotsQueryParserParseQueryTest.php | 7 +- .../QueryParserParseQueryTest.php | 7 +- 4 files changed, 284 insertions(+), 316 deletions(-) delete mode 100644 tests/unit/Support/Search/AbstractQueryParserInterfaceParseQueryTest.php create mode 100644 tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTest.php rename tests/unit/Support/Search/{ => QueryParser}/GdbotsQueryParserParseQueryTest.php (60%) rename tests/unit/Support/Search/{ => QueryParser}/QueryParserParseQueryTest.php (60%) diff --git a/tests/unit/Support/Search/AbstractQueryParserInterfaceParseQueryTest.php b/tests/unit/Support/Search/AbstractQueryParserInterfaceParseQueryTest.php deleted file mode 100644 index 47efe3a6ed..0000000000 --- a/tests/unit/Support/Search/AbstractQueryParserInterfaceParseQueryTest.php +++ /dev/null @@ -1,308 +0,0 @@ -createParser()->parse(''); - - $this->assertIsArray($result); - $this->assertEmpty($result); - } - - public function testGivenProhibitedFieldOperatorWhenParsingQueryThenReturnsFieldNode(): void - { - $result = $this->createParser()->parse('-amount:100'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsField($result[0], 'amount', '100', true); - } - - public function testGivenSimpleWordWhenParsingQueryThenReturnsWordNode(): void - { - $result = $this->createParser()->parse('groceries'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsWord($result[0], 'groceries'); - } - - public function testGivenMultipleWordsWhenParsingQueryThenReturnsWordNodes(): void - { - $result = $this->createParser()->parse('groceries shopping market'); - - $this->assertIsArray($result); - $this->assertCount(3, $result); - $this->assertIsWord($result[0], 'groceries'); - $this->assertIsWord($result[1], 'shopping'); - $this->assertIsWord($result[2], 'market'); - } - - public function testGivenMixedWordsAndOperatorsWhenParsingQueryThenReturnsCorrectNodes(): void - { - $result = $this->createParser()->parse('groceries amount:50 shopping'); - - $this->assertIsArray($result); - $this->assertCount(3, $result); - $this->assertIsWord($result[0], 'groceries'); - $this->assertIsField($result[1], 'amount', '50'); - $this->assertIsWord($result[2], 'shopping'); - } - - public function testGivenQuotedValueWithSpacesWhenParsingQueryThenReturnsFieldNode(): void - { - $result = $this->createParser()->parse('description_contains:"shopping at market"'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsField($result[0], 'description_contains', 'shopping at market'); - } - - public function testGivenSubqueryAfterFieldValueWhenParsingQueryThenReturnsCorrectNodes(): void - { - $result = $this->createParser()->parse('amount:100 (description:"market" category:food)'); - - $this->assertIsArray($result); - $this->assertCount(2, $result); - $this->assertIsField($result[0], 'amount', '100'); - - $expectedNodes = [ - new Field('description', 'market'), - new Field('category', 'food') - ]; - $this->assertIsSubquery($result[1], $expectedNodes); - } - - public function testGivenWordFollowedBySubqueryWhenParsingQueryThenReturnsCorrectNodes(): void - { - $result = $this->createParser()->parse('groceries (amount:100 description_contains:"test")'); - - $this->assertIsArray($result); - $this->assertCount(2, $result); - - $this->assertIsWord($result[0], 'groceries'); - - $expectedNodes = [ - new Field('amount', '100'), - new Field('description_contains', 'test') - ]; - $this->assertIsSubquery($result[1], $expectedNodes); - } - - public function testGivenNestedSubqueryWhenParsingQueryThenReturnsSubqueryNode(): void - { - $result = $this->createParser()->parse('(amount:100 (description_contains:"test payment" -has_attachments:true))'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - - $innerSubqueryNodes = [ - new Field('description_contains', 'test payment'), - new Field('has_attachments', 'true', true) - ]; - $outerSubqueryNodes = [ - new Field('amount', '100'), - new Subquery($innerSubqueryNodes) - ]; - $this->assertIsSubquery($result[0], $outerSubqueryNodes); - } - - public function testGivenComplexNestedSubqueriesWhenParsingQueryThenReturnsCorrectNodes(): void - { - $result = $this->createParser()->parse('shopping (amount:50 market (-category:food word description:"test phrase" (has_notes:true)))'); - - $this->assertIsArray($result); - $this->assertCount(2, $result); - - $this->assertIsWord($result[0], 'shopping'); - - $expectedLevel2 = [ - new Field('category', 'food', true), - new Word('word'), - new Field('description', 'test phrase'), - new Subquery([new Field('has_notes', 'true')]) - ]; - - $expectedLevel1 = [ - new Field('amount', '50'), - new Word('market'), - new Subquery($expectedLevel2) - ]; - - $this->assertIsSubquery($result[1], $expectedLevel1); - } - - public function testGivenProhibitedWordWhenParsingQueryThenReturnsWordNode(): void - { - $result = $this->createParser()->parse('-groceries'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertInstanceOf(Word::class, $result[0]); - /** @var Word $word */ - $word = $result[0]; - $this->assertTrue($word->isProhibited()); - $this->assertEquals('groceries', $word->getValue()); - } - - public function testGivenQuotedWordWhenParsingQueryThenReturnsWordNode(): void - { - $result = $this->createParser()->parse('"test phrase"'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertInstanceOf(Word::class, $result[0]); - /** @var Word $word */ - $word = $result[0]; - $this->assertEquals('test phrase', $word->getValue()); - } - - public function testGivenProhibitedQuotedWordWhenParsingQueryThenReturnsWordNode(): void - { - $result = $this->createParser()->parse('-"test phrase"'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsWord($result[0], 'test phrase', true); - } - - public function testGivenMultipleFieldsWhenParsingQueryThenReturnsFieldNodes(): void - { - $result = $this->createParser()->parse('amount:100 category:food description:"test phrase"'); - - $this->assertIsArray($result); - $this->assertCount(3, $result); - $this->assertIsField($result[0], 'amount', '100'); - $this->assertIsField($result[1], 'category', 'food'); - $this->assertIsField($result[2], 'description', 'test phrase'); - } - - public function testGivenProhibitedSubqueryWhenParsingQueryThenReturnsSubqueryNode(): void - { - $result = $this->createParser()->parse('-(amount:100 category:food)'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - - $expectedNodes = [ - new Field('amount', '100'), - new Field('category', 'food') - ]; - $this->assertIsSubquery($result[0], $expectedNodes, true); - } - - public function testGivenWordWithMultipleSpacesWhenParsingQueryThenRetainsSpaces(): void - { - $result = $this->createParser()->parse('"multiple spaces"'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsWord($result[0], 'multiple spaces'); - } - - public function testGivenFieldWithMultipleSpacesInValueWhenParsingQueryThenRetainsSpaces(): void - { - $result = $this->createParser()->parse('description:"multiple spaces here"'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsField($result[0], 'description', 'multiple spaces here'); - } - - public function testGivenUnmatchedRightParenthesisWhenParsingQueryThenTreatsAsCharacter(): void - { - $result = $this->createParser()->parse('test)word'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsWord($result[0], 'test)word'); - } - - public function testGivenUnmatchedRightParenthesisInFieldWhenParsingQueryThenTreatsAsCharacter(): void - { - $result = $this->createParser()->parse('description:test)phrase'); - - $this->assertIsArray($result); - $this->assertCount(1, $result); - $this->assertIsField($result[0], 'description', 'test)phrase'); - } - - public function testGivenSubqueryFollowedByWordWhenParsingQueryThenReturnsCorrectNodes(): void - { - $result = $this->createParser()->parse('(amount:100 category:food) shopping'); - - $this->assertIsArray($result); - $this->assertCount(2, $result); - - $expectedNodes = [ - new Field('amount', '100'), - new Field('category', 'food') - ]; - $this->assertIsSubquery($result[0], $expectedNodes); - $this->assertIsWord($result[1], 'shopping'); - } - - private function assertIsWord(Node $node, string $expectedValue, bool $prohibited = false): void - { - $this->assertInstanceOf(Word::class, $node); - /** @var Word $node */ - $this->assertEquals($expectedValue, $node->getValue()); - $this->assertEquals($prohibited, $node->isProhibited()); - } - - private function assertIsField( - Node $node, - string $expectedOperator, - string $expectedValue, - bool $prohibited = false - ): void { - $this->assertInstanceOf(Field::class, $node); - /** @var Field $node */ - $this->assertEquals($expectedOperator, $node->getOperator()); - $this->assertEquals($expectedValue, $node->getValue()); - $this->assertEquals($prohibited, $node->isProhibited()); - } - - private function assertIsSubquery(Node $node, array $expectedNodes, bool $prohibited = false): void - { - $this->assertInstanceOf(Subquery::class, $node); - /** @var Subquery $node */ - $this->assertCount(count($expectedNodes), $node->getNodes()); - $this->assertEquals($prohibited, $node->isProhibited()); - - foreach ($expectedNodes as $index => $expected) { - $actual = $node->getNodes()[$index]; - if ($expected instanceof Word) { - $this->assertIsWord($actual, $expected->getValue(), $expected->isProhibited()); - } elseif ($expected instanceof Field) { - $this->assertIsField($actual, $expected->getOperator(), $expected->getValue(), $expected->isProhibited()); - } elseif ($expected instanceof Subquery) { - $this->assertIsSubquery($actual, $expected->getNodes(), $expected->isProhibited()); - } - } - } -} diff --git a/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTest.php b/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTest.php new file mode 100644 index 0000000000..3d2a5426dc --- /dev/null +++ b/tests/unit/Support/Search/QueryParser/AbstractQueryParserInterfaceParseQueryTest.php @@ -0,0 +1,278 @@ + [ + 'query' => '', + 'expected' => [] + ], + 'simple word' => [ + 'query' => 'grocaeries', + 'expected' => [new Word('groceries')] + ], + 'prohibited word' => [ + 'query' => '-groceries', + 'expected' => [new Word('groceries', true)] + ], + 'prohibited field' => [ + 'query' => '-amount:100', + 'expected' => [new Field('amount', '100', true)] + ], + 'quoted word' => [ + 'query' => '"test phrase"', + 'expected' => [new Word('test phrase')] + ], + 'prohibited quoted word' => [ + 'query' => '-"test phrase"', + 'expected' => [new Word('test phrase', true)] + ], + 'multiple words' => [ + 'query' => 'groceries shopping market', + 'expected' => [ + new Word('groceries'), + new Word('shopping'), + new Word('market') + ] + ], + 'field operator' => [ + 'query' => 'amount:100', + 'expected' => [new Field('amount', '100')] + ], + 'quoted field value with single space' => [ + 'query' => 'description:"test phrase"', + 'expected' => [new Field('description', 'test phrase')] + ], + 'multiple fields' => [ + 'query' => 'amount:100 category:food', + 'expected' => [ + new Field('amount', '100'), + new Field('category', 'food') + ] + ], + 'simple subquery' => [ + 'query' => '(amount:100 category:food)', + 'expected' => [ + new Subquery([ + new Field('amount', '100'), + new Field('category', 'food') + ]) + ] + ], + 'prohibited subquery' => [ + 'query' => '-(amount:100 category:food)', + 'expected' => [ + new Subquery([ + new Field('amount', '100'), + new Field('category', 'food') + ], true) + ] + ], + 'nested subquery' => [ + 'query' => '(amount:100 (description:"test" category:food))', + 'expected' => [ + new Subquery([ + new Field('amount', '100'), + new Subquery([ + new Field('description', 'test'), + new Field('category', 'food') + ]) + ]) + ] + ], + 'mixed words and operators' => [ + 'query' => 'groceries amount:50 shopping', + 'expected' => [ + new Word('groceries'), + new Field('amount', '50'), + new Word('shopping') + ] + ], + 'subquery after field value' => [ + 'query' => 'amount:100 (description:"market" category:food)', + 'expected' => [ + new Field('amount', '100'), + new Subquery([ + new Field('description', 'market'), + new Field('category', 'food') + ]) + ] + ], + 'word followed by subquery' => [ + 'query' => 'groceries (amount:100 description_contains:"test")', + 'expected' => [ + new Word('groceries'), + new Subquery([ + new Field('amount', '100'), + new Field('description_contains', 'test') + ]) + ] + ], + 'nested subquery with prohibited field' => [ + 'query' => '(amount:100 (description_contains:"test payment" -has_attachments:true))', + 'expected' => [ + new Subquery([ + new Field('amount', '100'), + new Subquery([ + new Field('description_contains', 'test payment'), + new Field('has_attachments', 'true', true) + ]) + ]) + ] + ], + 'complex nested subqueries' => [ + 'query' => 'shopping (amount:50 market (-category:food word description:"test phrase" (has_notes:true)))', + 'expected' => [ + new Word('shopping'), + new Subquery([ + new Field('amount', '50'), + new Word('market'), + new Subquery([ + new Field('category', 'food', true), + new Word('word'), + new Field('description', 'test phrase'), + new Subquery([ + new Field('has_notes', 'true') + ]) + ]) + ]) + ] + ], + 'word with multiple spaces' => [ + 'query' => '"multiple spaces"', + 'expected' => [new Word('multiple spaces')] + ], + 'field with multiple spaces in value' => [ + 'query' => 'description:"multiple spaces here"', + 'expected' => [new Field('description', 'multiple spaces here')] + ], + 'unmatched right parenthesis in word' => [ + 'query' => 'test)word', + 'expected' => [new Word('test)word')] + ], + 'unmatched right parenthesis in field' => [ + 'query' => 'description:test)phrase', + 'expected' => [new Field('description', 'test)phrase')] + ], + 'subquery followed by word' => [ + 'query' => '(amount:100 category:food) shopping', + 'expected' => [ + new Subquery([ + new Field('amount', '100'), + new Field('category', 'food') + ]), + new Word('shopping') + ] + ] + ]; + } + + /** + * @dataProvider queryDataProvider + * @param string $query The query string to parse + * @param array $expected The expected parse result + */ + public function testQueryParsing(string $query, array $expected): void + { + $result = $this->createParser()->parse($query); + + $this->assertNodesMatch($expected, $result); + } + + private function assertNodesMatch(array $expected, array $actual): void + { + $this->assertCount(count($expected), $actual); + + foreach ($expected as $index => $expectedNode) { + $actualNode = $actual[$index]; + $this->assertNodeMatches($expectedNode, $actualNode); + } + } + + private function assertNodeMatches(Node $expected, Node $actual): void + { + $this->assertInstanceOf(get_class($expected), $actual); + $this->assertEquals($expected->isProhibited(), $actual->isProhibited()); + + match (get_class($expected)) { + Word::class => $this->assertWordMatches($expected, $actual), + Field::class => $this->assertFieldMatches($expected, $actual), + Subquery::class => $this->assertSubqueryMatches($expected, $actual), + default => throw new \InvalidArgumentException(sprintf( + 'Unexpected node type: %s', + get_class($expected) + )) + }; + } + + private function assertWordMatches(Word $expected, Word $actual): void + { + $this->assertEquals($expected->getValue(), $actual->getValue()); + } + + private function assertFieldMatches(Field $expected, Field $actual): void + { + $this->assertEquals($expected->getOperator(), $actual->getOperator()); + $this->assertEquals($expected->getValue(), $actual->getValue()); + } + + private function assertSubqueryMatches(Subquery $expected, Subquery $actual): void + { + $this->assertNodesMatch($expected->getNodes(), $actual->getNodes()); + } + + private function assertIsWord(Node $node, string $expectedValue, bool $prohibited = false): void + { + $this->assertInstanceOf(Word::class, $node); + /** @var Word $node */ + $this->assertEquals($expectedValue, $node->getValue()); + $this->assertEquals($prohibited, $node->isProhibited()); + } + + private function assertIsField( + Node $node, + string $expectedOperator, + string $expectedValue, + bool $prohibited = false + ): void { + $this->assertInstanceOf(Field::class, $node); + /** @var Field $node */ + $this->assertEquals($expectedOperator, $node->getOperator()); + $this->assertEquals($expectedValue, $node->getValue()); + $this->assertEquals($prohibited, $node->isProhibited()); + } + + private function assertIsSubquery(Node $node, array $expectedNodes, bool $prohibited = false): void + { + $this->assertInstanceOf(Subquery::class, $node); + /** @var Subquery $node */ + $this->assertCount(count($expectedNodes), $node->getNodes()); + $this->assertEquals($prohibited, $node->isProhibited()); + + foreach ($expectedNodes as $index => $expected) { + $actual = $node->getNodes()[$index]; + if ($expected instanceof Word) { + $this->assertIsWord($actual, $expected->getValue(), $expected->isProhibited()); + } elseif ($expected instanceof Field) { + $this->assertIsField($actual, $expected->getOperator(), $expected->getValue(), $expected->isProhibited()); + } elseif ($expected instanceof Subquery) { + $this->assertIsSubquery($actual, $expected->getNodes(), $expected->isProhibited()); + } + } + } +} diff --git a/tests/unit/Support/Search/GdbotsQueryParserParseQueryTest.php b/tests/unit/Support/Search/QueryParser/GdbotsQueryParserParseQueryTest.php similarity index 60% rename from tests/unit/Support/Search/GdbotsQueryParserParseQueryTest.php rename to tests/unit/Support/Search/QueryParser/GdbotsQueryParserParseQueryTest.php index 8f00f7a65d..c5880fe98c 100644 --- a/tests/unit/Support/Search/GdbotsQueryParserParseQueryTest.php +++ b/tests/unit/Support/Search/QueryParser/GdbotsQueryParserParseQueryTest.php @@ -1,10 +1,9 @@