From 615a530e6434e74742b6b12dabee5993ba8575fd Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Fri, 10 Apr 2026 16:40:59 +0530 Subject: [PATCH 1/2] feat: add StructureException handling for invalid datetime values in Mongo adapter and corresponding tests --- src/Database/Adapter/Mongo.php | 7 ++- tests/e2e/Adapter/Scopes/DocumentTests.php | 57 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index 7ddde43d3..b654b436e 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -13,6 +13,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Exception\Type as TypeException; @@ -1414,7 +1415,11 @@ public function castingBefore(Document $collection, Document $document): Documen switch ($type) { case Database::VAR_DATETIME: if (!($node instanceof UTCDateTime)) { - $node = new UTCDateTime(new \DateTime($node)); + try { + $node = new UTCDateTime(new \DateTime($node)); + } catch (\Throwable $e) { + throw new StructureException('Invalid datetime value for attribute "' . $key . '": ' . $e->getMessage()); + } } break; case Database::VAR_OBJECT: diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index dd91cc0bd..d9327093e 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -5801,6 +5801,63 @@ public function testDateTimeDocument(): void $database->deleteCollection($collection); } + public function testInvalidCreatedAndUpdatedAtThrowStructureException(): void + { + /** @var Database $database */ + $database = $this->getDatabase(); + $collection = 'invalid_date_attributes'; + + $database->createCollection($collection); + $this->assertEquals(true, $database->createAttribute($collection, 'string', Database::VAR_STRING, 128, false)); + + $invalidDates = [ + 'not-a-date', + '2026-13-01T00:00:00.000+00:00', + ]; + + $database->setPreserveDates(true); + + try { + foreach ($invalidDates as $index => $invalidDate) { + foreach (['$createdAt', '$updatedAt'] as $attribute) { + try { + if ($attribute === '$createdAt') { + $database->createDocument($collection, new Document([ + '$id' => $attribute . '-' . $index, + '$permissions' => [ + Permission::read(Role::any()), + Permission::write(Role::any()), + Permission::update(Role::any()), + ], + 'string' => 'invalid-date', + $attribute => $invalidDate, + ])); + } else { + $document = $database->createDocument($collection, new Document([ + '$id' => 'doc-' . $index, + '$permissions' => [ + Permission::read(Role::any()), + Permission::write(Role::any()), + Permission::update(Role::any()), + ], + 'string' => 'valid-date', + ])); + $document->setAttribute($attribute, $invalidDate); + $database->updateDocument($collection, $document->getId(), $document); + } + + $this->fail('Expected StructureException for invalid ' . $attribute); + } catch (Throwable $e) { + $this->assertInstanceOf(StructureException::class, $e); + } + } + } + } finally { + $database->setPreserveDates(false); + $database->deleteCollection($collection); + } + } + public function testSingleDocumentDateOperations(): void { /** @var Database $database */ From 81d3b85b2ce576219834ca258f0f0df20a8390fa Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Fri, 10 Apr 2026 16:59:18 +0530 Subject: [PATCH 2/2] updated tests --- tests/e2e/Adapter/Scopes/DocumentTests.php | 75 +++++++++++----------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index d9327093e..49d75e4b6 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -5805,52 +5805,53 @@ public function testInvalidCreatedAndUpdatedAtThrowStructureException(): void { /** @var Database $database */ $database = $this->getDatabase(); + + if (!$database->getAdapter()->getSupportForAttributes()) { + $this->expectNotToPerformAssertions(); + return; + } + $collection = 'invalid_date_attributes'; $database->createCollection($collection); $this->assertEquals(true, $database->createAttribute($collection, 'string', Database::VAR_STRING, 128, false)); - $invalidDates = [ - 'not-a-date', - '2026-13-01T00:00:00.000+00:00', - ]; - $database->setPreserveDates(true); try { - foreach ($invalidDates as $index => $invalidDate) { - foreach (['$createdAt', '$updatedAt'] as $attribute) { - try { - if ($attribute === '$createdAt') { - $database->createDocument($collection, new Document([ - '$id' => $attribute . '-' . $index, - '$permissions' => [ - Permission::read(Role::any()), - Permission::write(Role::any()), - Permission::update(Role::any()), - ], - 'string' => 'invalid-date', - $attribute => $invalidDate, - ])); - } else { - $document = $database->createDocument($collection, new Document([ - '$id' => 'doc-' . $index, - '$permissions' => [ - Permission::read(Role::any()), - Permission::write(Role::any()), - Permission::update(Role::any()), - ], - 'string' => 'valid-date', - ])); - $document->setAttribute($attribute, $invalidDate); - $database->updateDocument($collection, $document->getId(), $document); - } + // Outside allowed year range (Structure uses DatetimeValidator min/max, e.g. 0000–9999). + $invalidDate = '10000-01-01T00:00:00.000+00:00'; - $this->fail('Expected StructureException for invalid ' . $attribute); - } catch (Throwable $e) { - $this->assertInstanceOf(StructureException::class, $e); - } - } + try { + $database->createDocument($collection, new Document([ + '$id' => 'doc1', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + '$createdAt' => $invalidDate, + ])); + $this->fail('Expected StructureException for invalid $createdAt'); + } catch (Throwable $e) { + $this->assertInstanceOf(StructureException::class, $e); + } + + $database->createDocument($collection, new Document([ + '$id' => 'doc2', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + ], + 'string' => 'x', + ])); + + try { + $database->updateDocument($collection, 'doc2', new Document([ + '$updatedAt' => $invalidDate, + ])); + $this->fail('Expected StructureException for invalid $updatedAt'); + } catch (Throwable $e) { + $this->assertInstanceOf(StructureException::class, $e); } } finally { $database->setPreserveDates(false);