Skip to content

Remove DB sleeps#11108

Open
abnegate wants to merge 1 commit into1.8.xfrom
chore-remove-sleeps
Open

Remove DB sleeps#11108
abnegate wants to merge 1 commit into1.8.xfrom
chore-remove-sleeps

Conversation

@abnegate
Copy link
Member

@abnegate abnegate commented Jan 8, 2026

What does this PR do?

(Provide a description of what this PR does and why it's needed.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)

Related PRs and Issues

  • (Related PR or issue)

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new SchemaPoll trait that provides polling utilities for waiting on schema resource availability (attributes, indexes) in end-to-end tests. The trait is integrated into multiple database test classes across Legacy and TablesDB test suites. Throughout these test files, hard-coded sleep calls are systematically replaced with explicit polling helpers such as waitForAttribute, waitForAllAttributes, waitForIndex, waitForIndexDeletion, and waitForAttributeDeletion. The changes alter test control flow from relying on fixed time-based delays to dynamic resource readiness checks with configurable timeouts and polling intervals.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes span seven files with significant structural shifts in test synchronization logic. The new SchemaPoll trait introduces non-trivial polling methods that require validation of timeout handling, interval logic, and error handling. While individual test file changes follow a repetitive pattern of replacing sleep calls with polling utilities, each test context requires separate verification to ensure the polling strategy is appropriate. The total scope, distributed nature across multiple files, and the need to understand both the trait's implementation and its application across diverse test scenarios increases review complexity.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is mostly a template with unfilled sections, but the commit message and raw_summary confirm this relates to replacing sleeps with schema polling helpers. Fill in the 'What does this PR do?' and 'Test Plan' sections to clarify the motivation, approach, and verification strategy for reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Remove DB sleeps' directly and clearly summarizes the main objective of the changeset—replacing database sleep calls with polling utilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@abnegate abnegate changed the title chore: bump appwrite version to 1.8.1 Remove DB sleeps Jan 8, 2026
@github-actions
Copy link

github-actions bot commented Jan 8, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libpng 1.6.51-r0 CVE-2025-66293 HIGH
libpng-dev 1.6.51-r0 CVE-2025-66293 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)

4523-4523: Typo CLient::METHOD_PUT will cause a fatal error

CLient (capital “L”) is not the imported class; this line will fail with an undefined class error instead of executing the request. It should use Client::METHOD_PUT like the rest of the file.

Proposed fix
-        $this->client->call(CLient::METHOD_PUT, '/tablesdb/' . $databaseId . '/tables/' . $tableId, [
+        $this->client->call(Client::METHOD_PUT, '/tablesdb/' . $databaseId . '/tables/' . $tableId, [
tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php (1)

213-235: Missing wait for the required_field attribute.

The test creates both required_field (lines 213-221) and age (lines 224-233) attributes, but only waits for age to be ready (line 235). Since the transaction operations at lines 258-260 use both attributes, required_field must also be available before proceeding. The previous fixed sleep likely gave both attributes time to become ready, but the explicit polling for only age may not.

⏱️ Proposed fix to wait for both attributes
         ]);
 
+        $this->waitForAttribute($databaseId, $collectionId, 'required_field');
         $this->waitForAttribute($databaseId, $collectionId, 'age');
🤖 Fix all issues with AI agents
In @tests/e2e/Traits/SchemaPoll.php:
- Around line 149-181: In waitForAttributeDeletion, add explicit handling for a
'failed' status: after reading $status from the response in the closure inside
assertEventually, if $status === 'failed' throw an Exception (or fail the
assertion) indicating attribute deletion failed with status 'failed' for the
given $attributeKey; keep the existing checks for 'available' and 'deleting'
unchanged so the poll only succeeds when the attribute is 404 or otherwise
confirmed deleted.
- Around line 183-215: In waitForIndexDeletion, explicitly handle the 'failed'
index status like waitForAttributeDeletion does: when reading $status from the
response body in waitForIndexDeletion, add a check for $status === 'failed' and
throw a clear Exception (including the indexKey and status) so the poll does not
treat a failed deletion as success; keep the existing check for 'available' and
the assertNotEquals('deleting', $status) but ensure 'failed' causes an immediate
failure during polling.
🧹 Nitpick comments (8)
tests/e2e/Services/Databases/Legacy/DatabasesBase.php (1)

357-359: Consider replacing fixed sleeps with SchemaPoll polling helpers

Now that SchemaPoll is mixed into this trait, these hard-coded sleep() calls (and similar ones elsewhere in this file) could be gradually replaced with the trait’s waitFor* helpers so tests wait on actual attribute/index readiness instead of time-based guesses. This would reduce flakiness and speed up runs when the worker is faster than the fixed delay.

tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)

370-372: Consider replacing remaining sleep() calls with SchemaPoll wait helpers

Now that SchemaPoll is wired in, the fixed sleep() after column/index creation (and similar sleeps elsewhere in this trait) can be swapped for waitForAttribute / waitForAllAttributes / waitForIndex-style helpers to reduce flakiness and keep this suite consistent with the rest of the PR.

tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php (1)

214-214: Consider replacing fixed delay with polling.

This fixed usleep(500 * 1000) is inconsistent with the PR's objective to remove database sleeps. Since table deletion is asynchronous, consider using a polling approach similar to the attribute waits, or verify that the subsequent 404 check (lines 217-222) adequately handles the async nature of deletion without needing this delay.

🔄 Suggested polling approach

If table deletion timing is critical, consider adding a helper method similar to waitForAttribute that polls for table deletion:

// Poll until table returns 404
$this->assertEventually(function () use ($databaseId) {
    $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/permissionCheck', array_merge([
        'content-type' => 'application/json',
        'x-appwrite-project' => $this->getProject()['$id'],
        'x-appwrite-key' => $this->getProject()['apiKey']
    ]));
    $this->assertEquals(404, $response['headers']['status-code']);
    return true;
}, 5000, 100);

Alternatively, if the immediate 404 check works reliably without the sleep, the usleep could potentially be removed entirely.

tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php (2)

1306-1317: Replace hand-rolled polling loops with assertEventually for consistency

Both deletion checks use manual microtime/usleep loops to wait for a 404, duplicating what assertEventually already does in SchemaPoll. Refactoring to assertEventually would reduce boilerplate and give clearer failure output on timeouts.

Example refactor using assertEventually
-        $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);
-        }
+        $this->assertEventually(function () use ($databaseId, $table2) {
+            $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'],
+            ]));
+
+            $this->assertEquals(404, $response['headers']['status-code']);
+            return true;
+        }, 30000, 100);

A similar pattern can be applied in cleanupRelationshipTable() for /tablesdb/database1.

Also applies to: 3697-3710


4497-4497: Consider replacing remaining fixed usleep calls with polling

The two usleep(500 * 1000); waits around the bulk-update cache invalidation are small but still time-based heuristics. If these tests are still flaky on slow environments, you might want to replace them with an assertEventually check on whatever invariant you expect after the cache purge (e.g., row count or field values) instead of sleeping for a fixed duration.

Also applies to: 4523-4523

tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php (2)

3745-3766: Make database deletion polling fail explicitly on timeout

cleanupRelationshipCollection()’s manual polling loop exits quietly after 30s even if /databases/database1 never returns 404, which can leave residual state and make later tests fail in harder-to-diagnose ways. Adding an explicit failure after the loop would make such issues surface clearly.

💡 Minimal change to fail on timeout
         $this->client->call(Client::METHOD_DELETE, '/databases/database1', [
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
             'x-appwrite-key' => $this->getProject()['apiKey']
         ]);
 
-        // Poll until database is deleted (returns 404)
+        // 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);
         }
+
+        $this->fail('Timed out waiting for database1 deletion');

4553-4553: Consider replacing fixed 500 ms cache sleeps with assertion-based polling

The two usleep(500 * 1000) calls still rely on hard-coded timing for cache purge, which may be tight on slower CI or busy DB nodes and can reintroduce flakiness. Since you already have assertEventually available, you could instead poll for the expected document state (e.g., updated number values) and drop these fixed sleeps.

Also applies to: 4579-4579

tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php (1)

214-225: Consider extracting collection deletion polling to SchemaPoll trait.

This manual polling loop is functional but inconsistent with the trait-based approach used for attributes and indexes. Extracting a waitForCollectionDeletion method to the SchemaPoll trait would improve code reusability and consistency across the test suite.

♻️ Example trait method to add to SchemaPoll.php
protected function waitForCollectionDeletion(
    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,
            array_merge([
                'content-type' => 'application/json',
                'x-appwrite-project' => $this->getProject()['$id'],
                'x-appwrite-key' => $this->getProject()['apiKey']
            ])
        );
        
        $this->assertEquals(404, $response['headers']['status-code']);
        return true;
    }, $timeoutMs, $intervalMs);
}

Then replace the manual loop with:

-$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);
-}
+$this->waitForCollectionDeletion($databaseId, 'permissionCheck');
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7d39b5 and 1683d34.

📒 Files selected for processing (8)
  • tests/e2e/Services/Databases/Legacy/DatabasesBase.php
  • tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php
  • tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php
  • tests/e2e/Traits/SchemaPoll.php
🧰 Additional context used
🧬 Code graph analysis (6)
tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php (1)
tests/e2e/Traits/SchemaPoll.php (2)
  • waitForAttribute (19-47)
  • waitForAllAttributes (79-112)
tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)
tests/e2e/Traits/SchemaPoll.php (1)
  • getSchemaApiConfig (9-17)
tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php (1)
tests/e2e/Traits/SchemaPoll.php (6)
  • waitForAllAttributes (79-112)
  • waitForAttribute (19-47)
  • waitForIndex (49-77)
  • waitForAttributeDeletion (149-181)
  • waitForIndexDeletion (183-215)
  • waitForAllIndexes (114-147)
tests/e2e/Traits/SchemaPoll.php (3)
tests/e2e/Client.php (1)
  • Client (8-342)
tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)
  • getSchemaApiConfig (25-33)
src/Appwrite/Extend/Exception.php (1)
  • Exception (7-472)
tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php (1)
tests/e2e/Traits/SchemaPoll.php (2)
  • waitForAttribute (19-47)
  • waitForIndex (49-77)
tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php (1)
tests/e2e/Traits/SchemaPoll.php (6)
  • waitForAttribute (19-47)
  • waitForAllAttributes (79-112)
  • waitForIndex (49-77)
  • waitForAttributeDeletion (149-181)
  • waitForIndexDeletion (183-215)
  • waitForAllIndexes (114-147)
🔇 Additional comments (19)
tests/e2e/Services/Databases/Legacy/DatabasesBase.php (1)

5-21: SchemaPoll trait integration looks correct

Importing Tests\E2E\Traits\SchemaPoll and adding use SchemaPoll; in DatabasesBase cleanly exposes the new polling helpers to all legacy database tests that use this trait, without altering existing behavior. No issues from this wiring alone.

tests/e2e/Services/Databases/TablesDB/DatabasesBase.php (1)

7-7: SchemaPoll integration and TablesDB API config override look correct

The new SchemaPoll trait usage plus getSchemaApiConfig() override cleanly point the polling helpers at /tablesdb/{databaseId}/tables/{tableId}/columns|indexes, matching the existing endpoints in this trait. No functional issues spotted here.

Also applies to: 20-33

tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php (5)

67-69: LGTM! Proper synchronization after attribute creation.

The change from sleep to waitForAttribute ensures the test waits dynamically for attribute readiness rather than relying on fixed delays. This improves test reliability and reduces flakiness.


158-160: LGTM! Consistent polling pattern.

Correctly waits for the 'name' attribute to be available before proceeding with document operations.


290-290: LGTM! Proper relationship attribute synchronization.

Correctly waits for the relationship attribute to be available before proceeding with relationship operations.


374-374: LGTM! Appropriate conditional synchronization.

The implementation correctly waits for relationship attributes after successful creation (lines 374, 409, 445) while skipping waits for failed requests that return 409 conflicts (lines 392, 427, 464). The added comments clearly document why no synchronization is needed for failed requests.

Also applies to: 392-394, 409-409, 427-429, 445-445, 464-466


668-672: LGTM! Efficient bulk synchronization.

Using waitForAllAttributes for each table is the correct approach after creating multiple attributes and relationships. This ensures all schema changes are complete before proceeding with document operations, improving test reliability while being more efficient than waiting for each attribute individually.

tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php (2)

730-730: SchemaPoll helper usage looks correct and improves test determinism

All the new waitForAttribute* / waitForAll* / waitForIndex* calls are wired with the expected (databaseId, tableId, key) ordering and placed before dependent row/assert logic, which should remove the old timing races around async schema changes. No issues from a correctness perspective.

Also applies to: 830-830, 863-863, 891-891, 935-935, 979-980, 1008-1009, 1020-1020, 1084-1085, 1108-1109, 1137-1138, 1149-1149, 1298-1298, 1378-1378, 1445-1445, 1667-1667, 3734-3734, 3848-3848, 3962-3962, 4080-4080, 4228-4228, 4466-4466, 4687-4687, 4755-4755, 4946-4946, 5219-5219, 5279-5279, 5401-5401, 5687-5687, 5932-5932, 6244-6244, 6641-6641


3686-3686: Comment on synchronous table creation aligns with new waiting strategy

The note that tables are created synchronously explains why no waitFor* helper is used here, while schema elements and relationships now rely on polling. This is consistent as long as table creation semantics stay synchronous.

tests/e2e/Services/Databases/Legacy/Transactions/ACIDTest.php (4)

9-9: LGTM!

The import and application of the SchemaPoll trait enables polling utilities for schema readiness checks, replacing fixed sleeps with dynamic resource polling.

Also applies to: 19-19


78-79: LGTM!

Properly waits for both the email attribute and unique_email index to be available before proceeding with document operations. This ensures the schema is fully ready and eliminates race conditions.


356-356: LGTM!

Correctly waits for the counter attribute to be available before creating the initial document and proceeding with transaction operations.


513-513: LGTM!

Correctly waits for the data attribute to be available before proceeding with transaction operations.

tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php (1)

747-1724: SchemaPoll-based waits correctly gate async schema and relationship operations

The new waitForAttribute* / waitForIndex* / waitForAll* usages line up with the attributes and indexes you create/delete, and they consistently wrap the subsequent assertions that depend on those resources. This should substantially reduce timing-related flakiness compared to the previous fixed sleeps while preserving the existing test intent.

Also applies to: 3790-4522, 4760-5352, 5473-5991, 6303-6693

tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php (3)

69-70: LGTM - Clean replacement of sleep with explicit polling.

The attribute polling ensures the 'title' attribute is available before proceeding, improving test reliability.


683-688: LGTM - Proper synchronization for complex relationship setup.

The sequential polling ensures all attributes (including relationships) across five collections are available before proceeding with nested document creation. This is a good replacement for fixed sleeps and properly handles the asynchronous nature of schema changes.


287-287: LGTM - Clear documentation of synchronous operations.

The comments explaining when polling is not needed (synchronous collection creation and immediate validation failures) improve test readability and help future maintainers understand the control flow.

Also applies to: 374-374, 407-409, 442-443, 479-481

tests/e2e/Traits/SchemaPoll.php (2)

9-17: LGTM - Clean abstraction for API path configuration.

The overridable getSchemaApiConfig() method allows different test suites (Legacy vs TablesDB) to use the same polling utilities with different API endpoints, promoting code reuse.


19-147: LGTM - Robust polling implementation for resource creation and availability.

The polling methods for attributes and indexes properly handle:

  • Explicit 'failed' status checks with descriptive error messages
  • Configurable timeouts and intervals with sensible defaults (30s for attributes, 60s for indexes)
  • Support for both Legacy and TablesDB APIs through the configurable path mechanism
  • Batch operations (waitForAllAttributes, waitForAllIndexes) with proper iteration and error reporting

This is a solid foundation for eliminating flaky test timing issues.

Comment on lines +149 to +181
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add explicit handling for 'failed' status in deletion polling.

The current logic doesn't explicitly check for a 'failed' status during deletion. If a deletion operation fails and the status becomes 'failed' (rather than remaining 'deleting' or returning to 'available'), the method will return true and consider it successful.

🐛 Proposed fix to handle 'failed' status
 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");
         }

+        if ($status === 'failed') {
+            throw new \Exception("Attribute '{$attributeKey}' deletion failed: " . ($response['body']['error'] ?? 'unknown error'));
+        }
+
         $this->assertNotEquals('deleting', $status);
         return true;
     }, $timeoutMs, $intervalMs);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 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");
}
if ($status === 'failed') {
throw new \Exception("Attribute '{$attributeKey}' deletion failed: " . ($response['body']['error'] ?? 'unknown error'));
}
$this->assertNotEquals('deleting', $status);
return true;
}, $timeoutMs, $intervalMs);
}
🤖 Prompt for AI Agents
In @tests/e2e/Traits/SchemaPoll.php around lines 149 - 181, In
waitForAttributeDeletion, add explicit handling for a 'failed' status: after
reading $status from the response in the closure inside assertEventually, if
$status === 'failed' throw an Exception (or fail the assertion) indicating
attribute deletion failed with status 'failed' for the given $attributeKey; keep
the existing checks for 'available' and 'deleting' unchanged so the poll only
succeeds when the attribute is 404 or otherwise confirmed deleted.

Comment on lines +183 to +215
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add explicit handling for 'failed' status in index deletion polling.

Similar to waitForAttributeDeletion, this method doesn't explicitly check for a 'failed' status. If an index deletion fails with a 'failed' status, the method will incorrectly consider it successful.

🐛 Proposed fix to handle 'failed' status
 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");
         }

+        if ($status === 'failed') {
+            throw new \Exception("Index '{$indexKey}' deletion failed: " . ($response['body']['error'] ?? 'unknown error'));
+        }
+
         $this->assertNotEquals('deleting', $status);
         return true;
     }, $timeoutMs, $intervalMs);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
}
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");
}
if ($status === 'failed') {
throw new \Exception("Index '{$indexKey}' deletion failed: " . ($response['body']['error'] ?? 'unknown error'));
}
$this->assertNotEquals('deleting', $status);
return true;
}, $timeoutMs, $intervalMs);
}
🤖 Prompt for AI Agents
In @tests/e2e/Traits/SchemaPoll.php around lines 183 - 215, In
waitForIndexDeletion, explicitly handle the 'failed' index status like
waitForAttributeDeletion does: when reading $status from the response body in
waitForIndexDeletion, add a check for $status === 'failed' and throw a clear
Exception (including the indexKey and status) so the poll does not treat a
failed deletion as success; keep the existing check for 'available' and the
assertNotEquals('deleting', $status) but ensure 'failed' causes an immediate
failure during polling.

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

✨ Benchmark results

  • Requests per second: 2,212
  • Requests with 200 status code: 398,214
  • P99 latency: 0.079641897

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 2,212 1,240
200 398,214 223,266
P99 0.079641897 0.165478608

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant