Files
appwrite/tests/e2e/Scopes/SchemaPolling.php
Jake Barnby e1791e17c3 fix: increase attribute/index polling timeouts to 240s for shared mode CI
Shared tables mode experiences significant worker queue contention during
parallel test execution. 120s was insufficient for attribute processing
in Shared V1 mode and occasionally for PostgreSQL under load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:50:46 +13:00

238 lines
10 KiB
PHP

<?php
namespace Tests\E2E\Scopes;
use Appwrite\Tests\Async\Exceptions\Critical;
use Tests\E2E\Client;
/**
* Trait for polling schema changes (attributes, indexes) instead of using sleep().
* Uses assertEventually to wait for async operations to complete.
*/
trait SchemaPolling
{
/**
* Wait for an attribute to become available.
*
* @param string $databaseId The database ID
* @param string $containerId The collection/table ID
* @param string $attributeKey The attribute key to wait for
* @param int $timeoutMs Maximum time to wait in milliseconds
* @param int $waitMs Time between polling attempts in milliseconds
*/
protected function waitForAttribute(string $databaseId, string $containerId, string $attributeKey, int $timeoutMs = 240000, int $waitMs = 500): void
{
$this->assertEventually(function () use ($databaseId, $containerId, $attributeKey) {
$attribute = $this->client->call(
Client::METHOD_GET,
$this->getSchemaUrl($databaseId, $containerId) . '/' . $attributeKey,
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
])
);
$this->assertEquals(200, $attribute['headers']['status-code']);
$status = $attribute['body']['status'] ?? '';
if ($status === 'failed') {
throw new Critical("Attribute '{$attributeKey}' failed: " . ($attribute['body']['error'] ?? 'unknown error'));
}
$this->assertEquals('available', $status);
}, $timeoutMs, $waitMs);
}
/**
* Wait for multiple attributes to become available.
*
* @param string $databaseId The database ID
* @param string $containerId The collection/table ID
* @param array $attributeKeys Array of attribute keys to wait for
* @param int $timeoutMs Maximum time to wait in milliseconds
* @param int $waitMs Time between polling attempts in milliseconds
*/
protected function waitForAttributes(string $databaseId, string $containerId, array $attributeKeys, int $timeoutMs = 240000, int $waitMs = 500): void
{
$this->assertEventually(function () use ($databaseId, $containerId, $attributeKeys) {
$container = $this->client->call(
Client::METHOD_GET,
$this->getContainerUrl($databaseId, $containerId),
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
])
);
$this->assertEquals(200, $container['headers']['status-code']);
$this->assertArrayHasKey('body', $container);
$this->assertArrayHasKey($this->getSchemaResource(), $container['body']);
$attributes = $container['body'][$this->getSchemaResource()];
$availableKeys = [];
foreach ($attributes as $attr) {
if ($attr['status'] === 'failed') {
throw new Critical("Attribute '{$attr['key']}' failed: " . ($attr['error'] ?? 'unknown error'));
}
if ($attr['status'] === 'available') {
$availableKeys[] = $attr['key'];
}
}
foreach ($attributeKeys as $key) {
$this->assertContains($key, $availableKeys, "Attribute '$key' is not available yet");
}
}, $timeoutMs, $waitMs);
}
/**
* Wait for the collection/table to have at least a certain number of available attributes.
*
* @param string $databaseId The database ID
* @param string $containerId The collection/table ID
* @param int $count Minimum number of available attributes required
* @param int $timeoutMs Maximum time to wait in milliseconds
* @param int $waitMs Time between polling attempts in milliseconds
*/
protected function waitForAttributeCount(string $databaseId, string $containerId, int $count, int $timeoutMs = 240000, int $waitMs = 500): void
{
$this->assertEventually(function () use ($databaseId, $containerId, $count) {
$container = $this->client->call(
Client::METHOD_GET,
$this->getContainerUrl($databaseId, $containerId),
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
])
);
$this->assertEquals(200, $container['headers']['status-code']);
$this->assertArrayHasKey('body', $container);
$this->assertArrayHasKey($this->getSchemaResource(), $container['body']);
$attributes = $container['body'][$this->getSchemaResource()];
$availableCount = 0;
foreach ($attributes as $attr) {
if ($attr['status'] === 'failed') {
throw new Critical("Attribute '{$attr['key']}' failed: " . ($attr['error'] ?? 'unknown error'));
}
if ($attr['status'] === 'available') {
$availableCount++;
}
}
$this->assertGreaterThanOrEqual($count, $availableCount, "Expected at least $count available attributes, got $availableCount");
}, $timeoutMs, $waitMs);
}
/**
* Wait for an index to become available.
*
* @param string $databaseId The database ID
* @param string $containerId The collection/table ID
* @param string $indexKey The index key to wait for
* @param int $timeoutMs Maximum time to wait in milliseconds
* @param int $waitMs Time between polling attempts in milliseconds
*/
protected function waitForIndex(string $databaseId, string $containerId, string $indexKey, int $timeoutMs = 240000, int $waitMs = 500): void
{
$this->assertEventually(function () use ($databaseId, $containerId, $indexKey) {
$index = $this->client->call(
Client::METHOD_GET,
$this->getIndexUrl($databaseId, $containerId, $indexKey),
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
])
);
$this->assertEquals(200, $index['headers']['status-code']);
$this->assertArrayHasKey('body', $index);
$this->assertArrayHasKey('status', $index['body']);
$status = $index['body']['status'] ?? '';
if ($status === 'failed') {
throw new Critical("Index '{$indexKey}' failed: " . ($index['body']['error'] ?? 'unknown error'));
}
$this->assertEquals('available', $status);
}, $timeoutMs, $waitMs);
}
/**
* Wait for all indexes in a collection/table to become available.
*
* @param string $databaseId The database ID
* @param string $containerId The collection/table ID
* @param int $timeoutMs Maximum time to wait in milliseconds
* @param int $waitMs Time between polling attempts in milliseconds
*/
protected function waitForAllIndexes(string $databaseId, string $containerId, int $timeoutMs = 240000, int $waitMs = 500): void
{
$this->assertEventually(function () use ($databaseId, $containerId) {
$container = $this->client->call(
Client::METHOD_GET,
$this->getContainerUrl($databaseId, $containerId),
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
])
);
$this->assertEquals(200, $container['headers']['status-code']);
$this->assertArrayHasKey('body', $container);
$this->assertArrayHasKey('indexes', $container['body']);
foreach ($container['body']['indexes'] as $index) {
if ($index['status'] === 'failed') {
throw new Critical("Index '{$index['key']}' failed: " . ($index['error'] ?? 'unknown error'));
}
$this->assertEquals('available', $index['status'], "Index '{$index['key']}' is not available yet");
}
}, $timeoutMs, $waitMs);
}
/**
* Wait for all attributes in a collection/table to become available.
*
* @param string $databaseId The database ID
* @param string $containerId The collection/table ID
* @param int $timeoutMs Maximum time to wait in milliseconds
* @param int $waitMs Time between polling attempts in milliseconds
*/
protected function waitForAllAttributes(string $databaseId, string $containerId, int $timeoutMs = 240000, int $waitMs = 500): void
{
$this->assertEventually(function () use ($databaseId, $containerId) {
$container = $this->client->call(
Client::METHOD_GET,
$this->getContainerUrl($databaseId, $containerId),
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
])
);
// Tolerate transient 500s during heavy attribute processing
$this->assertContains($container['headers']['status-code'], [200], "Expected 200 but got {$container['headers']['status-code']} polling container {$containerId}");
$schemaResource = $this->getSchemaResource();
$this->assertNotEmpty($container['body'][$schemaResource], "No attributes found in container {$containerId}");
foreach ($container['body'][$schemaResource] as $attribute) {
if ($attribute['status'] === 'failed') {
throw new Critical("Attribute '{$attribute['key']}' failed: " . ($attribute['error'] ?? 'unknown error'));
}
$this->assertEquals('available', $attribute['status'], "Attribute '{$attribute['key']}' is not available yet");
}
}, $timeoutMs, $waitMs);
}
}