From fac84a36cf10b2fdf692a6de9d8aa09f6f564cfc Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 24 Jan 2024 17:49:19 +0200 Subject: [PATCH 1/8] Integer validator --- composer.lock | 26 ++--- phpunit.xml | 2 +- src/Database/Database.php | 3 + src/Database/Validator/Structure.php | 17 ++- tests/e2e/Adapter/Base.php | 160 +++++++++++++++++---------- 5 files changed, 135 insertions(+), 73 deletions(-) diff --git a/composer.lock b/composer.lock index 122a03843..692f3ac95 100644 --- a/composer.lock +++ b/composer.lock @@ -509,16 +509,16 @@ }, { "name": "laravel/pint", - "version": "v1.13.9", + "version": "v1.13.10", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "e3e269cc5d874c8efd2dc7962b1c7ff2585fe525" + "reference": "e2b5060885694ca30ac008c05dc9d47f10ed1abf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/e3e269cc5d874c8efd2dc7962b1c7ff2585fe525", - "reference": "e3e269cc5d874c8efd2dc7962b1c7ff2585fe525", + "url": "https://api.github.com/repos/laravel/pint/zipball/e2b5060885694ca30ac008c05dc9d47f10ed1abf", + "reference": "e2b5060885694ca30ac008c05dc9d47f10ed1abf", "shasum": "" }, "require": { @@ -529,8 +529,8 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.47.0", - "illuminate/view": "^10.40.0", + "friendsofphp/php-cs-fixer": "^3.47.1", + "illuminate/view": "^10.41.0", "larastan/larastan": "^2.8.1", "laravel-zero/framework": "^10.3.0", "mockery/mockery": "^1.6.7", @@ -571,7 +571,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-01-16T17:39:29+00:00" + "time": "2024-01-22T09:04:15+00:00" }, { "name": "myclabs/deep-copy", @@ -1216,16 +1216,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.15", + "version": "9.6.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", "shasum": "" }, "require": { @@ -1299,7 +1299,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" }, "funding": [ { @@ -1315,7 +1315,7 @@ "type": "tidelift" } ], - "time": "2023-12-01T16:55:19+00:00" + "time": "2024-01-19T07:03:14+00:00" }, { "name": "psr/container", diff --git a/phpunit.xml b/phpunit.xml index ccdaa969e..783265d80 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/unit diff --git a/src/Database/Database.php b/src/Database/Database.php index 72864e2cc..7478bac2f 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -32,6 +32,9 @@ class Database public const VAR_BOOLEAN = 'boolean'; public const VAR_DATETIME = 'datetime'; + public const MAX_INTEGER = 2147483647; + public const MAX_BIG_INTEGER = PHP_INT_MAX; + // Relationship Types public const VAR_RELATIONSHIP = 'relationship'; diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index db57ce8d8..3c64d8f22 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -12,7 +12,9 @@ use Utopia\Validator\Boolean; use Utopia\Validator\FloatValidator; use Utopia\Validator\Integer; +use Utopia\Validator\Range; use Utopia\Validator\Text; +use function _PHPStan_3d4486d07\Symfony\Component\String\s; class Structure extends Validator { @@ -249,6 +251,8 @@ public function isValid($document): bool $array = $attribute['array'] ?? false; $format = $attribute['format'] ?? ''; $required = $attribute['required'] ?? false; + $size = $attribute['size'] ?? 0; + $signed = $attribute['signed'] ?? false; if ($required === false && is_null($value)) { // Allow null value to optional params continue; @@ -260,12 +264,19 @@ public function isValid($document): bool switch ($type) { case Database::VAR_STRING: - $size = $attribute['size'] ?? 0; $validator = new Text($size, min: 0); break; case Database::VAR_INTEGER: - $validator = new Integer(); + $max = $size >= 8 ? Database::MAX_BIG_INTEGER : Database::MAX_INTEGER; + $min = -$max; + + if($signed === false){ + //$max *= 2; + $min = 0; + } + + $validator = new Range($min, $max, Database::VAR_INTEGER); break; case Database::VAR_FLOAT: @@ -314,6 +325,8 @@ public function isValid($document): bool } } } else { + if($type == Database::VAR_INTEGER)var_dump($value); + if (!$validator->isValid($value)) { $this->message = 'Attribute "'.$key.'" has invalid '.$label.'. '.$validator->getDescription(); return false; diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index 801c8b089..c6c608596 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -4,6 +4,7 @@ use Exception; use PHPUnit\Framework\TestCase; +use Swoole\FastCGI\Record\Data; use Throwable; use Utopia\Database\Adapter\SQL; use Utopia\Database\Database; @@ -1165,8 +1166,10 @@ public function testCreateDocument(): Document static::getDatabase()->createCollection('documents'); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'string', Database::VAR_STRING, 128, true)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer', Database::VAR_INTEGER, 0, true)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'bigint', Database::VAR_INTEGER, 8, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer_signed', Database::VAR_INTEGER, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer_unsigned', Database::VAR_INTEGER, 4, true, signed: false)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'bigint_signed', Database::VAR_INTEGER, 8, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'bigint_unsigned', Database::VAR_INTEGER, 9, true, signed: false)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, null, true, true)); @@ -1189,8 +1192,10 @@ public function testCreateDocument(): Document Permission::delete(Role::user(ID::custom('2x'))), ], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -1201,10 +1206,14 @@ public function testCreateDocument(): Document $this->assertNotEmpty(true, $document->getId()); $this->assertIsString($document->getAttribute('string')); $this->assertEquals('textđź“ť', $document->getAttribute('string')); // Also makes sure an emoji is working - $this->assertIsInt($document->getAttribute('integer')); - $this->assertEquals(5, $document->getAttribute('integer')); - $this->assertIsInt($document->getAttribute('bigint')); - $this->assertEquals(8589934592, $document->getAttribute('bigint')); + $this->assertIsInt($document->getAttribute('integer_signed')); + $this->assertEquals(-Database::MAX_INTEGER, $document->getAttribute('integer_signed')); + $this->assertIsInt($document->getAttribute('integer_unsigned')); + $this->assertEquals(Database::MAX_INTEGER, $document->getAttribute('integer_unsigned')); + $this->assertIsInt($document->getAttribute('bigint_signed')); + $this->assertEquals(-Database::MAX_BIG_INTEGER, $document->getAttribute('bigint_signed')); + $this->assertIsInt($document->getAttribute('bigint_signed')); + $this->assertEquals(Database::MAX_BIG_INTEGER, $document->getAttribute('bigint_unsigned')); $this->assertIsFloat($document->getAttribute('float')); $this->assertEquals(5.55, $document->getAttribute('float')); $this->assertIsBool($document->getAttribute('boolean')); @@ -1233,8 +1242,10 @@ public function testCreateDocument(): Document Permission::delete(Role::user(ID::custom('2x'))), ], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -1246,10 +1257,14 @@ public function testCreateDocument(): Document $this->assertNotEmpty(true, $manualIdDocument->getId()); $this->assertIsString($manualIdDocument->getAttribute('string')); $this->assertEquals('textđź“ť', $manualIdDocument->getAttribute('string')); // Also makes sure an emoji is working - $this->assertIsInt($manualIdDocument->getAttribute('integer')); - $this->assertEquals(5, $manualIdDocument->getAttribute('integer')); - $this->assertIsInt($manualIdDocument->getAttribute('bigint')); - $this->assertEquals(8589934592, $manualIdDocument->getAttribute('bigint')); + $this->assertIsInt($manualIdDocument->getAttribute('integer_signed')); + $this->assertEquals(-Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_signed')); + $this->assertIsInt($manualIdDocument->getAttribute('integer_unsigned')); + $this->assertEquals(Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_unsigned')); + $this->assertIsInt($manualIdDocument->getAttribute('bigint_signed')); + $this->assertEquals(-Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_signed')); + $this->assertIsInt($manualIdDocument->getAttribute('bigint_unsigned')); + $this->assertEquals(Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_unsigned')); $this->assertIsFloat($manualIdDocument->getAttribute('float')); $this->assertEquals(5.55, $manualIdDocument->getAttribute('float')); $this->assertIsBool($manualIdDocument->getAttribute('boolean')); @@ -1265,10 +1280,14 @@ public function testCreateDocument(): Document $this->assertNotEmpty(true, $manualIdDocument->getId()); $this->assertIsString($manualIdDocument->getAttribute('string')); $this->assertEquals('textđź“ť', $manualIdDocument->getAttribute('string')); // Also makes sure an emoji is working - $this->assertIsInt($manualIdDocument->getAttribute('integer')); - $this->assertEquals(5, $manualIdDocument->getAttribute('integer')); - $this->assertIsInt($manualIdDocument->getAttribute('bigint')); - $this->assertEquals(8589934592, $manualIdDocument->getAttribute('bigint')); + $this->assertIsInt($manualIdDocument->getAttribute('integer_signed')); + $this->assertEquals(-Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_signed')); + $this->assertIsInt($manualIdDocument->getAttribute('integer_unsigned')); + $this->assertEquals(Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_unsigned')); + $this->assertIsInt($manualIdDocument->getAttribute('bigint_signed')); + $this->assertEquals(-Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_signed')); + $this->assertIsInt($manualIdDocument->getAttribute('bigint_unsigned')); + $this->assertEquals(Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_unsigned')); $this->assertIsFloat($manualIdDocument->getAttribute('float')); $this->assertEquals(5.55, $manualIdDocument->getAttribute('float')); $this->assertIsBool($manualIdDocument->getAttribute('boolean')); @@ -1493,8 +1512,8 @@ public function testGetDocument(Document $document): Document $this->assertNotEmpty(true, $document->getId()); $this->assertIsString($document->getAttribute('string')); $this->assertEquals('textđź“ť', $document->getAttribute('string')); - $this->assertIsInt($document->getAttribute('integer')); - $this->assertEquals(5, $document->getAttribute('integer')); + $this->assertIsInt($document->getAttribute('integer_signed')); + $this->assertEquals(-Database::MAX_INTEGER, $document->getAttribute('integer_signed')); $this->assertIsFloat($document->getAttribute('float')); $this->assertEquals(5.55, $document->getAttribute('float')); $this->assertIsBool($document->getAttribute('boolean')); @@ -1514,15 +1533,15 @@ public function testGetDocumentSelect(Document $document): Document $documentId = $document->getId(); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer']), + Query::select(['string', 'integer_signed']), ]); $this->assertEmpty($document->getId()); $this->assertFalse($document->isEmpty()); $this->assertIsString($document->getAttribute('string')); $this->assertEquals('textđź“ť', $document->getAttribute('string')); - $this->assertIsInt($document->getAttribute('integer')); - $this->assertEquals(5, $document->getAttribute('integer')); + $this->assertIsInt($document->getAttribute('integer_signed')); + $this->assertEquals(-Database::MAX_INTEGER, $document->getAttribute('integer_signed')); $this->assertArrayNotHasKey('float', $document->getAttributes()); $this->assertArrayNotHasKey('boolean', $document->getAttributes()); $this->assertArrayNotHasKey('colors', $document->getAttributes()); @@ -1535,7 +1554,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertArrayNotHasKey('$collection', $document); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer', '$id']), + Query::select(['string', 'integer_signed', '$id']), ]); $this->assertArrayHasKey('$id', $document); @@ -1546,7 +1565,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertArrayNotHasKey('$collection', $document); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer', '$permissions']), + Query::select(['string', 'integer_signed', '$permissions']), ]); $this->assertArrayNotHasKey('$id', $document); @@ -1557,7 +1576,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertArrayNotHasKey('$collection', $document); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer', '$internalId']), + Query::select(['string', 'integer_signed', '$internalId']), ]); $this->assertArrayNotHasKey('$id', $document); @@ -1568,7 +1587,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertArrayNotHasKey('$collection', $document); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer', '$collection']), + Query::select(['string', 'integer_signed', '$collection']), ]); $this->assertArrayNotHasKey('$id', $document); @@ -1579,7 +1598,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertArrayHasKey('$collection', $document); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer', '$createdAt']), + Query::select(['string', 'integer_signed', '$createdAt']), ]); $this->assertArrayNotHasKey('$id', $document); @@ -1590,7 +1609,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertArrayNotHasKey('$collection', $document); $document = static::getDatabase()->getDocument('documents', $documentId, [ - Query::select(['string', 'integer', '$updatedAt']), + Query::select(['string', 'integer_signed', '$updatedAt']), ]); $this->assertArrayNotHasKey('$id', $document); @@ -1613,10 +1632,10 @@ public function testFulltextIndexWithInteger(): void if (!$this->getDatabase()->getAdapter()->getSupportForFulltextIndex()) { $this->expectExceptionMessage('Fulltext index is not supported'); } else { - $this->expectExceptionMessage('Attribute "integer" cannot be part of a FULLTEXT index, must be of type string'); + $this->expectExceptionMessage('Attribute "integer_signed" cannot be part of a FULLTEXT index, must be of type string'); } - static::getDatabase()->createIndex('documents', 'fulltext_integer', Database::INDEX_FULLTEXT, ['string','integer']); + static::getDatabase()->createIndex('documents', 'fulltext_integer', Database::INDEX_FULLTEXT, ['string','integer_signed']); } public function testListDocumentSearch(): void @@ -1636,8 +1655,10 @@ public function testListDocumentSearch(): void Permission::delete(Role::any()), ], 'string' => '*test+alias@email-provider.com', - 'integer' => 0, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => 0, + 'integer_unsigned' => 0, + 'bigint_signed' => 0, + 'bigint_unsigned' => 0, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -1685,7 +1706,7 @@ public function testUpdateDocument(Document $document): Document { $document ->setAttribute('string', 'textđź“ť updated') - ->setAttribute('integer', 6) + ->setAttribute('integer_signed', 6) ->setAttribute('float', 5.56) ->setAttribute('boolean', false) ->setAttribute('colors', 'red', Document::SET_TYPE_APPEND) @@ -1696,8 +1717,8 @@ public function testUpdateDocument(Document $document): Document $this->assertNotEmpty(true, $new->getId()); $this->assertIsString($new->getAttribute('string')); $this->assertEquals('textđź“ť updated', $new->getAttribute('string')); - $this->assertIsInt($new->getAttribute('integer')); - $this->assertEquals(6, $new->getAttribute('integer')); + $this->assertIsInt($new->getAttribute('integer_signed')); + $this->assertEquals(6, $new->getAttribute('integer_signed')); $this->assertIsFloat($new->getAttribute('float')); $this->assertEquals(5.56, $new->getAttribute('float')); $this->assertIsBool($new->getAttribute('boolean')); @@ -1791,18 +1812,23 @@ public function testUpdateDocuments(array $documents): void */ public function testUpdateDocumentConflict(Document $document): void { - $document->setAttribute('integer', 7); + $document->setAttribute('integer_signed', 7); $result = $this->getDatabase()->withRequestTimestamp(new \DateTime(), function () use ($document) { return $this->getDatabase()->updateDocument($document->getCollection(), $document->getId(), $document); }); - $this->assertEquals(7, $result->getAttribute('integer')); + $this->assertEquals(7, $result->getAttribute('integer_signed')); $oneHourAgo = (new \DateTime())->sub(new \DateInterval('PT1H')); - $document->setAttribute('integer', 8); - $this->expectException(ConflictException::class); - $this->getDatabase()->withRequestTimestamp($oneHourAgo, function () use ($document) { - return $this->getDatabase()->updateDocument($document->getCollection(), $document->getId(), $document); - }); + $document->setAttribute('integer_signed', 8); + try { + $this->getDatabase()->withRequestTimestamp($oneHourAgo, function () use ($document) { + return $this->getDatabase()->updateDocument($document->getCollection(), $document->getId(), $document); + }); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertTrue($e instanceof ConflictException); + $this->assertEquals('Document was updated after the request timestamp', $e->getMessage()); + } } /** @@ -1822,6 +1848,8 @@ public function testDeleteDocumentConflict(Document $document): void */ public function testUpdateDocumentDuplicatePermissions(Document $document): Document { + + var_dump($document); $new = $this->getDatabase()->updateDocument($document->getCollection(), $document->getId(), $document); $new @@ -1896,7 +1924,6 @@ public function testArrayAttribute(): void Database::VAR_INTEGER, size: 0, required: false, - signed: false, array: true )); @@ -1970,7 +1997,16 @@ public function testArrayAttribute(): void ])); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid integer', $e->getMessage()); + $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid range between 0 and 2,147,483,647', $e->getMessage()); + } + + try { + $database->createDocument($collection, new Document([ + 'age' => -100, + ])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid range between 0 and 2,147,483,647', $e->getMessage()); } $database->createDocument($collection, new Document([ @@ -4011,8 +4047,10 @@ public function testReadPermissionsSuccess(Document $document): Document Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -4043,8 +4081,10 @@ public function testReadPermissionsFailure(): Document Permission::delete(Role::user('1')), ], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -4077,8 +4117,10 @@ public function testWritePermissionsSuccess(Document $document): void Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -4103,8 +4145,10 @@ public function testWritePermissionsUpdateFailure(Document $document): Document Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -4121,8 +4165,8 @@ public function testWritePermissionsUpdateFailure(Document $document): Document Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer' => 6, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => 6, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], @@ -4140,8 +4184,10 @@ public function testNoChangeUpdateDocumentWithoutPermission(Document $document): '$id' => ID::unique(), '$permissions' => [], 'string' => 'textđź“ť', - 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'integer_signed' => -Database::MAX_INTEGER, + 'integer_unsigned' => Database::MAX_INTEGER, + 'bigint_signed' => -Database::MAX_BIG_INTEGER, + 'bigint_unsigned' => Database::MAX_BIG_INTEGER, 'float' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], From 604afb04c47b60a5f723fd14819488da184d35eb Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 24 Jan 2024 18:00:44 +0200 Subject: [PATCH 2/8] Formatting --- src/Database/Validator/Structure.php | 8 ++++---- tests/unit/Validator/StructureTest.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 3c64d8f22..605c45422 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -11,10 +11,8 @@ use Utopia\Validator; use Utopia\Validator\Boolean; use Utopia\Validator\FloatValidator; -use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; -use function _PHPStan_3d4486d07\Symfony\Component\String\s; class Structure extends Validator { @@ -271,7 +269,7 @@ public function isValid($document): bool $max = $size >= 8 ? Database::MAX_BIG_INTEGER : Database::MAX_INTEGER; $min = -$max; - if($signed === false){ + if($signed === false) { //$max *= 2; $min = 0; } @@ -325,7 +323,9 @@ public function isValid($document): bool } } } else { - if($type == Database::VAR_INTEGER)var_dump($value); + if($type == Database::VAR_INTEGER) { + var_dump($value); + } if (!$validator->isValid($value)) { $this->message = 'Attribute "'.$key.'" has invalid '.$label.'. '.$validator->getDescription(); diff --git a/tests/unit/Validator/StructureTest.php b/tests/unit/Validator/StructureTest.php index f6c57de89..1aa7dd5e5 100644 --- a/tests/unit/Validator/StructureTest.php +++ b/tests/unit/Validator/StructureTest.php @@ -325,7 +325,7 @@ public function testIntegerValidation(): void 'feedback' => 'team@appwrite.io', ]))); - $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -338,7 +338,7 @@ public function testIntegerValidation(): void 'feedback' => 'team@appwrite.io', ]))); - $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); } public function testArrayOfIntegersValidation(): void @@ -393,7 +393,7 @@ public function testArrayOfIntegersValidation(): void 'feedback' => 'team@appwrite.io', ]))); - $this->assertEquals('Invalid document structure: Attribute "reviews[\'0\']" has invalid type. Value must be a valid integer', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Attribute "reviews[\'0\']" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); } public function testFloatValidation(): void From 2d75c91ac60b1af759757f5477dadf6d34ad0498 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 24 Jan 2024 18:03:17 +0200 Subject: [PATCH 3/8] Remove debug --- src/Database/Validator/Structure.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 605c45422..8dff1085f 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -323,10 +323,6 @@ public function isValid($document): bool } } } else { - if($type == Database::VAR_INTEGER) { - var_dump($value); - } - if (!$validator->isValid($value)) { $this->message = 'Attribute "'.$key.'" has invalid '.$label.'. '.$validator->getDescription(); return false; From 464faf34b4b07a19083a3a095e6ac1a37e8433bc Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jan 2024 12:38:53 +0200 Subject: [PATCH 4/8] Float tests --- src/Database/Database.php | 5 +- src/Database/Validator/Structure.php | 46 +++--- tests/e2e/Adapter/Base.php | 206 ++++++++++++++++--------- tests/unit/Validator/StructureTest.php | 79 +++++++++- 4 files changed, 235 insertions(+), 101 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 7478bac2f..723e0f8f1 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -32,8 +32,9 @@ class Database public const VAR_BOOLEAN = 'boolean'; public const VAR_DATETIME = 'datetime'; - public const MAX_INTEGER = 2147483647; - public const MAX_BIG_INTEGER = PHP_INT_MAX; + public const INT_MAX = 2147483647; + public const BIG_INT_MAX = PHP_INT_MAX; + public const DOUBLE_MAX = PHP_FLOAT_MAX; // Relationship Types public const VAR_RELATIONSHIP = 'relationship'; diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 8dff1085f..b1a46a10c 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -11,6 +11,7 @@ use Utopia\Validator; use Utopia\Validator\Boolean; use Utopia\Validator\FloatValidator; +use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; @@ -250,7 +251,7 @@ public function isValid($document): bool $format = $attribute['format'] ?? ''; $required = $attribute['required'] ?? false; $size = $attribute['size'] ?? 0; - $signed = $attribute['signed'] ?? false; + $signed = $attribute['signed'] ?? true; if ($required === false && is_null($value)) { // Allow null value to optional params continue; @@ -260,33 +261,32 @@ public function isValid($document): bool continue; } + $validators = []; + switch ($type) { case Database::VAR_STRING: - $validator = new Text($size, min: 0); + $validators[] = new Text($size, min: 0); break; case Database::VAR_INTEGER: - $max = $size >= 8 ? Database::MAX_BIG_INTEGER : Database::MAX_INTEGER; - $min = -$max; - - if($signed === false) { - //$max *= 2; - $min = 0; - } - - $validator = new Range($min, $max, Database::VAR_INTEGER); + $validators[] = new Integer(); + $max = $size >= 8 ? Database::BIG_INT_MAX : Database::INT_MAX; + $min = $signed ? -$max : 0; + $validators[] = new Range($min, $max, Database::VAR_INTEGER); break; case Database::VAR_FLOAT: - $validator = new FloatValidator(); + $validators[] = new FloatValidator(); + $min = $signed ? -Database::DOUBLE_MAX : 0; + $validators[] = new Range($min, Database::DOUBLE_MAX, Database::VAR_FLOAT); break; case Database::VAR_BOOLEAN: - $validator = new Boolean(); + $validators[] = new Boolean(); break; case Database::VAR_DATETIME: - $validator = new DatetimeValidator(); + $validators[] = new DatetimeValidator(); break; default: @@ -300,7 +300,7 @@ public function isValid($document): bool if ($format) { // Format encoded as json string containing format name and relevant format options $format = self::getFormat($format, $type); - $validator = $format['callback']($attribute); + $validators = [$format['callback']($attribute)]; } if ($array) { // Validate attribute type for arrays - format for arrays handled separately @@ -317,15 +317,19 @@ public function isValid($document): bool continue; } - if (!$validator->isValid($child)) { - $this->message = 'Attribute "'.$key.'[\''.$x.'\']" has invalid '.$label.'. '.$validator->getDescription(); - return false; + foreach ($validators as $validator){ + if (!$validator->isValid($child)) { + $this->message = 'Attribute "'.$key.'[\''.$x.'\']" has invalid '.$label.'. '.$validator->getDescription(); + return false; + } } } } else { - if (!$validator->isValid($value)) { - $this->message = 'Attribute "'.$key.'" has invalid '.$label.'. '.$validator->getDescription(); - return false; + foreach ($validators as $validator){ + if (!$validator->isValid($value)) { + $this->message = 'Attribute "'.$key.'" has invalid '.$label.'. '.$validator->getDescription(); + return false; + } } } } diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index c6c608596..d32ea590a 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -4,7 +4,6 @@ use Exception; use PHPUnit\Framework\TestCase; -use Swoole\FastCGI\Record\Data; use Throwable; use Utopia\Database\Adapter\SQL; use Utopia\Database\Database; @@ -1170,7 +1169,8 @@ public function testCreateDocument(): Document $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'integer_unsigned', Database::VAR_INTEGER, 4, true, signed: false)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'bigint_signed', Database::VAR_INTEGER, 8, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'bigint_unsigned', Database::VAR_INTEGER, 9, true, signed: false)); - $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float_signed', Database::VAR_FLOAT, 0, true)); + $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'float_unsigned', Database::VAR_FLOAT, 0, true, signed: false)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'boolean', Database::VAR_BOOLEAN, 0, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'colors', Database::VAR_STRING, 32, true, null, true, true)); $this->assertEquals(true, static::getDatabase()->createAttribute('documents', 'empty', Database::VAR_STRING, 32, false, null, true, true)); @@ -1192,11 +1192,12 @@ public function testCreateDocument(): Document Permission::delete(Role::user(ID::custom('2x'))), ], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], 'empty' => [], @@ -1207,15 +1208,17 @@ public function testCreateDocument(): Document $this->assertIsString($document->getAttribute('string')); $this->assertEquals('textđź“ť', $document->getAttribute('string')); // Also makes sure an emoji is working $this->assertIsInt($document->getAttribute('integer_signed')); - $this->assertEquals(-Database::MAX_INTEGER, $document->getAttribute('integer_signed')); + $this->assertEquals(-Database::INT_MAX, $document->getAttribute('integer_signed')); $this->assertIsInt($document->getAttribute('integer_unsigned')); - $this->assertEquals(Database::MAX_INTEGER, $document->getAttribute('integer_unsigned')); + $this->assertEquals(Database::INT_MAX, $document->getAttribute('integer_unsigned')); $this->assertIsInt($document->getAttribute('bigint_signed')); - $this->assertEquals(-Database::MAX_BIG_INTEGER, $document->getAttribute('bigint_signed')); + $this->assertEquals(-Database::BIG_INT_MAX, $document->getAttribute('bigint_signed')); $this->assertIsInt($document->getAttribute('bigint_signed')); - $this->assertEquals(Database::MAX_BIG_INTEGER, $document->getAttribute('bigint_unsigned')); - $this->assertIsFloat($document->getAttribute('float')); - $this->assertEquals(5.55, $document->getAttribute('float')); + $this->assertEquals(Database::BIG_INT_MAX, $document->getAttribute('bigint_unsigned')); + $this->assertIsFloat($document->getAttribute('float_signed')); + $this->assertEquals(-5.55, $document->getAttribute('float_signed')); + $this->assertIsFloat($document->getAttribute('float_unsigned')); + $this->assertEquals(5.55, $document->getAttribute('float_unsigned')); $this->assertIsBool($document->getAttribute('boolean')); $this->assertEquals(true, $document->getAttribute('boolean')); $this->assertIsArray($document->getAttribute('colors')); @@ -1242,11 +1245,12 @@ public function testCreateDocument(): Document Permission::delete(Role::user(ID::custom('2x'))), ], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], 'empty' => [], @@ -1258,15 +1262,17 @@ public function testCreateDocument(): Document $this->assertIsString($manualIdDocument->getAttribute('string')); $this->assertEquals('textđź“ť', $manualIdDocument->getAttribute('string')); // Also makes sure an emoji is working $this->assertIsInt($manualIdDocument->getAttribute('integer_signed')); - $this->assertEquals(-Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_signed')); + $this->assertEquals(-Database::INT_MAX, $manualIdDocument->getAttribute('integer_signed')); $this->assertIsInt($manualIdDocument->getAttribute('integer_unsigned')); - $this->assertEquals(Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_unsigned')); + $this->assertEquals(Database::INT_MAX, $manualIdDocument->getAttribute('integer_unsigned')); $this->assertIsInt($manualIdDocument->getAttribute('bigint_signed')); - $this->assertEquals(-Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_signed')); + $this->assertEquals(-Database::BIG_INT_MAX, $manualIdDocument->getAttribute('bigint_signed')); $this->assertIsInt($manualIdDocument->getAttribute('bigint_unsigned')); - $this->assertEquals(Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_unsigned')); - $this->assertIsFloat($manualIdDocument->getAttribute('float')); - $this->assertEquals(5.55, $manualIdDocument->getAttribute('float')); + $this->assertEquals(Database::BIG_INT_MAX, $manualIdDocument->getAttribute('bigint_unsigned')); + $this->assertIsFloat($manualIdDocument->getAttribute('float_signed')); + $this->assertEquals(-5.55, $manualIdDocument->getAttribute('float_signed')); + $this->assertIsFloat($manualIdDocument->getAttribute('float_unsigned')); + $this->assertEquals(5.55, $manualIdDocument->getAttribute('float_unsigned')); $this->assertIsBool($manualIdDocument->getAttribute('boolean')); $this->assertEquals(true, $manualIdDocument->getAttribute('boolean')); $this->assertIsArray($manualIdDocument->getAttribute('colors')); @@ -1281,15 +1287,17 @@ public function testCreateDocument(): Document $this->assertIsString($manualIdDocument->getAttribute('string')); $this->assertEquals('textđź“ť', $manualIdDocument->getAttribute('string')); // Also makes sure an emoji is working $this->assertIsInt($manualIdDocument->getAttribute('integer_signed')); - $this->assertEquals(-Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_signed')); + $this->assertEquals(-Database::INT_MAX, $manualIdDocument->getAttribute('integer_signed')); $this->assertIsInt($manualIdDocument->getAttribute('integer_unsigned')); - $this->assertEquals(Database::MAX_INTEGER, $manualIdDocument->getAttribute('integer_unsigned')); + $this->assertEquals(Database::INT_MAX, $manualIdDocument->getAttribute('integer_unsigned')); $this->assertIsInt($manualIdDocument->getAttribute('bigint_signed')); - $this->assertEquals(-Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_signed')); + $this->assertEquals(-Database::BIG_INT_MAX, $manualIdDocument->getAttribute('bigint_signed')); $this->assertIsInt($manualIdDocument->getAttribute('bigint_unsigned')); - $this->assertEquals(Database::MAX_BIG_INTEGER, $manualIdDocument->getAttribute('bigint_unsigned')); - $this->assertIsFloat($manualIdDocument->getAttribute('float')); - $this->assertEquals(5.55, $manualIdDocument->getAttribute('float')); + $this->assertEquals(Database::BIG_INT_MAX, $manualIdDocument->getAttribute('bigint_unsigned')); + $this->assertIsFloat($manualIdDocument->getAttribute('float_signed')); + $this->assertEquals(-5.55, $manualIdDocument->getAttribute('float_signed')); + $this->assertIsFloat($manualIdDocument->getAttribute('float_unsigned')); + $this->assertEquals(5.55, $manualIdDocument->getAttribute('float_unsigned')); $this->assertIsBool($manualIdDocument->getAttribute('boolean')); $this->assertEquals(true, $manualIdDocument->getAttribute('boolean')); $this->assertIsArray($manualIdDocument->getAttribute('colors')); @@ -1297,6 +1305,44 @@ public function testCreateDocument(): Document $this->assertEquals([], $manualIdDocument->getAttribute('empty')); $this->assertEquals('Works', $manualIdDocument->getAttribute('with-dash')); + try { + static::getDatabase()->createDocument('documents', new Document([ + 'string' => '', + 'integer_signed' => 0, + 'integer_unsigned' => 0, + 'bigint_signed' => 0, + 'bigint_unsigned' => 0, + 'float_signed' => 0, + 'float_unsigned' => -5.55, + 'boolean' => true, + 'colors' => [], + 'empty' => [], + ])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertTrue($e instanceof StructureException); + $this->assertStringContainsString('Invalid document structure: Attribute "float_unsigned" has invalid type. Value must be a valid range between 0 and', $e->getMessage()); + } + + try { + static::getDatabase()->createDocument('documents', new Document([ + 'string' => '', + 'integer_signed' => 0, + 'integer_unsigned' => 0, + 'bigint_signed' => 0, + 'bigint_unsigned' => -10, + 'float_signed' => 0, + 'float_unsigned' => 0, + 'boolean' => true, + 'colors' => [], + 'empty' => [], + ])); + $this->fail('Failed to throw exception'); + } catch(Throwable $e) { + $this->assertTrue($e instanceof StructureException); + $this->assertEquals('Invalid document structure: Attribute "bigint_unsigned" has invalid type. Value must be a valid range between 0 and 9,223,372,036,854,775,808', $e->getMessage()); + } + return $document; } @@ -1327,7 +1373,7 @@ public function testCreateDocuments(): array ], 'string' => 'textđź“ť', 'integer' => 5, - 'bigint' => 8589934592, // 2^33 + 'bigint' => Database::BIG_INT_MAX, ]); } @@ -1342,7 +1388,7 @@ public function testCreateDocuments(): array $this->assertIsInt($document->getAttribute('integer')); $this->assertEquals(5, $document->getAttribute('integer')); $this->assertIsInt($document->getAttribute('bigint')); - $this->assertEquals(8589934592, $document->getAttribute('bigint')); + $this->assertEquals(9223372036854775807, $document->getAttribute('bigint')); } return $documents; @@ -1513,9 +1559,11 @@ public function testGetDocument(Document $document): Document $this->assertIsString($document->getAttribute('string')); $this->assertEquals('textđź“ť', $document->getAttribute('string')); $this->assertIsInt($document->getAttribute('integer_signed')); - $this->assertEquals(-Database::MAX_INTEGER, $document->getAttribute('integer_signed')); - $this->assertIsFloat($document->getAttribute('float')); - $this->assertEquals(5.55, $document->getAttribute('float')); + $this->assertEquals(-Database::INT_MAX, $document->getAttribute('integer_signed')); + $this->assertIsFloat($document->getAttribute('float_signed')); + $this->assertEquals(-5.55, $document->getAttribute('float_signed')); + $this->assertIsFloat($document->getAttribute('float_unsigned')); + $this->assertEquals(5.55, $document->getAttribute('float_unsigned')); $this->assertIsBool($document->getAttribute('boolean')); $this->assertEquals(true, $document->getAttribute('boolean')); $this->assertIsArray($document->getAttribute('colors')); @@ -1541,7 +1589,7 @@ public function testGetDocumentSelect(Document $document): Document $this->assertIsString($document->getAttribute('string')); $this->assertEquals('textđź“ť', $document->getAttribute('string')); $this->assertIsInt($document->getAttribute('integer_signed')); - $this->assertEquals(-Database::MAX_INTEGER, $document->getAttribute('integer_signed')); + $this->assertEquals(-Database::INT_MAX, $document->getAttribute('integer_signed')); $this->assertArrayNotHasKey('float', $document->getAttributes()); $this->assertArrayNotHasKey('boolean', $document->getAttributes()); $this->assertArrayNotHasKey('colors', $document->getAttributes()); @@ -1659,7 +1707,8 @@ public function testListDocumentSearch(): void 'integer_unsigned' => 0, 'bigint_signed' => 0, 'bigint_unsigned' => 0, - 'float' => 5.55, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], 'empty' => [], @@ -1706,8 +1755,10 @@ public function testUpdateDocument(Document $document): Document { $document ->setAttribute('string', 'textđź“ť updated') - ->setAttribute('integer_signed', 6) - ->setAttribute('float', 5.56) + ->setAttribute('integer_signed', -6) + ->setAttribute('integer_unsigned', 6) + ->setAttribute('float_signed', -5.56) + ->setAttribute('float_unsigned', 5.56) ->setAttribute('boolean', false) ->setAttribute('colors', 'red', Document::SET_TYPE_APPEND) ->setAttribute('with-dash', 'Works'); @@ -1718,9 +1769,13 @@ public function testUpdateDocument(Document $document): Document $this->assertIsString($new->getAttribute('string')); $this->assertEquals('textđź“ť updated', $new->getAttribute('string')); $this->assertIsInt($new->getAttribute('integer_signed')); - $this->assertEquals(6, $new->getAttribute('integer_signed')); - $this->assertIsFloat($new->getAttribute('float')); - $this->assertEquals(5.56, $new->getAttribute('float')); + $this->assertEquals(-6, $new->getAttribute('integer_signed')); + $this->assertIsInt($new->getAttribute('integer_unsigned')); + $this->assertEquals(6, $new->getAttribute('integer_unsigned')); + $this->assertIsFloat($new->getAttribute('float_signed')); + $this->assertEquals(-5.56, $new->getAttribute('float_signed')); + $this->assertIsFloat($new->getAttribute('float_unsigned')); + $this->assertEquals(5.56, $new->getAttribute('float_unsigned')); $this->assertIsBool($new->getAttribute('boolean')); $this->assertEquals(false, $new->getAttribute('boolean')); $this->assertIsArray($new->getAttribute('colors')); @@ -1997,7 +2052,7 @@ public function testArrayAttribute(): void ])); $this->fail('Failed to throw exception'); } catch(Throwable $e) { - $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid range between 0 and 2,147,483,647', $e->getMessage()); + $this->assertEquals('Invalid document structure: Attribute "age" has invalid type. Value must be a valid integer', $e->getMessage()); } try { @@ -4047,11 +4102,12 @@ public function testReadPermissionsSuccess(Document $document): Document Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], ])); @@ -4081,11 +4137,12 @@ public function testReadPermissionsFailure(): Document Permission::delete(Role::user('1')), ], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], ])); @@ -4117,11 +4174,12 @@ public function testWritePermissionsSuccess(Document $document): void Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], ])); @@ -4145,11 +4203,12 @@ public function testWritePermissionsUpdateFailure(Document $document): Document Permission::delete(Role::any()), ], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -5.55, + 'float_unsigned' => 5.55, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], ])); @@ -4166,8 +4225,9 @@ public function testWritePermissionsUpdateFailure(Document $document): Document ], 'string' => 'textđź“ť', 'integer_signed' => 6, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'float_signed' => -Database::DOUBLE_MAX, + 'float_unsigned' => Database::DOUBLE_MAX, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], ])); @@ -4175,20 +4235,18 @@ public function testWritePermissionsUpdateFailure(Document $document): Document return $document; } - /** - * @depends testCreateDocument - */ - public function testNoChangeUpdateDocumentWithoutPermission(Document $document): Document + public function testNoChangeUpdateDocumentWithoutPermission(): Document { $document = static::getDatabase()->createDocument('documents', new Document([ '$id' => ID::unique(), '$permissions' => [], 'string' => 'textđź“ť', - 'integer_signed' => -Database::MAX_INTEGER, - 'integer_unsigned' => Database::MAX_INTEGER, - 'bigint_signed' => -Database::MAX_BIG_INTEGER, - 'bigint_unsigned' => Database::MAX_BIG_INTEGER, - 'float' => 5.55, + 'integer_signed' => -Database::INT_MAX, + 'integer_unsigned' => Database::INT_MAX, + 'bigint_signed' => -Database::BIG_INT_MAX, + 'bigint_unsigned' => Database::BIG_INT_MAX, + 'float_signed' => -123456789.12346, + 'float_unsigned' => 123456789.12346, 'boolean' => true, 'colors' => ['pink', 'green', 'blue'], ])); diff --git a/tests/unit/Validator/StructureTest.php b/tests/unit/Validator/StructureTest.php index 1aa7dd5e5..f714d52da 100644 --- a/tests/unit/Validator/StructureTest.php +++ b/tests/unit/Validator/StructureTest.php @@ -65,7 +65,7 @@ class StructureTest extends TestCase 'format' => '', 'size' => 5, 'required' => true, - 'signed' => true, + 'signed' => false, 'array' => false, 'filters' => [], ], @@ -220,6 +220,24 @@ public function testUnknownKeys(): void $this->assertEquals('Invalid document structure: Unknown attribute: "titlex"', $validator->getDescription()); } + public function testIntegerAsString(): void + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'Demo Title', + 'description' => 'Demo description', + 'rating' => '5', + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + ]))); + + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); + } + public function testValidDocument(): void { $validator = new Structure(new Document($this->collection)); @@ -325,7 +343,7 @@ public function testIntegerValidation(): void 'feedback' => 'team@appwrite.io', ]))); - $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); $this->assertEquals(false, $validator->isValid(new Document([ '$collection' => ID::custom('posts'), @@ -338,7 +356,7 @@ public function testIntegerValidation(): void 'feedback' => 'team@appwrite.io', ]))); - $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid integer', $validator->getDescription()); } public function testArrayOfIntegersValidation(): void @@ -393,7 +411,7 @@ public function testArrayOfIntegersValidation(): void 'feedback' => 'team@appwrite.io', ]))); - $this->assertEquals('Invalid document structure: Attribute "reviews[\'0\']" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); + $this->assertEquals('Invalid document structure: Attribute "reviews[\'0\']" has invalid type. Value must be a valid integer', $validator->getDescription()); } public function testFloatValidation(): void @@ -475,4 +493,57 @@ public function testFormatValidation(): void $this->assertEquals('Invalid document structure: Attribute "feedback" has invalid format. Value must be a valid email address', $validator->getDescription()); } + + public function testIntegerMaxRange(): void + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => PHP_INT_MAX, + 'price' => 1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + ]))); + + $this->assertEquals('Invalid document structure: Attribute "rating" has invalid type. Value must be a valid range between -2,147,483,647 and 2,147,483,647', $validator->getDescription()); + } + + public function testDoubleUnsigned(): void + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 5, + 'price' => -1.99, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + ]))); + + $this->assertStringContainsString('Invalid document structure: Attribute "price" has invalid type. Value must be a valid range between 0 and ', $validator->getDescription()); + } + + public function testDoubleMaxRange(): void + { + $validator = new Structure(new Document($this->collection)); + + $this->assertEquals(false, $validator->isValid(new Document([ + '$collection' => ID::custom('posts'), + 'title' => 'string', + 'description' => 'Demo description', + 'rating' => 1, + 'price' => INF, + 'published' => true, + 'tags' => ['dog', 'cat', 'mouse'], + 'feedback' => 'team@appwrite.io', + ]))); + } + } From c8abc879387c69145ffea824e97a581b15253ee7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jan 2024 12:41:51 +0200 Subject: [PATCH 5/8] formatting --- src/Database/Validator/Structure.php | 4 ++-- tests/e2e/Adapter/Base.php | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index b1a46a10c..d30b01b6b 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -317,7 +317,7 @@ public function isValid($document): bool continue; } - foreach ($validators as $validator){ + foreach ($validators as $validator) { if (!$validator->isValid($child)) { $this->message = 'Attribute "'.$key.'[\''.$x.'\']" has invalid '.$label.'. '.$validator->getDescription(); return false; @@ -325,7 +325,7 @@ public function isValid($document): bool } } } else { - foreach ($validators as $validator){ + foreach ($validators as $validator) { if (!$validator->isValid($value)) { $this->message = 'Attribute "'.$key.'" has invalid '.$label.'. '.$validator->getDescription(); return false; diff --git a/tests/e2e/Adapter/Base.php b/tests/e2e/Adapter/Base.php index d32ea590a..e6d0683af 100644 --- a/tests/e2e/Adapter/Base.php +++ b/tests/e2e/Adapter/Base.php @@ -1903,8 +1903,6 @@ public function testDeleteDocumentConflict(Document $document): void */ public function testUpdateDocumentDuplicatePermissions(Document $document): Document { - - var_dump($document); $new = $this->getDatabase()->updateDocument($document->getCollection(), $document->getId(), $document); $new From 9bd2555dd38ab1ee50d967bd5a218ee1cd44a1a1 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 Jan 2024 16:12:05 +0200 Subject: [PATCH 6/8] stopOnFailure --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 783265d80..ccdaa969e 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> + stopOnFailure="false"> ./tests/unit From 87f72ad13364695798638f08f4a664c0ab116a26 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 30 Jan 2024 09:40:56 +0200 Subject: [PATCH 7/8] format validation change --- src/Database/Validator/Structure.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index d30b01b6b..6500e5cd7 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -300,7 +300,7 @@ public function isValid($document): bool if ($format) { // Format encoded as json string containing format name and relevant format options $format = self::getFormat($format, $type); - $validators = [$format['callback']($attribute)]; + $validators[] = $format['callback']($attribute); } if ($array) { // Validate attribute type for arrays - format for arrays handled separately From cb2b32f9b49e78cff093016dd04960894d2dd1ee Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 30 Jan 2024 09:44:21 +0200 Subject: [PATCH 8/8] Add comment implicitly casts non-numeric --- src/Database/Validator/Structure.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Database/Validator/Structure.php b/src/Database/Validator/Structure.php index 6500e5cd7..ecf62e367 100644 --- a/src/Database/Validator/Structure.php +++ b/src/Database/Validator/Structure.php @@ -269,6 +269,7 @@ public function isValid($document): bool break; case Database::VAR_INTEGER: + // We need both Integer and Range because Range implicitly casts non-numeric values $validators[] = new Integer(); $max = $size >= 8 ? Database::BIG_INT_MAX : Database::INT_MAX; $min = $signed ? -$max : 0; @@ -276,6 +277,7 @@ public function isValid($document): bool break; case Database::VAR_FLOAT: + // We need both Float and Range because Range implicitly casts non-numeric values $validators[] = new FloatValidator(); $min = $signed ? -Database::DOUBLE_MAX : 0; $validators[] = new Range($min, Database::DOUBLE_MAX, Database::VAR_FLOAT);