From 1683d34d13630b55ed244a6385b081815fb3a135 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 9 Jan 2026 01:22:06 +1300 Subject: [PATCH] Remove DB sleeps --- .../Databases/Legacy/DatabasesBase.php | 2 + .../Legacy/DatabasesCustomClientTest.php | 55 +++-- .../Legacy/DatabasesCustomServerTest.php | 104 ++++----- .../Legacy/Transactions/ACIDTest.php | 11 +- .../Databases/TablesDB/DatabasesBase.php | 15 ++ .../TablesDB/DatabasesCustomClientTest.php | 38 ++- .../TablesDB/DatabasesCustomServerTest.php | 117 ++++++---- tests/e2e/Traits/SchemaPoll.php | 216 ++++++++++++++++++ 8 files changed, 410 insertions(+), 148 deletions(-) create mode 100644 tests/e2e/Traits/SchemaPoll.php diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 6cde01e240..c54b1bfbde 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4,6 +4,7 @@ namespace Tests\E2E\Services\Databases\Legacy; use Appwrite\Extend\Exception; use Tests\E2E\Client; +use Tests\E2E\Traits\SchemaPoll; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -16,6 +17,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator; trait DatabasesBase { + use SchemaPoll; public function testCreateDatabase(): array { /** diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php index 0fe5d21da1..5e41ead154 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php @@ -64,10 +64,10 @@ class DatabasesCustomClientTest extends Scope 'required' => true, ]); - sleep(1); - $this->assertEquals(202, $response['headers']['status-code']); + $this->waitForAttribute($databaseId, $moviesId, 'title'); + // Document aliases write to update, delete $document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ 'content-type' => 'application/json', @@ -157,8 +157,7 @@ class DatabasesCustomClientTest extends Scope ]); $this->assertEquals(202, $response['headers']['status-code']); - // Wait for database worker to finish creating attributes - sleep(2); + $this->waitForAttribute($databaseId, 'permissionCheck', 'name'); // Creating document by server, give read permission to our user + some other user $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/permissionCheck/documents', array_merge([ @@ -212,8 +211,18 @@ class DatabasesCustomClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); - // Wait for database worker to finish deleting collection - sleep(2); + $start = microtime(true) * 1000; + while ((microtime(true) * 1000) - $start < 30000) { + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/permissionCheck', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + if ($response['headers']['status-code'] === 404) { + break; + } + usleep(100 * 1000); + } // Make sure collection has been deleted $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/permissionCheck', array_merge([ @@ -275,7 +284,7 @@ class DatabasesCustomClientTest extends Scope ] ]); - \sleep(2); + // Collections are created synchronously, no wait needed // Creating two way relationship between collection 1 and collection 2 from collection 1 $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([ @@ -291,7 +300,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => $collection1['body']['$id'] ]); - \sleep(3); + $this->waitForAttribute($databaseId, $collection1['body']['$id'], $collection2['body']['$id']); // Update relation from collection 2 to on delete restrict $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collection2['body']['$id'] . '/attributes/' . $collection1['body']['$id'] . '/relationship', array_merge([ @@ -362,7 +371,7 @@ class DatabasesCustomClientTest extends Scope ] ]); - \sleep(2); + // Collections are created synchronously, no wait needed $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([ 'content-type' => 'application/json', @@ -377,11 +386,11 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'same_key' ]); - \sleep(2); - $this->assertEquals(202, $relation['headers']['status-code']); $this->assertEquals('same_key', $relation['body']['twoWayKey']); + $this->waitForAttribute($databaseId, $collection1['body']['$id'], 'attr1'); + $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -395,8 +404,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'same_key' ]); - \sleep(2); - + // No wait needed - request fails immediately with 409 $this->assertEquals(409, $relation['body']['code']); $this->assertEquals('Attribute with the requested key \'same_key\' already exists. Attribute keys must be unique, try again with a different key.', $relation['body']['message']); @@ -413,11 +421,11 @@ class DatabasesCustomClientTest extends Scope 'key' => 'attr3', ]); - \sleep(2); - $this->assertEquals(202, $relation['headers']['status-code']); $this->assertArrayHasKey('twoWayKey', $relation['body']); + $this->waitForAttribute($databaseId, $collection1['body']['$id'], 'attr3'); + // twoWayKey is null, TwoWayKey is default, second POST $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([ 'content-type' => 'application/json', @@ -431,8 +439,7 @@ class DatabasesCustomClientTest extends Scope 'key' => 'attr4', ]); - \sleep(2); - + // No wait needed - request fails immediately with 409 $this->assertEquals('Attribute with the requested key \'attr4\' already exists. Attribute keys must be unique, try again with a different key.', $relation['body']['message']); $this->assertEquals(409, $relation['body']['code']); @@ -450,11 +457,11 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'playlist', ]); - \sleep(2); - $this->assertEquals(202, $relation['headers']['status-code']); $this->assertArrayHasKey('twoWayKey', $relation['body']); + $this->waitForAttribute($databaseId, $collection1['body']['$id'], 'songs'); + // Second RelationshipManyToMany on Same collections $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([ 'content-type' => 'application/json', @@ -469,8 +476,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'playlist2', ]); - \sleep(2); - + // No wait needed - request fails immediately with 409 $this->assertEquals(409, $relation['body']['code']); $this->assertEquals('Creating more than one "manyToMany" relationship on the same collection is currently not permitted.', $relation['body']['message']); } @@ -674,7 +680,12 @@ class DatabasesCustomClientTest extends Scope 'default' => null, ]); - \sleep(2); + $this->waitForAllAttributes($databaseId, $collection1['body']['$id']); + $this->waitForAllAttributes($databaseId, $collection2['body']['$id']); + $this->waitForAllAttributes($databaseId, $collection3['body']['$id']); + $this->waitForAllAttributes($databaseId, $collection4['body']['$id']); + $this->waitForAllAttributes($databaseId, $collection5['body']['$id']); + // Creating parent document with a child reference to test the permissions $parentDocument = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/documents', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php index 7e960d82d5..82ea3e78ed 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php @@ -744,7 +744,7 @@ class DatabasesCustomServerTest extends Scope ]); $this->assertTrue($lastName['body']['encrypt']); - sleep(1); + $this->waitForAttribute($databaseId, $actors['body']['$id'], 'lastName'); $response = $this->client->call(Client::METHOD_GET, $attributesPath . '/lastName', array_merge([ 'content-type' => 'application/json', @@ -765,8 +765,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('lastName', $lastName['body']['key']); $this->assertEquals('string', $lastName['body']['type']); - // Wait for database worker to finish creating attributes - sleep(2); + $this->waitForAllAttributes($databaseId, $actors['body']['$id']); // Creating document to ensure cache is purged on schema change $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/documents', array_merge([ @@ -884,8 +883,7 @@ class DatabasesCustomServerTest extends Scope 'required' => true, ]); - // Wait for database worker to finish creating attributes - sleep(2); + $this->waitForAttribute($databaseId, $actors['body']['$id'], 'unneeded'); // Creating document to ensure cache is purged on schema change $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/documents', array_merge([ @@ -918,8 +916,7 @@ class DatabasesCustomServerTest extends Scope ], ]); - // Wait for database worker to finish creating index - sleep(2); + $this->waitForIndex($databaseId, $actors['body']['$id'], 'key_lastName'); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'], array_merge([ 'content-type' => 'application/json', @@ -947,7 +944,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $attribute['headers']['status-code']); - sleep(2); + $this->waitForAttributeDeletion($databaseId, $actors['body']['$id'], $unneededId); // Check document to ensure cache is purged on schema change $document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'] . '/documents/' . $document['body']['$id'], array_merge([ @@ -991,8 +988,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $index['headers']['status-code']); - // Wait for database worker to finish deleting index - sleep(2); + $this->waitForIndexDeletion($databaseId, $data['collectionId'], $data['key']); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['collectionId'], array_merge([ 'content-type' => 'application/json', @@ -1036,7 +1032,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('attribute1', $attribute1['body']['key']); $this->assertEquals('attribute2', $attribute2['body']['key']); - sleep(2); + $this->waitForAllAttributes($databaseId, $data['collectionId']); $index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['collectionId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1064,7 +1060,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('index1', $index1['body']['key']); $this->assertEquals('index2', $index2['body']['key']); - sleep(2); + $this->waitForAllIndexes($databaseId, $data['collectionId']); // Expected behavior: deleting attribute2 will cause index2 to be dropped, and index1 rebuilt with a single key $deleted = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['collectionId'] . '/attributes/' . $attribute2['body']['key'], array_merge([ @@ -1075,8 +1071,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $deleted['headers']['status-code']); - // wait for database worker to complete - sleep(2); + $this->waitForAttributeDeletion($databaseId, $data['collectionId'], $attribute2['body']['key']); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['collectionId'], array_merge([ 'content-type' => 'application/json', @@ -1164,7 +1159,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('attribute1', $attribute1['body']['key']); $this->assertEquals('attribute2', $attribute2['body']['key']); - sleep(2); + $this->waitForAllAttributes($databaseId, $collectionId); $index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1192,7 +1187,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('index1', $index1['body']['key']); $this->assertEquals('index2', $index2['body']['key']); - sleep(2); + $this->waitForAllIndexes($databaseId, $collectionId); // Expected behavior: deleting attribute1 would cause index1 to be a duplicate of index2 and automatically removed $deleted = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/' . $attribute1['body']['key'], array_merge([ @@ -1203,8 +1198,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $deleted['headers']['status-code']); - // wait for database worker to complete - sleep(2); + $this->waitForAttributeDeletion($databaseId, $collectionId, $attribute1['body']['key']); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', @@ -1353,7 +1347,7 @@ class DatabasesCustomServerTest extends Scope 'key' => 'collection2' ]); - sleep(2); + $this->waitForAttribute($databaseId, $collection1, 'collection2'); $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collection2, array_merge([ 'content-type' => 'application/json', @@ -1361,7 +1355,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'], ], $this->getHeaders())); - sleep(2); + $this->waitForAttributeDeletion($databaseId, $collection1, 'collection2'); $attributes = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes', array_merge([ 'content-type' => 'application/json', @@ -1422,7 +1416,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $attribute['headers']['status-code']); } - sleep(5); + $this->waitForAllAttributes($databaseId, $collectionId); $tooWide = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ 'content-type' => 'application/json', @@ -1489,7 +1483,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $attribute['headers']['status-code']); } - sleep(10); + $this->waitForAllAttributes($databaseId, $collectionId); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', @@ -1541,7 +1535,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals("key_attribute{$i}", $index['body']['key']); } - sleep(5); + $this->waitForAllIndexes($databaseId, $collectionId); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', @@ -1726,7 +1720,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $attribute['headers']['status-code']); - sleep(5); + $this->waitForAllAttributes($databaseId, $collectionId); return [ 'databaseId' => $databaseId, @@ -3745,7 +3739,7 @@ class DatabasesCustomServerTest extends Scope ] ]); - \sleep(2); + // Collections are created synchronously, no wait needed } public function cleanupRelationshipCollection(): void @@ -3756,7 +3750,20 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]); - \sleep(2); + // Poll until database is deleted (returns 404) + $start = microtime(true) * 1000; + $timeoutMs = 30000; + while ((microtime(true) * 1000) - $start < $timeoutMs) { + $response = $this->client->call(Client::METHOD_GET, '/databases/database1', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]); + if ($response['headers']['status-code'] === 404) { + return; + } + usleep(100 * 1000); + } } public function testAttributeRenameRelationshipOneToMany() @@ -3780,7 +3787,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $collection1Id, 'level2'); $collection1Attributes = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1Id, [ 'content-type' => 'application/json', @@ -3894,7 +3901,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $collection1Id, 'level2'); $collection1Attributes = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1Id, [ 'content-type' => 'application/json', @@ -4008,7 +4015,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $collection1Id, 'level2'); $collection1Attributes = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1Id, [ 'content-type' => 'application/json', @@ -4126,7 +4133,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $collection1Id, 'level2'); $collection1Attributes = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1Id, [ 'content-type' => 'application/json', @@ -4274,7 +4281,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberAttribute['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'number'); $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([ 'content-type' => 'application/json', @@ -4446,7 +4453,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([ 'content-type' => 'application/json', @@ -4512,8 +4519,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberAttribute['headers']['status-code']); - // Wait for database worker to create attributes - sleep(2); + $this->waitForAttribute($data['databaseId'], $data['$id'], 'number'); // Create documents $createBulkDocuments = function ($amount = 10) use ($data) { @@ -4544,7 +4550,7 @@ class DatabasesCustomServerTest extends Scope * This test specifically failed on 1.6.x response format, * could be due to the slow or overworked machine, but being safe here! */ - sleep(5); + usleep(500 * 1000); // 500ms for cache purge // TEST: Update all documents $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([ @@ -4570,7 +4576,7 @@ class DatabasesCustomServerTest extends Scope * This test specifically failed on 1.6.x response format, * could be due to the slow or overworked machine, but being safe here! */ - sleep(5); + usleep(500 * 1000); // 500ms for cache purge $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([ 'content-type' => 'application/json', @@ -4751,7 +4757,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([ 'content-type' => 'application/json', @@ -4819,8 +4825,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberAttribute['headers']['status-code']); - // Wait for database worker to create attributes - sleep(2); + $this->waitForAttribute($data['databaseId'], $data['$id'], 'number'); // Create documents $createBulkDocuments = function ($amount = 10) use ($data) { @@ -4944,7 +4949,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([ 'content-type' => 'application/json', @@ -5011,8 +5016,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberAttribute['headers']['status-code']); - // wait for database worker to create attributes - sleep(2); + $this->waitForAttribute($data['databaseId'], $data['$id'], 'number'); // Create documents $createBulkDocuments = function ($amount = 11) use ($data) { @@ -5285,7 +5289,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($data['databaseId'], $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([ 'content-type' => 'application/json', @@ -5345,7 +5349,7 @@ class DatabasesCustomServerTest extends Scope 'format' => 'datetime', ]); - sleep(1); + $this->waitForAllAttributes($databaseId, $collectionId); $date = '2000-01-01T10:00:00.000+00:00'; @@ -5466,7 +5470,7 @@ class DatabasesCustomServerTest extends Scope 'required' => false, ]); - sleep(1); + $this->waitForAttribute($databaseId, $collectionId, 'string'); $createDate = '2000-01-01T10:00:00.000+00:00'; $updateDate = '2000-02-01T15:30:00.000+00:00'; @@ -5753,7 +5757,7 @@ class DatabasesCustomServerTest extends Scope 'required' => false, ]); - sleep(1); + $this->waitForAttribute($databaseId, $collectionId, 'string'); $createDate = '2000-01-01T10:00:00.000+00:00'; $updateDate = '2000-02-01T15:30:00.000+00:00'; @@ -5984,7 +5988,7 @@ class DatabasesCustomServerTest extends Scope 'required' => false, ]); - sleep(1); + $this->waitForAttribute($databaseId, $collectionId, 'string'); $createDate = '2000-01-01T10:00:00.000+00:00'; $updateDate = '2000-02-01T15:30:00.000+00:00'; @@ -6296,8 +6300,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $polygonAttribute['headers']['status-code']); - // Wait for attributes to be created - sleep(2); + $this->waitForAllAttributes($databaseId, $collectionId); // Test 1: Bulk create with spatial data $spatialDocuments = []; @@ -6686,8 +6689,7 @@ class DatabasesCustomServerTest extends Scope // Handle both 201 (created) and 202 (accepted) status codes $this->assertEquals(202, $lineAttribute['headers']['status-code']); - // Wait for attributes to be created - sleep(2); + $this->waitForAllAttributes($databaseId, $collectionId); // Test bulk create with line string data $lineStringDocuments = []; diff --git a/tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php b/tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php index 4396290c06..fbdfc547a4 100644 --- a/tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php +++ b/tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php @@ -6,6 +6,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Tests\E2E\Traits\SchemaPoll; use Utopia\Database\Database; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -15,6 +16,7 @@ class ACIDTest extends Scope { use ProjectCustom; use SideClient; + use SchemaPoll; /** * Test atomicity - all operations succeed or all fail @@ -73,7 +75,8 @@ class ACIDTest extends Scope 'attributes' => ['email'] ]); - sleep(3); + $this->waitForAttribute($databaseId, $collectionId, 'email'); + $this->waitForIndex($databaseId, $collectionId, 'unique_email'); // Create first document outside transaction $doc1 = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ @@ -229,7 +232,7 @@ class ACIDTest extends Scope 'max' => 100 ]); - sleep(3); + $this->waitForAttribute($databaseId, $collectionId, 'age'); // Create transaction $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ @@ -350,7 +353,7 @@ class ACIDTest extends Scope 'max' => 1000000 ]); - sleep(2); + $this->waitForAttribute($databaseId, $collectionId, 'counter'); // Create initial document with counter $doc = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([ @@ -507,7 +510,7 @@ class ACIDTest extends Scope 'required' => true, ]); - sleep(2); + $this->waitForAttribute($databaseId, $collectionId, 'data'); // Create and commit transaction with multiple operations $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', array_merge([ diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index bcb87e92d5..3369caaa4b 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -4,6 +4,7 @@ namespace Tests\E2E\Services\Databases\TablesDB; use Appwrite\Extend\Exception; use Tests\E2E\Client; +use Tests\E2E\Traits\SchemaPoll; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -16,6 +17,20 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator; trait DatabasesBase { + use SchemaPoll; + + /** + * Override API config for TablesDB endpoints + */ + protected function getSchemaApiConfig(): array + { + return [ + 'basePath' => '/tablesdb', + 'collectionPath' => 'tables', + 'attributePath' => 'columns', + 'indexPath' => 'indexes', + ]; + } public function testCreateDatabase(): array { /** diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php index dc7c3a47b6..74f09f5635 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php @@ -64,10 +64,10 @@ class DatabasesCustomClientTest extends Scope 'required' => true, ]); - sleep(1); - $this->assertEquals(202, $response['headers']['status-code']); + $this->waitForAttribute($databaseId, $moviesId, 'title'); + // Document aliases write to update, delete $row1 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $moviesId . '/rows', array_merge([ 'content-type' => 'application/json', @@ -157,8 +157,7 @@ class DatabasesCustomClientTest extends Scope ]); $this->assertEquals(202, $response['headers']['status-code']); - // Wait for database worker to finish creating attributes - sleep(2); + $this->waitForAttribute($databaseId, 'permissionCheck', 'name'); // Creating row by server, give read permission to our user + some other user $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/permissionCheck/rows', array_merge([ @@ -212,8 +211,7 @@ class DatabasesCustomClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); - // Wait for database worker to finish deleting table - sleep(2); + usleep(500 * 1000); // Make sure table has been deleted $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/permissionCheck', array_merge([ @@ -275,8 +273,6 @@ class DatabasesCustomClientTest extends Scope ] ]); - \sleep(2); - // Creating two way relationship between table 1 and table 2 from table 1 $relation = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $table1['body']['$id'] . '/columns/relationship', array_merge([ 'content-type' => 'application/json', @@ -291,7 +287,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => $table1['body']['$id'] ]); - \sleep(3); + $this->waitForAttribute($databaseId, $table1['body']['$id'], $table2['body']['$id']); // Update relation from table 2 to on delete restrict $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $table2['body']['$id'] . '/columns/' . $table1['body']['$id'] . '/relationship', array_merge([ @@ -362,8 +358,6 @@ class DatabasesCustomClientTest extends Scope ] ]); - \sleep(2); - $relation = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $table1['body']['$id'] . '/columns/relationship', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -377,7 +371,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'same_key' ]); - \sleep(2); + $this->waitForAttribute($databaseId, $table1['body']['$id'], 'attr1'); $this->assertEquals(202, $relation['headers']['status-code']); $this->assertEquals('same_key', $relation['body']['twoWayKey']); @@ -395,8 +389,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'same_key' ]); - \sleep(2); - + // This request fails with 409 - no need to wait $this->assertEquals(409, $relation['body']['code']); $this->assertEquals('Column with the requested key \'same_key\' already exists. Column keys must be unique, try again with a different key.', $relation['body']['message']); @@ -413,7 +406,7 @@ class DatabasesCustomClientTest extends Scope 'key' => 'attr3', ]); - \sleep(2); + $this->waitForAttribute($databaseId, $table1['body']['$id'], 'attr3'); $this->assertEquals(202, $relation['headers']['status-code']); $this->assertArrayHasKey('twoWayKey', $relation['body']); @@ -431,8 +424,7 @@ class DatabasesCustomClientTest extends Scope 'key' => 'attr4', ]); - \sleep(2); - + // This request fails with 409 - no need to wait $this->assertEquals('Column with the requested key \'attr4\' already exists. Column keys must be unique, try again with a different key.', $relation['body']['message']); $this->assertEquals(409, $relation['body']['code']); @@ -450,7 +442,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'playlist', ]); - \sleep(2); + $this->waitForAttribute($databaseId, $table1['body']['$id'], 'songs'); $this->assertEquals(202, $relation['headers']['status-code']); $this->assertArrayHasKey('twoWayKey', $relation['body']); @@ -469,8 +461,7 @@ class DatabasesCustomClientTest extends Scope 'twoWayKey' => 'playlist2', ]); - \sleep(2); - + // This request fails with 409 - no need to wait $this->assertEquals(409, $relation['body']['code']); $this->assertEquals('Creating more than one "manyToMany" relationship on the same table is currently not permitted.', $relation['body']['message']); } @@ -674,7 +665,12 @@ class DatabasesCustomClientTest extends Scope 'default' => null, ]); - \sleep(2); + $this->waitForAllAttributes($databaseId, $table1['body']['$id']); + $this->waitForAllAttributes($databaseId, $table2['body']['$id']); + $this->waitForAllAttributes($databaseId, $table3['body']['$id']); + $this->waitForAllAttributes($databaseId, $table4['body']['$id']); + $this->waitForAllAttributes($databaseId, $table5['body']['$id']); + // Creating parent row with a child reference to test the permissions $parentDocument = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $table1['body']['$id'] . '/rows', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php index b0b80ce3b6..404ac14219 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php @@ -727,8 +727,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('lastName', $lastName['body']['key']); $this->assertEquals('string', $lastName['body']['type']); - // Wait for database worker to finish creating columns - sleep(2); + $this->waitForAllAttributes($databaseId, $actors['body']['$id']); // Creating row to ensure cache is purged on schema change $row = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $actors['body']['$id'] . '/rows', array_merge([ @@ -828,8 +827,7 @@ class DatabasesCustomServerTest extends Scope 'required' => true, ]); - // Wait for database worker to finish creating columns - sleep(2); + $this->waitForAttribute($databaseId, $actors['body']['$id'], 'unneeded'); // Creating row to ensure cache is purged on schema change $row = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $actors['body']['$id'] . '/rows', array_merge([ @@ -862,8 +860,7 @@ class DatabasesCustomServerTest extends Scope ], ]); - // Wait for database worker to finish creating index - sleep(2); + $this->waitForIndex($databaseId, $actors['body']['$id'], 'key_lastName'); $table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $actors['body']['$id'], array_merge([ 'content-type' => 'application/json', @@ -891,7 +888,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $column['headers']['status-code']); - sleep(2); + $this->waitForAttributeDeletion($databaseId, $actors['body']['$id'], $unneededId); // Check row to ensure cache is purged on schema change $row = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $actors['body']['$id'] . '/rows/' . $row['body']['$id'], array_merge([ @@ -935,8 +932,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $index['headers']['status-code']); - // Wait for database worker to finish deleting index - sleep(2); + $this->waitForIndexDeletion($databaseId, $data['tableId'], $data['key']); $table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $data['tableId'], array_merge([ 'content-type' => 'application/json', @@ -980,7 +976,8 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('column1', $column1['body']['key']); $this->assertEquals('column2', $column2['body']['key']); - sleep(2); + $this->waitForAttribute($databaseId, $data['tableId'], 'column1'); + $this->waitForAttribute($databaseId, $data['tableId'], 'column2'); $index1 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['tableId'] . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1008,7 +1005,8 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('index1', $index1['body']['key']); $this->assertEquals('index2', $index2['body']['key']); - sleep(2); + $this->waitForIndex($databaseId, $data['tableId'], 'index1'); + $this->waitForIndex($databaseId, $data['tableId'], 'index2'); // Expected behavior: deleting column2 will cause index2 to be dropped, and index1 rebuilt with a single key $deleted = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $data['tableId'] . '/columns/' . $column2['body']['key'], array_merge([ @@ -1019,8 +1017,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $deleted['headers']['status-code']); - // wait for database worker to complete - sleep(2); + $this->waitForAttributeDeletion($databaseId, $data['tableId'], 'column2'); $table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $data['tableId'], array_merge([ 'content-type' => 'application/json', @@ -1108,7 +1105,8 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('column1', $column1['body']['key']); $this->assertEquals('column2', $column2['body']['key']); - sleep(2); + $this->waitForAttribute($databaseId, $tableId, 'column1'); + $this->waitForAttribute($databaseId, $tableId, 'column2'); $index1 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/indexes', array_merge([ 'content-type' => 'application/json', @@ -1136,7 +1134,8 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals('index1', $index1['body']['key']); $this->assertEquals('index2', $index2['body']['key']); - sleep(2); + $this->waitForIndex($databaseId, $tableId, 'index1'); + $this->waitForIndex($databaseId, $tableId, 'index2'); // Expected behavior: deleting column1 would cause index1 to be a duplicate of index2 and automatically removed $deleted = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/' . $column1['body']['key'], array_merge([ @@ -1147,8 +1146,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(204, $deleted['headers']['status-code']); - // wait for database worker to complete - sleep(2); + $this->waitForAttributeDeletion($databaseId, $tableId, 'column1'); $table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([ 'content-type' => 'application/json', @@ -1297,7 +1295,7 @@ class DatabasesCustomServerTest extends Scope 'key' => 'table2' ]); - sleep(2); + $this->waitForAttribute($databaseId, $table1, 'table2'); $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $table2, array_merge([ 'content-type' => 'application/json', @@ -1305,7 +1303,18 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'], ], $this->getHeaders())); - sleep(2); + $start = microtime(true) * 1000; + while ((microtime(true) * 1000) - $start < 30000) { + $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $table2, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); + if ($response['headers']['status-code'] === 404) { + break; + } + usleep(100 * 1000); + } $columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $table1 . '/columns', array_merge([ 'content-type' => 'application/json', @@ -1366,7 +1375,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $column['headers']['status-code']); } - sleep(5); + $this->waitForAllAttributes($databaseId, $tableId); $tooWide = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ 'content-type' => 'application/json', @@ -1433,7 +1442,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $column['headers']['status-code']); } - sleep(10); + $this->waitForAllAttributes($databaseId, $tableId); $table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([ 'content-type' => 'application/json', @@ -1470,7 +1479,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals("key_column{$i}", $index['body']['key']); } - sleep(5); + $this->waitForAllIndexes($databaseId, $tableId); $table = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([ 'content-type' => 'application/json', @@ -1655,7 +1664,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $column['headers']['status-code']); - sleep(5); + $this->waitForAllAttributes($databaseId, $tableId); return [ 'databaseId' => $databaseId, @@ -3674,7 +3683,7 @@ class DatabasesCustomServerTest extends Scope ] ]); - \sleep(2); + // Tables are created synchronously, no wait needed } public function cleanupRelationshipTable(): void @@ -3685,7 +3694,20 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]); - \sleep(2); + // Poll until database is deleted (returns 404) + $start = microtime(true) * 1000; + $timeoutMs = 30000; + while ((microtime(true) * 1000) - $start < $timeoutMs) { + $response = $this->client->call(Client::METHOD_GET, '/tablesdb/database1', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]); + if ($response['headers']['status-code'] === 404) { + return; + } + usleep(100 * 1000); + } } public function testColumnRenameRelationshipOneToMany() @@ -3709,7 +3731,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $table1Id, 'level2'); $table1Columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $table1Id, [ 'content-type' => 'application/json', @@ -3823,7 +3845,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $table1Id, 'level2'); $table1Columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $table1Id, [ 'content-type' => 'application/json', @@ -3937,7 +3959,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $table1Id, 'level2'); $table1Columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $table1Id, [ 'content-type' => 'application/json', @@ -4055,7 +4077,7 @@ class DatabasesCustomServerTest extends Scope 'twoWayKey' => 'level1' ]); - \sleep(3); + $this->waitForAttribute($databaseId, $table1Id, 'level2'); $table1Columns = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $table1Id, [ 'content-type' => 'application/json', @@ -4203,7 +4225,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberColumn['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'number'); $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$data['$id']}/rows", array_merge([ 'content-type' => 'application/json', @@ -4375,7 +4397,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$data['$id']}/rows", array_merge([ 'content-type' => 'application/json', @@ -4441,8 +4463,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberColumn['headers']['status-code']); - // Wait for database worker to create columns - sleep(2); + $this->waitForAttribute($databaseId, $data['$id'], 'number'); // Create rows $createBulkRows = function ($amount = 10) use ($data) { @@ -4473,7 +4494,7 @@ class DatabasesCustomServerTest extends Scope * This test specifically failed on 1.6.x response format, * could be due to the slow or overworked machine, but being safe here! */ - sleep(5); + usleep(500 * 1000); // TEST: Update all rows $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([ @@ -4499,7 +4520,7 @@ class DatabasesCustomServerTest extends Scope * This test specifically failed on 1.6.x response format, * could be due to the slow or overworked machine, but being safe here! */ - sleep(5); + usleep(500 * 1000); $rows = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([ 'content-type' => 'application/json', @@ -4663,7 +4684,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([ 'content-type' => 'application/json', @@ -4731,8 +4752,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberColumn['headers']['status-code']); - // Wait for database worker to create columns - sleep(2); + $this->waitForAttribute($databaseId, $data['$id'], 'number'); // Create rows $createBulkRows = function ($amount = 10) use ($data) { @@ -4856,7 +4876,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($databaseId, $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_PUT, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([ 'content-type' => 'application/json', @@ -4923,8 +4943,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $numberColumn['headers']['status-code']); - // wait for database worker to create columns - sleep(2); + $this->waitForAttribute($databaseId, $data['$id'], 'number'); // Create rows $createBulkRows = function ($amount = 11) use ($data) { @@ -5197,7 +5216,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->waitForAttribute($data['databaseId'], $data['$id'], 'level2'); $response = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([ 'content-type' => 'application/json', @@ -5257,7 +5276,7 @@ class DatabasesCustomServerTest extends Scope 'format' => 'datetime', ]); - sleep(1); + $this->waitForAllAttributes($databaseId, $tableId); $date = '2000-01-01T10:00:00.000+00:00'; @@ -5378,7 +5397,7 @@ class DatabasesCustomServerTest extends Scope 'required' => false, ]); - sleep(1); + $this->waitForAttribute($databaseId, $tableId, 'string'); $createDate = '2000-01-01T10:00:00.000+00:00'; $updateDate = '2000-02-01T15:30:00.000+00:00'; @@ -5665,7 +5684,7 @@ class DatabasesCustomServerTest extends Scope 'required' => false, ]); - sleep(1); + $this->waitForAttribute($databaseId, $tableId, 'string'); $createDate = '2000-01-01T10:00:00.000+00:00'; $updateDate = '2000-02-01T15:30:00.000+00:00'; @@ -5910,7 +5929,7 @@ class DatabasesCustomServerTest extends Scope 'required' => false, ]); - sleep(1); + $this->waitForAttribute($databaseId, $tableId, 'string'); $createDate = '2000-01-01T10:00:00.000+00:00'; $updateDate = '2000-02-01T15:30:00.000+00:00'; @@ -6222,8 +6241,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $polygonColumn['headers']['status-code']); - // Wait for columns to be created - sleep(2); + $this->waitForAllAttributes($databaseId, $tableId); // Test 1: Bulk create with spatial data $spatialRows = []; @@ -6620,8 +6638,7 @@ class DatabasesCustomServerTest extends Scope // Handle both 201 (created) and 202 (accepted) status codes $this->assertEquals(202, $lineColumn['headers']['status-code']); - // Wait for columns to be created - sleep(2); + $this->waitForAllAttributes($databaseId, $tableId); // Test bulk create with line string data $lineStringRows = []; diff --git a/tests/e2e/Traits/SchemaPoll.php b/tests/e2e/Traits/SchemaPoll.php new file mode 100644 index 0000000000..7b3601f083 --- /dev/null +++ b/tests/e2e/Traits/SchemaPoll.php @@ -0,0 +1,216 @@ + '/databases', + 'collectionPath' => 'collections', + 'attributePath' => 'attributes', + 'indexPath' => 'indexes', + ]; + } + + protected function waitForAttribute( + string $databaseId, + string $collectionId, + string $attributeKey, + int $timeoutMs = 30000, + int $intervalMs = 100 + ): void { + $config = $this->getSchemaApiConfig(); + + $this->assertEventually(function () use ($config, $databaseId, $collectionId, $attributeKey) { + $response = $this->client->call( + Client::METHOD_GET, + $config['basePath'] . '/' . $databaseId . '/' . $config['collectionPath'] . '/' . $collectionId . '/' . $config['attributePath'] . '/' . $attributeKey, + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()) + ); + + $status = $response['body']['status'] ?? 'unknown'; + + if ($status === 'failed') { + throw new \Exception("Attribute creation failed: " . ($response['body']['error'] ?? 'unknown error')); + } + + $this->assertEquals('available', $status); + return true; + }, $timeoutMs, $intervalMs); + } + + protected function waitForIndex( + string $databaseId, + string $collectionId, + string $indexKey, + int $timeoutMs = 60000, + int $intervalMs = 100 + ): void { + $config = $this->getSchemaApiConfig(); + + $this->assertEventually(function () use ($config, $databaseId, $collectionId, $indexKey) { + $response = $this->client->call( + Client::METHOD_GET, + $config['basePath'] . '/' . $databaseId . '/' . $config['collectionPath'] . '/' . $collectionId . '/' . $config['indexPath'] . '/' . $indexKey, + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()) + ); + + $status = $response['body']['status'] ?? 'unknown'; + + if ($status === 'failed') { + throw new \Exception("Index creation failed: " . ($response['body']['error'] ?? 'unknown error')); + } + + $this->assertEquals('available', $status); + return true; + }, $timeoutMs, $intervalMs); + } + + protected function waitForAllAttributes( + string $databaseId, + string $collectionId, + int $timeoutMs = 30000, + int $intervalMs = 100 + ): void { + $config = $this->getSchemaApiConfig(); + + $this->assertEventually(function () use ($config, $databaseId, $collectionId) { + $response = $this->client->call( + Client::METHOD_GET, + $config['basePath'] . '/' . $databaseId . '/' . $config['collectionPath'] . '/' . $collectionId . '/' . $config['attributePath'], + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()) + ); + + $attributes = $response['body']['attributes'] ?? $response['body']['columns'] ?? []; + + if (empty($attributes)) { + return true; + } + + foreach ($attributes as $attr) { + if ($attr['status'] === 'failed') { + throw new \Exception("Attribute '{$attr['key']}' creation failed: " . ($attr['error'] ?? 'unknown error')); + } + $this->assertEquals('available', $attr['status']); + } + + return true; + }, $timeoutMs, $intervalMs); + } + + protected function waitForAllIndexes( + string $databaseId, + string $collectionId, + int $timeoutMs = 60000, + int $intervalMs = 100 + ): void { + $config = $this->getSchemaApiConfig(); + + $this->assertEventually(function () use ($config, $databaseId, $collectionId) { + $response = $this->client->call( + Client::METHOD_GET, + $config['basePath'] . '/' . $databaseId . '/' . $config['collectionPath'] . '/' . $collectionId . '/' . $config['indexPath'], + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()) + ); + + $indexes = $response['body']['indexes'] ?? []; + + if (empty($indexes)) { + return true; + } + + foreach ($indexes as $index) { + if ($index['status'] === 'failed') { + throw new \Exception("Index '{$index['key']}' creation failed: " . ($index['error'] ?? 'unknown error')); + } + $this->assertEquals('available', $index['status']); + } + + return true; + }, $timeoutMs, $intervalMs); + } + + protected function waitForAttributeDeletion( + string $databaseId, + string $collectionId, + string $attributeKey, + int $timeoutMs = 30000, + int $intervalMs = 100 + ): void { + $config = $this->getSchemaApiConfig(); + + $this->assertEventually(function () use ($config, $databaseId, $collectionId, $attributeKey) { + $response = $this->client->call( + Client::METHOD_GET, + $config['basePath'] . '/' . $databaseId . '/' . $config['collectionPath'] . '/' . $collectionId . '/' . $config['attributePath'] . '/' . $attributeKey, + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()) + ); + + if ($response['headers']['status-code'] === 404) { + return true; + } + + $status = $response['body']['status'] ?? 'unknown'; + + if ($status === 'available') { + throw new \Exception("Attribute '{$attributeKey}' deletion failed - still available"); + } + + $this->assertNotEquals('deleting', $status); + return true; + }, $timeoutMs, $intervalMs); + } + + protected function waitForIndexDeletion( + string $databaseId, + string $collectionId, + string $indexKey, + int $timeoutMs = 60000, + int $intervalMs = 100 + ): void { + $config = $this->getSchemaApiConfig(); + + $this->assertEventually(function () use ($config, $databaseId, $collectionId, $indexKey) { + $response = $this->client->call( + Client::METHOD_GET, + $config['basePath'] . '/' . $databaseId . '/' . $config['collectionPath'] . '/' . $collectionId . '/' . $config['indexPath'] . '/' . $indexKey, + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()) + ); + + if ($response['headers']['status-code'] === 404) { + return true; + } + + $status = $response['body']['status'] ?? 'unknown'; + + if ($status === 'available') { + throw new \Exception("Index '{$indexKey}' deletion failed - still available"); + } + + $this->assertNotEquals('deleting', $status); + return true; + }, $timeoutMs, $intervalMs); + } +}