|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Tests\Unit\Validator\Query; |
| 4 | + |
| 5 | +use PHPUnit\Framework\Attributes\DataProvider; |
| 6 | +use PHPUnit\Framework\TestCase; |
| 7 | +use Utopia\Database\Document; |
| 8 | +use Utopia\Database\Query; |
| 9 | +use Utopia\Database\Validator\Query\Filter; |
| 10 | +use Utopia\Query\Method; |
| 11 | +use Utopia\Query\Schema\ColumnType; |
| 12 | + |
| 13 | +/** |
| 14 | + * A query parsed from JSON takes a JSON object for its values as readily as a list. The validator read such values |
| 15 | + * by position and raised an undefined key warning; it refuses them as invalid instead. |
| 16 | + */ |
| 17 | +final class FilterMalformedValuesTest extends TestCase |
| 18 | +{ |
| 19 | + private function validator(bool $supportForAttributes = true): Filter |
| 20 | + { |
| 21 | + return new Filter( |
| 22 | + attributes: [ |
| 23 | + new Document(['$id' => 'name', 'key' => 'name', 'type' => ColumnType::String->value, 'array' => false]), |
| 24 | + new Document(['$id' => 'tags', 'key' => 'tags', 'type' => ColumnType::String->value, 'array' => true]), |
| 25 | + new Document(['$id' => 'location', 'key' => 'location', 'type' => ColumnType::Point->value, 'array' => false]), |
| 26 | + new Document(['$id' => 'embedding', 'key' => 'embedding', 'type' => ColumnType::Vector->value, 'array' => false, 'size' => 2]), |
| 27 | + ], |
| 28 | + idAttributeType: ColumnType::Integer->value, |
| 29 | + supportForAttributes: $supportForAttributes, |
| 30 | + ); |
| 31 | + } |
| 32 | + |
| 33 | + /** |
| 34 | + * @return iterable<string, array{string}> |
| 35 | + */ |
| 36 | + public static function objectValues(): iterable |
| 37 | + { |
| 38 | + yield 'equal' => ['{"method":"equal","attribute":"name","values":{"value":"a"}}']; |
| 39 | + yield 'contains' => ['{"method":"contains","attribute":"tags","values":{"tag":"a"}}']; |
| 40 | + yield 'notEqual' => ['{"method":"notEqual","attribute":"name","values":{"value":"a"}}']; |
| 41 | + yield 'regex' => ['{"method":"regex","attribute":"name","values":{"pattern":"^a"}}']; |
| 42 | + yield 'between' => ['{"method":"between","attribute":"name","values":{"from":"a","to":"b"}}']; |
| 43 | + yield 'exists' => ['{"method":"exists","attribute":"","values":{"attribute":"name"}}']; |
| 44 | + yield 'intersects' => ['{"method":"intersects","attribute":"location","values":{"type":"Point","coordinates":[1,2]}}']; |
| 45 | + yield 'distanceLessThan' => ['{"method":"distanceLessThan","attribute":"location","values":{"geometry":[[1,2],10,false]}}']; |
| 46 | + yield 'vectorDot' => ['{"method":"vectorDot","attribute":"embedding","values":{"vector":[1,2]}}']; |
| 47 | + } |
| 48 | + |
| 49 | + #[DataProvider('objectValues')] |
| 50 | + public function testValuesGivenAsAnObjectAreRefused(string $json): void |
| 51 | + { |
| 52 | + $validator = $this->validator(); |
| 53 | + $query = Query::parse($json); |
| 54 | + |
| 55 | + $this->assertFalse($validator->isValid($query)); |
| 56 | + $this->assertSame(\ucfirst($query->getMethod()->value).' query values must be a list', $validator->getDescription()); |
| 57 | + } |
| 58 | + |
| 59 | + public function testADistanceTupleGivenAsAnObjectIsRefused(): void |
| 60 | + { |
| 61 | + $validator = $this->validator(); |
| 62 | + $query = Query::parse('{"method":"distanceLessThan","attribute":"location","values":[{"geometry":[1,2],"distance":10,"meters":false}]}'); |
| 63 | + |
| 64 | + $this->assertFalse($validator->isValid($query)); |
| 65 | + $this->assertSame('Distance query requires [[geometry, distance]] parameters', $validator->getDescription()); |
| 66 | + } |
| 67 | + |
| 68 | + public function testADistanceWithAMetersFlagThatIsNotABooleanIsRefused(): void |
| 69 | + { |
| 70 | + $validator = $this->validator(); |
| 71 | + |
| 72 | + $this->assertFalse($validator->isValid(new Query(Method::DistanceLessThan, 'location', [[[1, 2], 10, ['yes']]]))); |
| 73 | + $this->assertSame('Distance query requires a boolean meters flag', $validator->getDescription()); |
| 74 | + $this->assertTrue($validator->isValid(Query::distanceLessThan('location', [1, 2], 10, true))); |
| 75 | + } |
| 76 | + |
| 77 | + public function testAVectorGivenAsAnObjectIsRefused(): void |
| 78 | + { |
| 79 | + $validator = $this->validator(); |
| 80 | + |
| 81 | + $this->assertFalse($validator->isValid(Query::vectorDot('embedding', ['x' => 1.0, 'y' => 2.0]))); |
| 82 | + $this->assertSame('Vector query value must be an array', $validator->getDescription()); |
| 83 | + $this->assertTrue($validator->isValid(Query::vectorDot('embedding', [1.0, 2.0]))); |
| 84 | + } |
| 85 | + |
| 86 | + public function testAVectorQueryOnAnUndeclaredAttributeIsRefusedWithoutAWarning(): void |
| 87 | + { |
| 88 | + $validator = $this->validator(supportForAttributes: false); |
| 89 | + |
| 90 | + $this->assertFalse($validator->isValid(Query::vectorDot('undeclared', [1.0, 2.0]))); |
| 91 | + $this->assertSame('Vector queries can only be used on vector attributes', $validator->getDescription()); |
| 92 | + } |
| 93 | + |
| 94 | + public function testListValuesAreStillValid(): void |
| 95 | + { |
| 96 | + $validator = $this->validator(); |
| 97 | + |
| 98 | + $this->assertTrue($validator->isValid(Query::equal('name', ['a', 'b']))); |
| 99 | + $this->assertTrue($validator->isValid(Query::contains('tags', ['a']))); |
| 100 | + $this->assertTrue($validator->isValid(Query::intersects('location', [1, 2]))); |
| 101 | + } |
| 102 | +} |
0 commit comments