Remove DB sleeps

This commit is contained in:
Jake Barnby
2026-01-09 01:22:06 +13:00
parent 27aee1fb88
commit 1683d34d13
8 changed files with 410 additions and 148 deletions
@@ -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
{
/**
@@ -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',
@@ -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 = [];
@@ -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([
@@ -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
{
/**
@@ -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',
@@ -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 = [];
+216
View File
@@ -0,0 +1,216 @@
<?php
namespace Tests\E2E\Traits;
use Tests\E2E\Client;
trait SchemaPoll
{
protected function getSchemaApiConfig(): array
{
return [
'basePath' => '/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);
}
}