Skip to content

Parallel test files#11113

Open
abnegate wants to merge 62 commits into1.8.xfrom
feat-db-tests
Open

Parallel test files#11113
abnegate wants to merge 62 commits into1.8.xfrom
feat-db-tests

Conversation

@abnegate
Copy link
Member

@abnegate abnegate commented Jan 9, 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 9, 2026

📝 Walkthrough

Walkthrough

Adds multiple test helper traits and URL builders (ApiLegacy, ApiTablesDB, DatabasesUrlHelpers, SchemaPolling, ACIDBase, TransactionPermissionsBase), a shared DatabaseFixture, retry and test-finished PHPUnit subscribers, and a TestHook extension. Introduces many scaffold end-to-end test classes for legacy and TablesDB flows, refactors transaction tests to trait-based bases, and adds extensive permission/permission‑team test suites. Removes numerous legacy/TablesDB test files and a permissions helper trait. Migrates PHPUnit docblock annotations and data providers to PHP 8 attributes (making providers static), increases test client timeout, updates composer dev dependencies (phpunit → 12.*, adds paratest), and updates CI workflow to use paratest.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Parallel test files' is vague and does not clearly summarize the main changes. It lacks specificity about the actual modifications made to the codebase. Consider using a more descriptive title that explains the purpose, such as 'Enable parallel test execution by removing test dependencies' or 'Refactor tests to remove dependency chains'.
Description check ❓ Inconclusive The PR description contains only the repository template with unfilled placeholders and no actual description of changes, objectives, or testing approach. Provide a detailed description explaining what changes were made, why they were necessary, and how the parallel test execution improvements benefit the project.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-db-tests

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.

@github-actions
Copy link

github-actions bot commented Jan 9, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libcrypto3 3.5.4-r0 CVE-2025-15467 CRITICAL
libcrypto3 3.5.4-r0 CVE-2025-69419 HIGH
libcrypto3 3.5.4-r0 CVE-2025-69421 HIGH
libexpat 2.7.3-r0 CVE-2026-24515 CRITICAL
libpng 1.6.51-r0 CVE-2025-66293 HIGH
libpng 1.6.51-r0 CVE-2026-22695 HIGH
libpng 1.6.51-r0 CVE-2026-22801 HIGH
libpng-dev 1.6.51-r0 CVE-2025-66293 HIGH
libpng-dev 1.6.51-r0 CVE-2026-22695 HIGH
libpng-dev 1.6.51-r0 CVE-2026-22801 HIGH
libssl3 3.5.4-r0 CVE-2025-15467 CRITICAL
libssl3 3.5.4-r0 CVE-2025-69419 HIGH
libssl3 3.5.4-r0 CVE-2025-69421 HIGH
libxml2 2.13.8-r0 CVE-2025-49794 CRITICAL
libxml2 2.13.8-r0 CVE-2025-49796 CRITICAL
libxml2 2.13.8-r0 CVE-2025-49795 HIGH
libxml2 2.13.8-r0 CVE-2025-6021 HIGH
openssl 3.5.4-r0 CVE-2025-15467 CRITICAL
openssl 3.5.4-r0 CVE-2025-69419 HIGH
openssl 3.5.4-r0 CVE-2025-69421 HIGH
openssl-dev 3.5.4-r0 CVE-2025-15467 CRITICAL
openssl-dev 3.5.4-r0 CVE-2025-69419 HIGH
openssl-dev 3.5.4-r0 CVE-2025-69421 HIGH
py3-urllib3 1.26.20-r0 CVE-2026-21441 HIGH
py3-urllib3-pyc 1.26.20-r0 CVE-2026-21441 HIGH
github.com/containerd/containerd/v2 v2.0.2 CVE-2024-25621 HIGH
golang.org/x/crypto v0.31.0 CVE-2025-22869 HIGH
golang.org/x/oauth2 v0.24.0 CVE-2025-22868 HIGH
stdlib 1.22.10 CVE-2025-47907 HIGH
stdlib 1.22.10 CVE-2025-58183 HIGH
stdlib 1.22.10 CVE-2025-61726 HIGH
stdlib 1.22.10 CVE-2025-61728 HIGH
stdlib 1.22.10 CVE-2025-61729 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: 8

🤖 Fix all issues with AI agents
In @tests/e2e/Services/Databases/LegacyCustomServerTest.php:
- Line 12: The class LegacyCustomServerTest references the DatabasesBase trait
but lacks an import; add the appropriate PHP "use" import for the DatabasesBase
trait (matching its declared namespace) at the top of the file and ensure the
class still uses the trait (e.g., the LegacyCustomServerTest class contains "use
DatabasesBase;") so PHP can locate the trait at runtime.

In @tests/e2e/Services/Databases/TablesDBConsoleClientTest.php:
- Line 12: The test class TablesDBConsoleClientTest uses the DatabasesBase trait
but does not import it; add the missing use/import for the DatabasesBase trait
(the trait symbol DatabasesBase referenced in TablesDBConsoleClientTest) at the
top of the file so PHP can resolve the trait, ensuring the namespace matches
where DatabasesBase is declared and no runtime fatal error occurs.

In @tests/e2e/Services/Databases/Transactions/ACIDBase.php:
- Line 288: The test currently accepts both 400 and 500 via
$this->assertContains($response['headers']['status-code'], [400, 500], ...),
which hides server errors; change the assertion to require a 400 status for
validation failures (e.g. replace the assertContains with
$this->assertEquals(400, $response['headers']['status-code'], 'Transaction
commit should fail due to validation. Response: ' .
json_encode($response['body'])) or an equivalent strict assertion) so only Bad
Request passes and 500s fail the test.

In
@tests/e2e/Services/Databases/Transactions/LegacyTransactionsCustomClientTest.php:
- Line 13: Add the missing import for the TransactionsBase trait: include a use
statement for TransactionsBase at the top of the test file (with other imports)
so the trait reference resolves; ensure the use statement references the trait's
correct namespace and is placed alongside the other use declarations before the
class declaration that uses TransactionsBase.

In
@tests/e2e/Services/Databases/Transactions/LegacyTransactionsCustomServerTest.php:
- Line 13: The test references the TransactionsBase trait but never imports it;
add a proper "use TransactionsBase;" import at the top of the class/file where
other trait imports live so the TransactionsBase trait is available to the test
(ensure you place the use statement alongside other use declarations and/or
trait imports to match project namespace conventions and resolve the symbol
TransactionsBase).

In @tests/e2e/Services/Databases/Transactions/TablesDBACIDTest.php:
- Line 13: The file references the ACIDBase trait but does not import its
namespace; add the correct import for the trait (e.g. add a top-level use
statement like use <Actual\Trait\Namespace>\ACIDBase;) or reference it with its
fully-qualified name where the trait is used (e.g. use
\Actual\Trait\Namespace\ACIDBase;) so PHP can locate the trait and avoid the
fatal runtime error.

In
@tests/e2e/Services/Databases/Transactions/TablesDBTransactionPermissionsCustomClientTest.php:
- Line 12: The file uses the TransactionPermissionsBase trait but lacks its
namespace import; add a top-level PHP "use" import for the
TransactionPermissionsBase trait that matches its actual namespace (so the trait
referenced by the class resolves), keeping the existing in-class "use
TransactionPermissionsBase;" line intact.

In
@tests/e2e/Services/Databases/Transactions/TablesDBTransactionsConsoleClientTest.php:
- Line 13: The TransactionsBase trait is referenced but not imported; add the
missing use statement for the TransactionsBase trait at the top of the test
class file so PHP can resolve it (e.g., add "use TransactionsBase;" or the
correct namespaced import matching the trait's namespace where the other imports
are declared), ensuring the class that uses TransactionsBase can compile and
run.
🧹 Nitpick comments (9)
tests/e2e/Services/Databases/LegacyConsoleClientTest.php (1)

1-16: Looks fine; double-check namespace/file-path convention for “Legacy” tests.*
If the suite expects Legacy* tests to live under a Tests\E2E\Services\Databases\Legacy namespace (PSR-4-ish), this could be a discovery/autoload footgun; if PHPUnit loads by path, it’s fine.

tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (1)

15-47: Duplicate URL helper methods – consider using DatabasesUrlHelpers trait instead.

These four URL helper methods (getDatabaseUrl, getContainerUrl, getSchemaUrl, getRecordUrl) duplicate the implementations in tests/e2e/Traits/DatabasesUrlHelpers.php. Additionally, the getSchemaUrl implementation here differs from DatabasesUrlHelpers – the trait version has special handling for relationship attributes that this version lacks.

Consider using the DatabasesUrlHelpers trait to avoid duplication and ensure consistent behavior:

 trait DatabasesPermissionsBase
 {
+    use \Tests\E2E\Traits\DatabasesUrlHelpers;
+
     public array $users = [];
     public array $teams = [];
 
-    // URL Helper Methods - uses methods from ApiLegacy/ApiTablesDB traits
-    protected function getDatabaseUrl(string $databaseId = ''): string
-    {
-        ...
-    }
-    // ... remove other duplicated URL methods
tests/e2e/Services/Databases/Permissions/LegacyPermissionsGuestTest.php (1)

86-86: Consider replacing sleep() with a polling mechanism for attribute readiness.

Using fixed sleep(2) delays is fragile and can cause flaky tests if attribute indexing takes longer than expected, or wastes time if it completes faster. A polling approach that checks attribute status would be more reliable.

tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (1)

96-99: Remove debug output statements before merging.

These echo statements will pollute test output in CI/CD pipelines. Consider using PHPUnit's assertion messages or removing them entirely since the assertion on line 100 will fail with a clear message if the status code is wrong.

🔧 Suggested fix
         // This should fail with 401 Unauthorized
-        if ($staged['headers']['status-code'] !== 401) {
-            echo "\nDEBUG - Actual response code: " . $staged['headers']['status-code'] . "\n";
-            echo "DEBUG - Response body: " . json_encode($staged['body'], JSON_PRETTY_PRINT) . "\n";
-        }
-        $this->assertEquals(401, $staged['headers']['status-code']);
+        $this->assertEquals(401, $staged['headers']['status-code'], 
+            'Expected 401 Unauthorized, got: ' . $staged['headers']['status-code'] . 
+            '. Response: ' . json_encode($staged['body']));
tests/e2e/Traits/DatabaseFixture.php (2)

56-77: Implicit method dependencies not defined in trait.

The trait calls getSchemaApiConfig(), waitForAllAttributes(), and waitForAllIndexes() (lines 58, 184-185, 200) which are not defined within this trait. This creates implicit dependencies on the consuming class.

Consider one of:

  1. Declaring abstract methods in the trait to make dependencies explicit
  2. Adding a docblock documenting required methods
  3. Using the existing ApiLegacy/ApiTablesDB traits' methods instead of getSchemaApiConfig()
📖 Example: Document dependencies
/**
 * Provides pre-created database fixtures for read-only tests.
 * 
 * Required methods from consuming class:
 * - getSchemaApiConfig(): array
 * - waitForAllAttributes(string $databaseId, string $collectionId): void
 * - waitForAllIndexes(string $databaseId, string $collectionId): void
 * - getProject(): array
 * - getHeaders(): array
 * - getUser(): array
 */
trait DatabaseFixture

74-77: Consider using trait methods instead of hardcoded key names.

The conditional key assignments duplicate logic that exists in ApiLegacy and ApiTablesDB traits. Using the trait methods would be more consistent:

♻️ Suggested refactor
-        $collectionKey = $isTablesDB ? 'tableId' : 'collectionId';
-        $docKey = $isTablesDB ? 'rowId' : 'documentId';
-        $docEndpoint = $isTablesDB ? 'rows' : 'documents';
+        $collectionKey = $this->getContainerIdParam();
+        $docKey = $this->getRecordIdParam();
+        $docEndpoint = $this->getRecordResource();

This requires the consuming class to use an API trait (ApiLegacy or ApiTablesDB), which aligns with the PR's trait composition pattern.

tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (1)

21-22: Consider using unique database IDs for test isolation.

The hardcoded databaseId = 'testpermissiondb' could cause test conflicts if tests run in parallel or if cleanup fails. Consider using ID::unique() like other tests in this PR.

Suggested approach
     public array $collections = [];
-    public string $databaseId = 'testpermissiondb';
+    public string $databaseId = '';

Then in createCollections():

+        $this->databaseId = ID::unique();
         $db = $this->client->call(
             Client::METHOD_POST,
             $this->getDatabaseUrl(),
             $this->getServerHeader(),
             [
                 'databaseId' => $this->databaseId,
tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php (1)

21-22: Same hardcoded databaseId concern as TablesDBPermissionsTeamTest.

Consider using ID::unique() for test isolation, as noted in the TablesDB variant review.

tests/e2e/Traits/DatabasesUrlHelpers.php (1)

12-61: Investigate getSchemaUrl duplication between DatabasesPermissionsBase and DatabasesUrlHelpers.

Both files define URL helper methods, but implementations differ. Most notably:

  • getSchemaUrl in DatabasesPermissionsBase lacks the relationship-specific logic present in DatabasesUrlHelpers (relationship URLs need /attributes/{key}/relationship vs /attributes/{type}/{key})
  • Parameter types differ (string vs ?string for optional parameters)
  • DatabasesUrlHelpers includes two additional methods (getTransactionUrl, getIndexUrl) not in DatabasesPermissionsBase

Verify whether both versions of getSchemaUrl are needed for different test scenarios or if one implementation should be unified. If the relationship logic should apply globally, consolidate to avoid future divergence.

📜 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 445d749 and 658308a.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (54)
  • tests/e2e/Scopes/ApiLegacy.php
  • tests/e2e/Scopes/ApiTablesDB.php
  • tests/e2e/Services/Databases/DatabasesBase.php
  • tests/e2e/Services/Databases/Legacy/DatabasesConsoleClientTest.php
  • tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php
  • tests/e2e/Services/Databases/Legacy/DatabasesCustomServerTest.php
  • tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php
  • tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsTeamTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsBase.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php
  • tests/e2e/Services/Databases/LegacyConsoleClientTest.php
  • tests/e2e/Services/Databases/LegacyCustomClientTest.php
  • tests/e2e/Services/Databases/LegacyCustomServerTest.php
  • tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php
  • tests/e2e/Services/Databases/Permissions/LegacyPermissionsGuestTest.php
  • tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php
  • tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsMemberTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesConsoleClientTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsMemberTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsScope.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsTeamTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php
  • tests/e2e/Services/Databases/TablesDBConsoleClientTest.php
  • tests/e2e/Services/Databases/TablesDBCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDBCustomServerTest.php
  • tests/e2e/Services/Databases/Transactions/ACIDBase.php
  • tests/e2e/Services/Databases/Transactions/LegacyACIDTest.php
  • tests/e2e/Services/Databases/Transactions/LegacyTransactionPermissionsCustomClientTest.php
  • tests/e2e/Services/Databases/Transactions/LegacyTransactionsConsoleClientTest.php
  • tests/e2e/Services/Databases/Transactions/LegacyTransactionsCustomClientTest.php
  • tests/e2e/Services/Databases/Transactions/LegacyTransactionsCustomServerTest.php
  • tests/e2e/Services/Databases/Transactions/TablesDBACIDTest.php
  • tests/e2e/Services/Databases/Transactions/TablesDBTransactionPermissionsCustomClientTest.php
  • tests/e2e/Services/Databases/Transactions/TablesDBTransactionsConsoleClientTest.php
  • tests/e2e/Services/Databases/Transactions/TablesDBTransactionsCustomClientTest.php
  • tests/e2e/Services/Databases/Transactions/TablesDBTransactionsCustomServerTest.php
  • tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php
  • tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php.tmp
  • tests/e2e/Services/Databases/Transactions/TransactionsBase.php
  • tests/e2e/Traits/DatabaseFixture.php
  • tests/e2e/Traits/DatabasesUrlHelpers.php
💤 Files with no reviewable changes (18)
  • tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsScope.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsConsoleClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsMemberTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomServerTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/PermissionsCustomClientTest.php
  • tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/TransactionsCustomClientTest.php
  • tests/e2e/Services/Databases/Legacy/DatabasesConsoleClientTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsTeamTest.php
  • tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsTeamTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsCustomServerTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
  • tests/e2e/Services/Databases/TablesDB/DatabasesConsoleClientTest.php
  • tests/e2e/Services/Databases/Legacy/Transactions/TransactionsConsoleClientTest.php
  • tests/e2e/Services/Databases/TablesDB/Transactions/ACIDTest.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-02T10:16:48.075Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10023
File: src/Appwrite/Databases/TransactionState.php:174-219
Timestamp: 2025-10-02T10:16:48.075Z
Learning: In `src/Appwrite/Databases/TransactionState.php`, the `listDocuments` method intentionally applies queries only to the initial database fetch of committed documents. Transaction state changes (creates, updates, deletes) are then overlaid on top of the filtered committed set without re-evaluating the queries against the final merged result. This is the intended behavior for transaction-aware document listing.

Applied to files:

  • tests/e2e/Services/Databases/Transactions/ACIDBase.php
🧬 Code graph analysis (23)
tests/e2e/Services/Databases/TablesDBConsoleClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Transactions/TablesDBACIDTest.php (2)
src/Appwrite/Platform/Modules/Databases/Workers/Databases.php (1)
  • Databases (21-644)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/TablesDBCustomClientTest.php (2)
src/Appwrite/Platform/Modules/Databases/Workers/Databases.php (1)
  • Databases (21-644)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/LegacyCustomClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Transactions/TablesDBTransactionPermissionsCustomClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (4)
tests/e2e/Scopes/ApiLegacy.php (4)
  • getApiBasePath (11-14)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getApiBasePath (11-14)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (3)
  • getContainerUrl (22-27)
  • getSchemaUrl (29-40)
  • getRecordUrl (42-47)
tests/e2e/Traits/DatabasesUrlHelpers.php (4)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getTransactionUrl (51-55)
  • getRecordUrl (44-49)
tests/e2e/Services/Databases/LegacyCustomServerTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php (1)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsMemberTest.php (3)
tests/e2e/Traits/DatabasesUrlHelpers.php (4)
  • getDatabaseUrl (12-16)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getRecordUrl (44-49)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (5)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (7)
  • createTeam (92-101)
  • createUser (50-84)
  • getDatabaseUrl (16-20)
  • getServerHeader (119-126)
  • getContainerUrl (22-27)
  • getSchemaUrl (29-40)
  • getRecordUrl (42-47)
tests/e2e/Traits/DatabasesUrlHelpers.php (4)
  • getDatabaseUrl (12-16)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getRecordUrl (44-49)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php (3)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (5)
  • getDatabaseUrl (16-20)
  • getServerHeader (119-126)
  • getContainerUrl (22-27)
  • getSchemaUrl (29-40)
  • getRecordUrl (42-47)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Traits/DatabaseFixture.php (1)
tests/e2e/Client.php (1)
  • Client (8-342)
tests/e2e/Services/Databases/Transactions/TablesDBTransactionsConsoleClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Scopes/ApiTablesDB.php (1)
tests/e2e/Scopes/ApiLegacy.php (14)
  • getApiBasePath (11-14)
  • getDatabaseType (16-19)
  • getContainerResource (21-24)
  • getContainerIdParam (26-29)
  • getSchemaResource (31-34)
  • getSchemaParam (36-39)
  • getRecordResource (41-44)
  • getRecordIdParam (46-49)
  • getSecurityParam (51-54)
  • getRelatedIdParam (56-59)
  • getRelatedResourceKey (61-64)
  • getContainerIdResponseKey (66-69)
  • getIndexAttributesParam (71-74)
  • getSecurityResponseKey (76-79)
tests/e2e/Services/Databases/TablesDBCustomServerTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Transactions/ACIDBase.php (3)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (4)
  • getDatabaseUrl (16-20)
  • getContainerUrl (22-27)
  • getSchemaUrl (29-40)
  • getRecordUrl (42-47)
tests/e2e/Scopes/ApiLegacy.php (4)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
  • getSchemaParam (36-39)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
  • getSchemaParam (36-39)
tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php (3)
tests/e2e/Traits/DatabasesUrlHelpers.php (4)
  • getDatabaseUrl (12-16)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getRecordUrl (44-49)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Services/Databases/Transactions/LegacyACIDTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (3)
tests/e2e/Traits/DatabasesUrlHelpers.php (4)
  • getDatabaseUrl (12-16)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getRecordUrl (44-49)
tests/e2e/Scopes/ApiLegacy.php (4)
  • getApiBasePath (11-14)
  • getContainerResource (21-24)
  • getSchemaResource (31-34)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getApiBasePath (11-14)
  • getContainerResource (21-24)
  • getSchemaResource (31-34)
  • getRecordResource (41-44)
tests/e2e/Traits/DatabasesUrlHelpers.php (3)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (4)
  • getDatabaseUrl (16-20)
  • getContainerUrl (22-27)
  • getSchemaUrl (29-40)
  • getRecordUrl (42-47)
tests/e2e/Scopes/ApiLegacy.php (4)
  • getApiBasePath (11-14)
  • getContainerResource (21-24)
  • getSchemaResource (31-34)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getApiBasePath (11-14)
  • getContainerResource (21-24)
  • getSchemaResource (31-34)
  • getRecordResource (41-44)
tests/e2e/Services/Databases/Transactions/LegacyTransactionPermissionsCustomClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Services/Databases/LegacyConsoleClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • Scope (12-256)
tests/e2e/Scopes/ApiLegacy.php (1)
tests/e2e/Scopes/ApiTablesDB.php (14)
  • getApiBasePath (11-14)
  • getDatabaseType (16-19)
  • getContainerResource (21-24)
  • getContainerIdParam (26-29)
  • getSchemaResource (31-34)
  • getSchemaParam (36-39)
  • getRecordResource (41-44)
  • getRecordIdParam (46-49)
  • getSecurityParam (51-54)
  • getRelatedIdParam (56-59)
  • getRelatedResourceKey (61-64)
  • getContainerIdResponseKey (66-69)
  • getIndexAttributesParam (71-74)
  • getSecurityResponseKey (76-79)
🪛 GitHub Actions: Linter
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php

[error] 1-1: Pint linting failed. PSR-12 style issues detected. Script 'vendor/bin/pint --test --config pint.json' exited with code 1.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan
🔇 Additional comments (41)
tests/e2e/Services/Databases/Transactions/LegacyTransactionPermissionsCustomClientTest.php (1)

1-16: Good composition-only scaffold for the “custom client + legacy API” permissions suite.
Assuming TransactionPermissionsBase carries the test* methods and ProjectCustom/SideClient provide Scope’s abstract methods, this is good to go.

tests/e2e/Services/Databases/LegacyCustomClientTest.php (1)

1-16: LGTM scaffold for “legacy + custom client” DB tests.
Consistent with the trait-composition pattern introduced in this PR.

tests/e2e/Services/Databases/Transactions/LegacyACIDTest.php (1)

1-18: Good setup; watch for trait method-name collisions (ACIDBase vs URL/API helper traits).
If any two traits define the same method name without insteadof/aliasing, PHP will fatal at load time—worth a quick sanity check as the trait stack grows.

tests/e2e/Services/Databases/TablesDBCustomServerTest.php (1)

1-16: Scaffold is correct; trait composition properly satisfies Scope abstract methods.

All referenced traits (DatabasesBase, ProjectCustom, SideServer, ApiTablesDB) are properly defined. The Scope abstract methods getHeaders() and getProject() are provided by SideServer (line 12 in SideServer.php) and ProjectCustom (line 21 in ProjectCustom.php) respectively. No runtime errors will occur.

tests/e2e/Services/Databases/Transactions/LegacyTransactionsCustomClientTest.php (1)

3-3: Verify namespace includes Legacy subdirectory.

Ensure the namespace aligns with the file path. The filename LegacyTransactionsCustomClientTest.php suggests the file may be located in a Legacy subdirectory under Transactions/, in which case the namespace should be Tests\E2E\Services\Databases\Transactions\Legacy rather than Tests\E2E\Services\Databases\Transactions.

tests/e2e/Services/Databases/Transactions/LegacyTransactionsConsoleClientTest.php (2)

3-3: [No action required] The namespace is correct. The file path tests/e2e/Services/Databases/Transactions/LegacyTransactionsConsoleClientTest.php correctly maps to the namespace Tests\E2E\Services\Databases\Transactions. The "Legacy" prefix is part of the class name, not a directory level—the subdirectory structure is Services/Databases/Transactions/, not Services/Databases/Legacy/Transactions/.

Likely an incorrect or invalid review comment.


13-13: No action needed. The TransactionsBase trait is in the same namespace as the test class (Tests\E2E\Services\Databases\Transactions) and resolves correctly without an explicit import statement. Unqualified trait names automatically resolve within the current namespace in PHP.

Likely an incorrect or invalid review comment.

tests/e2e/Services/Databases/Transactions/TablesDBTransactionsCustomClientTest.php (2)

3-3: No action required. The namespace Tests\E2E\Services\Databases\Transactions correctly aligns with the actual file path tests/e2e/Services/Databases/Transactions/TablesDBTransactionsCustomClientTest.php per PSR-4 autoloading standards. The concern was based on an inaccurate path suggestion.

Likely an incorrect or invalid review comment.


13-13: No action needed. The TransactionsBase trait is properly accessible in the same namespace without an explicit import statement.

Likely an incorrect or invalid review comment.

tests/e2e/Services/Databases/Transactions/LegacyTransactionsCustomServerTest.php (1)

3-3: No action needed — namespace correctly matches file path.

The namespace Tests\E2E\Services\Databases\Transactions accurately reflects the file's location at tests/e2e/Services/Databases/Transactions/. The "Legacy" designation in the class name does not require a separate directory level. Other similar test files in the same directory follow the identical namespace pattern.

Likely an incorrect or invalid review comment.

tests/e2e/Services/Databases/TablesDBCustomClientTest.php (1)

12-12: > Likely an incorrect or invalid review comment.

tests/e2e/Services/Databases/Transactions/TablesDBTransactionsCustomServerTest.php (1)

11-18: LGTM!

The test scaffold correctly composes the required traits for TablesDB transaction testing with server-side authentication. The trait composition pattern is consistent with the PR's architecture.

tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (1)

50-126: LGTM!

The user management, team management, and server header helper methods are well-structured and follow the established test patterns in the codebase.

tests/e2e/Services/Databases/Permissions/LegacyPermissionsGuestTest.php (2)

14-20: LGTM!

The test class correctly composes the required traits for legacy API guest permission testing. The trait composition aligns with the PR's architecture pattern.


107-172: LGTM!

The testReadDocuments test method properly validates read permissions across public and private collections with various permission configurations. The logic correctly handles the Role::any() permission check for private collection access.

tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (3)

11-14: LGTM!

The trait correctly uses DatabasesUrlHelpers for URL construction and declares the $permissionsDatabase property for storing the test database ID.


674-785: Well-designed cross-batch dependency test.

The testCanUpdateDocumentCreatedInPreviousBatch method effectively validates the transaction state management for cross-batch dependencies. The test properly covers create → update → delete sequences and includes cleanup via rollback.


787-1009: Comprehensive user isolation tests.

The user isolation tests (testUserCannotReadAnotherUsersTransaction, testUserCannotListAnotherUsersTransactions, testUserCannotUpdateAnotherUsersTransaction, testUserCannotDeleteAnotherUsersTransaction) provide excellent coverage for transaction access control. Each test properly creates fresh users and validates that unauthorized access returns 404.

tests/e2e/Scopes/ApiTablesDB.php (1)

5-80: LGTM!

The ApiTablesDB trait provides a consistent API configuration pattern that mirrors ApiLegacy. The method signatures and return values correctly map to the TablesDB API resources (/tablesdb, tables, columns, rows).

tests/e2e/Traits/DatabaseFixture.php (1)

229-238: LGTM!

The tearDownAfterClass method properly resets all static fixture state and calls the parent implementation, ensuring clean test isolation between test classes.

tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php (3)

14-19: LGTM!

The class correctly migrates to the new trait composition pattern with DatabasesPermissionsBase and ApiLegacy, aligning with the PR's architecture.


117-229: LGTM!

The testSetupDatabase method is well-refactored to use URL helper methods (getDatabaseUrl, getContainerUrl, getSchemaUrl) and parameter helpers (getContainerIdParam, getSecurityParam) consistently throughout. This improves maintainability and allows the same test logic to work with different API implementations.


236-334: LGTM!

The testReadDocuments test method correctly uses the URL helpers for all API calls and properly tests document visibility across different collection permission configurations.

tests/e2e/Services/Databases/Transactions/ACIDBase.php (3)

11-166: LGTM on testAtomicity implementation.

The atomicity test correctly validates that either all operations succeed or none are applied when a unique constraint violation occurs. The use of dynamic URL helpers (getDatabaseUrl, getContainerUrl, etc.) and parameter helpers (getContainerIdParam, getRecordIdParam) enables proper reuse across Legacy and TablesDB API variants.


302-454: LGTM on testIsolation.

The isolation test correctly validates that concurrent transactions on the same data are properly handled, with both increments applied atomically (0 + 10 + 5 = 15).


459-618: LGTM on testDurability.

The durability test properly validates that committed transaction data persists and can be subsequently modified outside the transaction context.

tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php (5)

14-20: LGTM on trait composition and class structure.

The test class properly composes the required traits (DatabasesPermissionsBase, ProjectCustom, SideClient, ApiTablesDB) for TablesDB guest permission testing, enabling reuse of URL helpers and API parameter mappings.


21-105: LGTM on collection setup and data provider.

The createCollection method properly creates both public (with Role::any() permissions) and private (with rowSecurity enabled) collections, and permissionsProvider covers key permission scenarios for data-driven testing.


107-172: LGTM on testReadDocuments.

The test correctly validates guest read access based on document permissions, properly checking that guests can only read documents with Permission::read(Role::any()) in private collections.


174-286: LGTM on testWriteDocument.

The test comprehensively validates guest write permissions, correctly asserting 401 for unauthorized operations on private collections while allowing operations on public collections with Role::any() permissions.


288-357: LGTM on testWriteDocumentWithPermissions.

The test properly validates that guests can create documents with specific permissions when the collection allows Permission::create(Role::any()).

tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (2)

24-147: LGTM on team permission setup and data providers.

The setup correctly creates team-based permission scenarios with:

  • Collection 1: Team 1 read access, admin role for CUD
  • Collection 2: Team 2 read access, owner role for CUD
  • User3 in both teams without roles to test basic membership

149-253: LGTM on test methods with proper @depends/@dataProvider usage.

The tests correctly use PHPUnit's @depends testSetupDatabase with @dataProvider to share state while iterating over permission scenarios. The assertion logic properly handles both authorized (201/200) and unauthorized (401/404) responses.

tests/e2e/Services/Databases/Permissions/TablesDBPermissionsMemberTest.php (3)

31-107: LGTM on comprehensive permission data provider.

The 12 permission scenarios cover key cases including Role::any(), Role::users(), specific user roles, and combinations. The cumulative count expectations (any: 1→12, users: 1→12, doconly: 1→7) correctly reflect document visibility based on permissions.


231-334: LGTM on testReadDocuments implementation.

The test correctly validates document visibility across three collection types (public/private/doconly) for user1, using the cumulative count approach to verify permission-based filtering works correctly.


163-163: Status code 202 is correct for attribute creation.

The expected status code of 202 is consistent across all database tests in the codebase. The TablesDB API returns 202 (Accepted) for asynchronous attribute creation, followed by a sleep period to allow the operation to complete. This is the standard behavior and requires no changes.

tests/e2e/Traits/DatabasesUrlHelpers.php (1)

5-9: Good documentation of trait dependencies.

The docblock clearly states that this trait requires ApiLegacy or ApiTablesDB to provide getApiBasePath(), getContainerResource(), etc. This helps developers understand the composition requirements.

tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php (2)

14-19: LGTM on trait composition for Legacy API.

The class correctly uses ApiLegacy (instead of ApiTablesDB in the TablesDB variant) to configure legacy API paths (/databases, collections, documents).


24-253: LGTM on test implementation.

The test logic correctly mirrors TablesDBPermissionsTeamTest for the legacy API, validating team-based permissions for collections and documents.

tests/e2e/Scopes/ApiLegacy.php (2)

5-9: Good documentation of trait purpose.

The docblock clearly describes the trait's role in configuring legacy database API paths (/databases, collections, attributes, documents).


11-79: LGTM on API configuration methods.

All 14 getter methods correctly return legacy-specific configuration values that mirror the ApiTablesDB trait structure. The response keys with $ prefix ($collectionId) correctly match Appwrite's response format.

@github-actions
Copy link

github-actions bot commented Jan 9, 2026

✨ Benchmark results

  • Requests per second: 3,027
  • Requests with 200 status code: 544,984
  • P99 latency: 0.053167932

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 3,027 1,407
200 544,984 253,306
P99 0.053167932 0.148249624

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: 7

🤖 Fix all issues with AI agents
In @tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php:
- Around line 108-177: testCollectionUpdatePermissionDenied opens a transaction
($transaction from getTransactionUrl()) and leaves it open on failure; wrap the
transaction lifecycle in a try/finally and always close it in the finally block
(call the transaction delete/rollback endpoint via
getTransactionUrl($transaction['body']['$id']) with the same headers) so the
transaction is removed regardless of the staged operation result; ensure the
finally runs even when the staged call asserts a 401 so tests stay isolated.
- Around line 22-37: The test setup creates a database in setUp() but never
cleans it up; add a tearDown() method that checks $this->permissionsDatabase and
calls $this->client->call(Client::METHOD_DELETE, $this->getApiBasePath(), [same
headers used in setUp()], ['databaseId' => $this->permissionsDatabase]) to
remove it after each test; ensure tearDown() runs even on failures (override
tearDown(): void and call parent::tearDown() at the end), and handle/delete
errors safely (catch exceptions or ignore not-found responses) so cleanup does
not mask test failures.
- Around line 838-898: The test testUserCannotListAnotherUsersTransactions
relies on strict total checks (assertEquals(1 / 2 on listUser2/listUser1) which
is flaky if other transactions exist; instead remove the assertEquals on list
totals and verify ownership by checking returned transaction IDs: for listUser2
and listUser1 use array_column($response['body']['transactions'], '$id') and
assertContains/assertNotContains for expected IDs (transaction3 present in
listUser2, transaction1/transaction2 absent), and optionally assertCount on the
filtered arrays of IDs that match the expected set (e.g.,
count(array_intersect($returnedIds, [$transaction3['body']['$id']])) === 1).
Replace references to the total assertions in
testUserCannotListAnotherUsersTransactions with these ID-based presence/absence
checks using getTransactionUrl(), getUser(),
transaction1/transaction2/transaction3 and listUser1/listUser2 response
variables.
- Around line 1016-1105: Add explicit cleanup to remove/rollback user1's
transaction at the end of testUserCannotAddOperationsToAnotherUsersTransaction:
after the final assertions call the transactions delete/rollback endpoint for
$transactionId1 using the same $user1Headers (invoke Client::METHOD_DELETE on
$this->getTransactionUrl($transactionId1)) and assert the expected success
status (e.g., 204) so the staged transaction is removed and the test environment
stays clean.
- Around line 182-250: In testCollectionDeletePermissionDenied: after the staged
operations check, explicitly close the created transaction (using
getTransactionUrl with $transaction['body']['$id']) by calling the appropriate
commit/rollback/delete endpoint via $this->client->call and assert a successful
close status so the transaction is not left open and tests remain isolated.
- Around line 413-479: The test method testCannotSetUnauthorizedRolePermissions
currently accesses $staged['body']['message'] directly which can trigger
undefined index notices if the API returns an empty or differently-shaped error
body; update the assertions to first guard the shape of $staged (e.g., assert
that 'body' exists and is an array, and that 'message' key exists or use isset
on $staged['body']['message']) before asserting its contents, and if missing
fail with a clear assertion message so the test reports a meaningful error
rather than a PHP notice.
- Around line 42-103: The test prints debug output with echo and never closes
the created transaction; remove the echo statements and instead include the
response body/status in the assertion message for the $staged check, and wrap
the transaction lifecycle in a try/finally so the transaction created by
$transaction = $this->client->call(...) is cleaned up (rollback/delete) in the
finally block using the transaction id ($transaction['body']['$id']) and
getTransactionUrl; ensure you reference $staged for the assertion message and
always call the API to delete/rollback the transaction in the finally regardless
of test outcome.
🧹 Nitpick comments (8)
tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (5)

12-18: Make the trait’s required host-class contract explicit (to avoid “magic” dependencies).

This trait implicitly requires $this->client, getProject(), getHeaders(), getUser(), getTransactionUrl(), etc. Consider adding a @mixin/@property phpdoc (or declaring abstract method stubs) so consumers fail fast and IDE/static analysis can help.


553-611: Status-code expectation looks reasonable; consider asserting error shape for debuggability.

For failures like 404, asserting a stable error code/type field (if present) is usually less brittle than message strings.


616-675: Status-code expectation looks reasonable; consider asserting error shape for debuggability.

Same note as the non-existent update case.


680-787: Good cross-batch dependency coverage; cleanup is handled here (rollback).

Nice that this one explicitly rolls back—consider applying the same pattern to the other transaction-creating tests for consistency.


1110-1157: Test intent is good; consider using a fresh user here for determinism.

Using getUser() (non-fresh) plus >= 3 makes the test tolerant, but also less precise; a fresh user would let you assert exact totals and better catch regressions.

tests/e2e/Scopes/SchemaPolling.php (1)

28-32: Remove unnecessary array_merge() calls.

Throughout this trait, array_merge() is called with a single array argument (lines 28, 55, 94, 131, 157, 186). This is redundant since there's nothing to merge.

♻️ Suggested simplification
-            $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']
-                ])
-            );
+            $attribute = $this->client->call(
+                Client::METHOD_GET,
+                $this->getSchemaUrl($databaseId, $containerId) . '/' . $attributeKey,
+                [
+                    'content-type' => 'application/json',
+                    'x-appwrite-project' => $this->getProject()['$id'],
+                    'x-appwrite-key' => $this->getProject()['apiKey']
+                ]
+            );

Apply this same simplification to all similar occurrences in waitForAttributes, waitForAttributeCount, waitForIndex, waitForAllIndexes, and waitForAllAttributes.

tests/e2e/Services/Databases/Transactions/ACIDBase.php (1)

292-292: Use assertContains correctly or prefer explicit assertions.

assertContains($needle, $haystack) in PHPUnit 9.x expects an iterable as the second argument. The current usage passes the result as $needle and an array as $haystack, which is correct. However, for clarity and type safety when checking against a set of expected status codes, consider using an explicit conditional or assertThat with logicalOr.

♻️ Alternative approach for clearer intent
-        $this->assertContains($response['headers']['status-code'], [400, 500], 'Transaction commit should fail due to validation. Response: ' . json_encode($response['body']));
+        $statusCode = $response['headers']['status-code'];
+        $this->assertTrue(
+            in_array($statusCode, [400, 500], true),
+            "Transaction commit should fail due to validation (expected 400 or 500, got {$statusCode}). Response: " . json_encode($response['body'])
+        );
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (1)

12-44: Code duplication: URL helpers are duplicated from DatabasesUrlHelpers trait.

These URL helper methods (getDatabaseUrl, getContainerUrl, getSchemaUrl, getRecordUrl) duplicate the implementation in tests/e2e/Traits/DatabasesUrlHelpers.php. Additionally, this implementation of getSchemaUrl is missing the special handling for relationship attributes that exists in DatabasesUrlHelpers.

Consider using the DatabasesUrlHelpers trait instead of duplicating these methods. This would eliminate the duplication and ensure consistency across the test suite.

♻️ Suggested refactor to use existing trait
 namespace Tests\E2E\Services\Databases\Permissions;

 use Tests\E2E\Client;
+use Tests\E2E\Traits\DatabasesUrlHelpers;

 trait DatabasesPermissionsBase
 {
+    use DatabasesUrlHelpers;
+
     public array $users = [];
     public array $teams = [];

-    // URL Helper Methods - uses methods from ApiLegacy/ApiTablesDB traits
-    protected function getDatabaseUrl(string $databaseId = ''): string
-    {
-        $base = $this->getApiBasePath();
-        return $databaseId ? "{$base}/{$databaseId}" : $base;
-    }
-
-    protected function getContainerUrl(string $databaseId, string $containerId = ''): string
-    {
-        $resource = $this->getContainerResource();
-        $base = "{$this->getApiBasePath()}/{$databaseId}/{$resource}";
-        return $containerId ? "{$base}/{$containerId}" : $base;
-    }
-
-    protected function getSchemaUrl(string $databaseId, string $containerId, string $type = '', string $key = ''): string
-    {
-        $resource = $this->getSchemaResource();
-        $base = "{$this->getContainerUrl($databaseId, $containerId)}/{$resource}";
-        if ($type) {
-            $base .= "/{$type}";
-        }
-        if ($key) {
-            $base .= "/{$key}";
-        }
-        return $base;
-    }
-
-    protected function getRecordUrl(string $databaseId, string $containerId, string $recordId = ''): string
-    {
-        $resource = $this->getRecordResource();
-        $base = "{$this->getContainerUrl($databaseId, $containerId)}/{$resource}";
-        return $recordId ? "{$base}/{$recordId}" : $base;
-    }
-
     // User Management Methods
📜 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 658308a and f797ae3.

📒 Files selected for processing (9)
  • .github/workflows/tests.yml
  • tests/e2e/Scopes/ApiLegacy.php
  • tests/e2e/Scopes/ApiTablesDB.php
  • tests/e2e/Scopes/SchemaPolling.php
  • tests/e2e/Services/Databases/DatabasesBase.php
  • tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php
  • tests/e2e/Services/Databases/Transactions/ACIDBase.php
  • tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php
  • tests/e2e/Services/Databases/Transactions/TransactionsBase.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-02T10:16:48.075Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10023
File: src/Appwrite/Databases/TransactionState.php:174-219
Timestamp: 2025-10-02T10:16:48.075Z
Learning: In `src/Appwrite/Databases/TransactionState.php`, the `listDocuments` method intentionally applies queries only to the initial database fetch of committed documents. Transaction state changes (creates, updates, deletes) are then overlaid on top of the filtered committed set without re-evaluating the queries against the final merged result. This is the intended behavior for transaction-aware document listing.

Applied to files:

  • tests/e2e/Services/Databases/Transactions/ACIDBase.php
🧬 Code graph analysis (6)
tests/e2e/Services/Databases/Transactions/ACIDBase.php (3)
tests/e2e/Scopes/ApiLegacy.php (6)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getIndexAttributesParam (76-79)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
  • getSchemaParam (36-39)
tests/e2e/Scopes/ApiTablesDB.php (6)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getIndexAttributesParam (76-79)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
  • getSchemaParam (36-39)
tests/e2e/Scopes/SchemaPolling.php (2)
  • waitForAllAttributes (180-199)
  • waitForIndex (125-141)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (3)
tests/e2e/Traits/DatabasesUrlHelpers.php (4)
  • getDatabaseUrl (12-16)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getRecordUrl (44-49)
tests/e2e/Scopes/ApiLegacy.php (4)
  • getApiBasePath (11-14)
  • getContainerResource (21-24)
  • getSchemaResource (31-34)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getApiBasePath (11-14)
  • getContainerResource (21-24)
  • getSchemaResource (31-34)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (1)
tests/e2e/Scopes/ApiLegacy.php (15)
  • getApiBasePath (11-14)
  • getDatabaseType (16-19)
  • getContainerResource (21-24)
  • getContainerIdParam (26-29)
  • getSchemaResource (31-34)
  • getSchemaParam (36-39)
  • getRecordResource (41-44)
  • getRecordIdParam (46-49)
  • getSecurityParam (51-54)
  • getRelatedIdParam (56-59)
  • getRelatedResourceKey (61-64)
  • getContainerIdResponseKey (66-69)
  • getOppositeContainerIdResponseKey (71-74)
  • getIndexAttributesParam (76-79)
  • getSecurityResponseKey (81-84)
tests/e2e/Scopes/SchemaPolling.php (4)
tests/e2e/Client.php (1)
  • Client (8-342)
tests/e2e/Scopes/ApiLegacy.php (1)
  • getSchemaResource (31-34)
tests/e2e/Scopes/ApiTablesDB.php (1)
  • getSchemaResource (31-34)
tests/e2e/Traits/DatabasesUrlHelpers.php (1)
  • getIndexUrl (57-61)
tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (3)
tests/e2e/Scopes/ApiLegacy.php (4)
  • getApiBasePath (11-14)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getApiBasePath (11-14)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Traits/DatabasesUrlHelpers.php (3)
  • getContainerUrl (18-23)
  • getSchemaUrl (25-42)
  • getRecordUrl (44-49)
tests/e2e/Scopes/ApiLegacy.php (1)
tests/e2e/Scopes/ApiTablesDB.php (15)
  • getApiBasePath (11-14)
  • getDatabaseType (16-19)
  • getContainerResource (21-24)
  • getContainerIdParam (26-29)
  • getSchemaResource (31-34)
  • getSchemaParam (36-39)
  • getRecordResource (41-44)
  • getRecordIdParam (46-49)
  • getSecurityParam (51-54)
  • getRelatedIdParam (56-59)
  • getRelatedResourceKey (61-64)
  • getContainerIdResponseKey (66-69)
  • getOppositeContainerIdResponseKey (71-74)
  • getIndexAttributesParam (76-79)
  • getSecurityResponseKey (81-84)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Benchmark
  • GitHub Check: E2E Service Test (Migrations)
  • GitHub Check: E2E Service Test (Projects)
  • GitHub Check: E2E Service Test (Messaging)
  • GitHub Check: E2E Service Test (Tokens)
  • GitHub Check: E2E Service Test (Sites)
  • GitHub Check: E2E Service Test (Proxy)
  • GitHub Check: E2E Service Test (Storage)
  • GitHub Check: E2E Service Test (Teams)
  • GitHub Check: E2E Service Test (Locale)
  • GitHub Check: E2E Service Test (FunctionsSchedule)
  • GitHub Check: E2E Service Test (Databases)
  • GitHub Check: E2E Service Test (GraphQL)
  • GitHub Check: E2E Service Test (Avatars)
  • GitHub Check: E2E Service Test (Console)
  • GitHub Check: E2E Service Test (Site Screenshots)
  • GitHub Check: Unit Test
  • GitHub Check: E2E General Test
  • GitHub Check: E2E Service Test (Abuse enabled)
  • GitHub Check: scan
🔇 Additional comments (16)
.github/workflows/tests.yml (2)

243-266: Same consolidation concern applies here for shared-mode tests.

This matrix definition shows the same consolidation pattern as e2e_service_test. Since this job tests database changes with shared table modes (V1 and V2), it's particularly important that both Legacy and TablesDB modes are adequately covered within the unified "Databases" test path.

The consistency of this change across both matrices suggests it was intentional, but verification remains important to ensure no regression in coverage.


166-188: No coverage reduction risk from single "Databases" matrix entry.

The test structure properly supports testing both Legacy and TablesDB modes through PHPUnit's auto-discovery. The tests/e2e/Services/Databases/ directory contains separate test classes for each mode (LegacyConsoleClientTest, TablesDBConsoleClientTest, etc.), each using the appropriate trait (ApiLegacy or ApiTablesDB). PHPUnit discovers and runs all classes within the directory when a single "Databases" matrix entry is used. Both modes are exercised within this single job, not reduced. The e2e_shared_mode_test job provides additional coverage through the tables-mode matrix dimension for shared table configurations.

tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (7)

256-328: Good coverage of document-level permissions with row security enabled.

The setup clearly isolates collection-level vs document-level permissions and validates the expected allow behavior.


334-408: Good coverage of document-level delete permissions with row security enabled.

This complements the update case well and validates allow behavior at document scope.


484-548: Nice: positive-case staging validates expected operation count.

This is a good counterpart to the denial tests and asserts both status and a key response field.


792-833: Good user-isolation behavior check (404 for other user).

This validates the “don’t leak existence” behavior and also confirms the owner can still read.


903-958: Good coverage for commit/rollback authorization on чужие transactions.

Verifies both mutation paths are blocked and owner can still commit.


963-1011: Good delete authorization coverage; also validates owner delete path.

Looks solid for user isolation + correct deletion status code.


1162-1220: Solid CRUD coverage for user-owned transactions.

Creates, reads, deletes, and validates 404 after deletion; plus repeats with a second transaction.

tests/e2e/Scopes/ApiLegacy.php (1)

5-85: LGTM! Clean trait implementation for Legacy API configuration.

The trait provides a consistent set of accessor methods that mirror the ApiTablesDB counterpart. The naming is clear, and the return values correctly map to the Legacy database API's resource naming conventions (collections, attributes, documents, etc.).

tests/e2e/Services/Databases/Transactions/ACIDBase.php (3)

12-14: Good trait composition with SchemaPolling.

The trait correctly composes SchemaPolling and uses its methods (waitForAllAttributes, waitForIndex) to replace sleep-based waits with polling-based synchronization. This makes tests more reliable and potentially faster.


18-170: Well-structured atomicity test.

The testAtomicity test properly validates that when one operation in a transaction fails (due to unique constraint violation), none of the operations are committed. The test correctly handles both success and failure paths with appropriate assertions.


306-458: Isolation test correctly validates concurrent increment behavior.

The test creates two independent transactions that increment a shared counter atomically. Both transactions succeed as expected (0 + 10 + 5 = 15) because increment operations use atomic SQL and are exempt from timestamp-based write-write conflict detection—by design, they are commutative and safe to execute concurrently. This properly tests the isolation guarantees for atomic operations.

tests/e2e/Scopes/ApiTablesDB.php (1)

5-85: LGTM! Symmetric implementation to ApiLegacy.

The trait provides a consistent set of accessor methods that correctly mirror ApiLegacy with TablesDB-specific values (tables, columns, rows, etc.). The symmetric design allows test code to be reused across both API types through trait composition.

tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (1)

46-123: User and team management methods are well-structured.

The createUser, getCreatedUser, createTeam, addToTeam, and getServerHeader methods provide clean abstractions for test setup. The user session handling and team membership creation follow proper API patterns.

tests/e2e/Scopes/SchemaPolling.php (1)

7-14: Good use of polling pattern to replace sleep-based waits.

The trait correctly documents its purpose and uses assertEventually for non-blocking waits. This is a cleaner approach than arbitrary sleep calls. All dependencies are properly satisfied: assertEventually is available via the Async trait used by the Scope base class; helper methods (getSchemaUrl, getContainerUrl, getIndexUrl) are provided by DatabasesUrlHelpers used by composing traits; and getSchemaResource is implemented by the scope helper classes (e.g., ApiTablesDB) used by concrete test classes.

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 (1)
.github/workflows/tests.yml (1)

215-229: Make paratest --processes count configurable via environment variable.
The process count is currently hard-coded to 4 across both test jobs (lines 228 and 319). While this matches typical ubuntu-latest resources, making it configurable would allow dynamic adjustment based on runner specifications or enable testing with different parallelism levels. Export the value as an environment variable (e.g., PARATEST_PROCESSES=4) to support future flexibility.

🤖 Fix all issues with AI agents
In @tests/extensions/Retryable.php:
- Around line 19-34: The repository lacks an executor for the Retry attribute:
implement a RetryExtension (or similarly named test runner hook) that locates
tests using the Retryable trait and their getNumberOfRetries() method and wraps
test invocation with retry semantics; specifically, create a RetryExtension that
hooks into the test lifecycle (e.g., before/around test execution), calls
$test->getNumberOfRetries() on Retryable tests, retries the test up to that
count on failure, and logs or rethrows the final failure—alternatively, if
retries are intentionally unsupported, add a comment in Retryable.php and remove
or mark getNumberOfRetries() unused so the code and tests reflect the intended
design.
🧹 Nitpick comments (3)
tests/e2e/Client.php (1)

212-213: Make the 120s cURL timeout configurable (avoid long hangs in CI/local).
Hard-coding 120s can mask regressions and slow failure feedback; consider an env override with a sane default.

Proposed tweak
-        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
-        curl_setopt($ch, CURLOPT_TIMEOUT, 120);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
+        $timeout = (int)($_ENV['_APP_E2E_HTTP_TIMEOUT'] ?? \getenv('_APP_E2E_HTTP_TIMEOUT') ?: 120);
+        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
tests/e2e/Scopes/SchemaPolling.php (1)

24-33: DRY: extract common “project auth headers” builder (and drop no-op array_merge).
The repeated header block is identical across all methods; a small helper will reduce churn and mistakes.

Sketch
 trait SchemaPolling
 {
+    private function getProjectAuthHeaders(): array
+    {
+        return [
+            'content-type' => 'application/json',
+            'x-appwrite-project' => $this->getProject()['$id'],
+            'x-appwrite-key' => $this->getProject()['apiKey'],
+        ];
+    }
@@
-                array_merge([
-                    'content-type' => 'application/json',
-                    'x-appwrite-project' => $this->getProject()['$id'],
-                    'x-appwrite-key' => $this->getProject()['apiKey']
-                ])
+                $this->getProjectAuthHeaders()
             );

Also applies to: 51-60, 90-99, 127-136, 153-162, 182-191

tests/extensions/TestFinishedSubscriber.php (1)

15-29: Implementation is correct.

The notify method correctly tracks and reports test completion times, with appropriate unit conversions (seconds to milliseconds) and threshold checks.

Optional observation: The completion message prints for every test, which may produce verbose output in large test suites. This might be intentional for debugging, but consider whether this level of output is necessary in all environments.

📜 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 f797ae3 and 1a22c28.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .github/workflows/tests.yml
  • composer.json
  • phpunit.xml
  • tests/e2e/Client.php
  • tests/e2e/Scopes/SchemaPolling.php
  • tests/extensions/Retryable.php
  • tests/extensions/TestFinishedSubscriber.php
  • tests/extensions/TestHook.php
🧰 Additional context used
🧬 Code graph analysis (1)
tests/extensions/TestHook.php (1)
tests/extensions/TestFinishedSubscriber.php (1)
  • TestFinishedSubscriber (8-30)
🪛 PHPMD (2.15.0)
tests/extensions/TestHook.php

14-14: Avoid unused parameters such as '$configuration'. (undefined)

(UnusedFormalParameter)


14-14: Avoid unused parameters such as '$parameters'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan
🔇 Additional comments (8)
tests/extensions/Retryable.php (1)

6-10: LGTM! Clear documentation of the delegated retry mechanism.

The docblock accurately describes the new architecture where retry logic is handled by RetryExtension, and clearly documents the attribute usage pattern.

.github/workflows/tests.yml (1)

166-188: LGTM on consolidating Databases into a single service entry.
Matrix simplification should reduce CI complexity while keeping coverage in one lane.

tests/e2e/Scopes/SchemaPolling.php (1)

22-38: Good use of assertEventually for async readiness checks (attribute/index).
This should reduce test flakiness vs sleep().

  • Please confirm assertEventually() retries on assertion failures and on transient call/parse failures (or explicitly document what it catches), otherwise a single intermittent failure could still abort polling.

Also applies to: 125-141

phpunit.xml (1)

1-13: PHPUnit 10 configuration is valid. The <extensions><bootstrap class="Appwrite\Tests\TestHook" /> element is correctly implemented. TestHook properly implements the PHPUnit\Runner\Extension\Extension interface with the correct bootstrap() method signature, and phpunit.xml validates successfully against the schema. No changes needed.

composer.json (1)

90-100: ParaTest 7.4.x is compatible with PHPUnit 10.x and meets the PHP 8.2–8.3 requirement for this environment. No configuration concerns.

tests/extensions/TestFinishedSubscriber.php (1)

10-13: LGTM!

The constructor uses readonly property promotion effectively and clearly defines the threshold parameter.

tests/extensions/TestHook.php (2)

10-12: LGTM!

The migration to the Extension interface aligns with PHPUnit 10's architecture, and the 15-second threshold for slow tests is reasonable for e2e tests.


14-17: LGTM!

The bootstrap method correctly registers the TestFinishedSubscriber with the facade. The unused parameter warnings from PHPMD are false positives—the Extension interface requires these parameters in the signature, even when specific implementations don't need them.

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: 3

🤖 Fix all issues with AI agents
In @src/Appwrite/Messaging/Adapter/Realtime.php:
- Around line 41-48: The getPubSubPool method currently depends on the global
$register and assumes $register->get('pools')->get('pubsub') exists; replace
this by accepting a PubSubPool (or a pools factory/service) via constructor or a
setter to remove the global dependency, and add defensive checks in
getPubSubPool: verify the pools service and the 'pubsub' config are non-null and
of expected type before constructing PubSubPool, and throw a descriptive
exception (e.g., InvalidArgumentException or RuntimeException) if missing or
invalid so failures occur early and are testable.

In @tests/e2e/Scopes/Scope.php:
- Around line 157-160: getLastRequest() assumes getProject() returns an array
containing '$id' and will raise an undefined index notice if absent; update
getLastRequest() to retrieve $project = $this->getProject(), verify
isset($project['$id']) (or array_key_exists) before calling
$this->getLastRequestForProject($project['$id']), and if missing either return a
safe default (e.g., empty array) or throw a clear exception indicating the
missing project id so the real failure isn't masked.
🧹 Nitpick comments (5)
tests/unit/Migration/MigrationVersionsTest.php (3)

15-15: Avoid require_once app/init.php inside the test (prefer PHPUnit bootstrap / setUpBeforeClass).

If phpunit.xml already bootstraps the app, re-requiring init here can add hidden side-effects and make ordering dependencies harder to spot. Consider moving this to PHPUnit’s bootstrap or a setUpBeforeClass() guarded include.


21-25: Make RC detection/constant assumptions more robust.

str_contains(APP_VERSION_STABLE, 'RC') is case-sensitive and assumes APP_VERSION_STABLE is always defined by the time the test runs. If version formatting ever changes (e.g., -rc.1), this can become flaky. Consider guarding defined('APP_VERSION_STABLE') and using a case-insensitive check.

Proposed change
-        if (!(\str_contains(APP_VERSION_STABLE, 'RC'))) {
-            $this->assertArrayHasKey(APP_VERSION_STABLE, Migration::$versions);
-        }
+        if (\defined('APP_VERSION_STABLE') && \stripos(APP_VERSION_STABLE, 'rc') === false) {
+            $this->assertArrayHasKey(APP_VERSION_STABLE, Migration::$versions);
+        }

17-19: Add assertion message and build FQCN explicitly for better test diagnostics.

The test currently concatenates the hardcoded namespace prefix without providing an assertion message. When a migration class is missing, the failure won't indicate which class, making debugging harder. Also, explicitly building the FQCN avoids string duplication and allows for handling both short names and full FQCNs defensively.

Proposed change
-        foreach (Migration::$versions as $class) {
-            $this->assertTrue(class_exists('Appwrite\\Migration\\Version\\' . $class));
-        }
+        foreach (Migration::$versions as $class) {
+            $fqcn = \str_starts_with($class, 'Appwrite\\Migration\\Version\\')
+                ? $class
+                : 'Appwrite\\Migration\\Version\\' . $class;
+
+            $this->assertTrue(
+                \class_exists($fqcn),
+                "Missing migration version class: {$fqcn}"
+            );
+        }
tests/e2e/Scopes/Scope.php (2)

62-87: Harden MailDev fetch to avoid flaky warnings and reduce race risk.

file_get_contents() can return false and json_decode() can return null, which will emit warnings / cause odd downstream failures under parallel load. Also, a fixed sleep(3) is still race-prone: sometimes the email arrives later, sometimes sooner (wasted time).

Proposed patch (defensive fetch + simple polling)
 protected function getLastEmailByAddress(string $address): array
 {
-    sleep(3);
-
-    $emails = json_decode(file_get_contents('http://maildev:1080/email'), true);
+    $maxAttempts = 10;
+    $delayMs = 300;
+
+    for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
+        $raw = @file_get_contents('http://maildev:1080/email');
+        if ($raw === false) {
+            usleep($delayMs * 1000);
+            continue;
+        }
+
+        $emails = json_decode($raw, true);
 
-    if ($emails && is_array($emails)) {
-        // Search from the end (most recent) to the beginning
-        for ($i = count($emails) - 1; $i >= 0; $i--) {
-            $email = $emails[$i];
-            if (isset($email['to']) && is_array($email['to'])) {
-                foreach ($email['to'] as $recipient) {
-                    if (isset($recipient['address']) && $recipient['address'] === $address) {
-                        return $email;
-                    }
-                }
-            }
-        }
-    }
+        if (is_array($emails)) {
+            // Search from the end (most recent) to the beginning
+            for ($i = count($emails) - 1; $i >= 0; $i--) {
+                $email = $emails[$i];
+                if (isset($email['to']) && is_array($email['to'])) {
+                    foreach ($email['to'] as $recipient) {
+                        if (($recipient['address'] ?? null) === $address) {
+                            return $email;
+                        }
+                    }
+                }
+            }
+        }
+
+        usleep($delayMs * 1000);
+    }
 
     return [];
 }

162-191: The header casing and value handling concerns are not substantiated by the actual request-catcher behavior.

The existing webhook tests throughout the codebase (WebhooksCustomClientTest.php, WebhooksCustomServerTest.php, etc.) consistently access headers using exact casing—e.g., $webhook['headers']['X-Appwrite-Webhook-Project-Id']—and these tests pass. The request-catcher service (appwrite/requestcatcher:1.0.0) is a simple HTTP sink that preserves header names and values exactly as received; it does not normalize casing or convert header values to arrays. Header values in the current implementation are strings.

The proposed defensive programming improvements (case-insensitive matching, array handling) would add robustness but are not addressing actual issues with the current implementation. Consider these enhancements as optional refactoring if you want to harden the code against hypothetical future changes to request-catcher's behavior.

📜 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 1a22c28 and 879a599.

📒 Files selected for processing (10)
  • src/Appwrite/Messaging/Adapter/Realtime.php
  • tests/e2e/Scopes/Scope.php
  • tests/e2e/Services/Account/AccountBase.php
  • tests/e2e/Services/Account/AccountConsoleClientTest.php
  • tests/e2e/Services/Account/AccountCustomClientTest.php
  • tests/e2e/Services/Account/AccountCustomServerTest.php
  • tests/e2e/Services/Teams/TeamsBaseClient.php
  • tests/e2e/Services/Teams/TeamsCustomClientTest.php
  • tests/unit/Migration/MigrationTest.php
  • tests/unit/Migration/MigrationVersionsTest.php
💤 Files with no reviewable changes (1)
  • tests/unit/Migration/MigrationTest.php
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-16T06:37:59.054Z
Learnt from: hmacr
Repo: appwrite/appwrite PR: 10501
File: app/config/console.php:54-75
Timestamp: 2025-09-16T06:37:59.054Z
Learning: The Console project in Appwrite does not currently have email OTP flows implemented. Adding OTP session email templates to the Console configuration would cause test failures since the authentication flow is not defined for Console at this point.

Applied to files:

  • tests/e2e/Services/Account/AccountConsoleClientTest.php
📚 Learning: 2025-08-22T06:23:32.026Z
Learnt from: ItzNotABug
Repo: appwrite/appwrite PR: 10348
File: app/controllers/shared/api.php:546-546
Timestamp: 2025-08-22T06:23:32.026Z
Learning: In Appwrite's codebase, Utopia\Queue\Broker\Pool (aliased as BrokerPool) is a readonly class that implements both Publisher and Consumer interfaces, allowing it to be used directly in constructors expecting either interface type. This is extensively used throughout the codebase for queue operations.

Applied to files:

  • src/Appwrite/Messaging/Adapter/Realtime.php
📚 Learning: 2025-08-22T06:23:32.026Z
Learnt from: ItzNotABug
Repo: appwrite/appwrite PR: 10348
File: app/controllers/shared/api.php:546-546
Timestamp: 2025-08-22T06:23:32.026Z
Learning: The Utopia\Queue\Broker\Pool class (aliased as BrokerPool) implements both Publisher and Consumer interfaces, making it directly compatible with constructors expecting Publisher parameters like Func::__construct(protected Publisher $publisher). No wrapping is needed.

Applied to files:

  • src/Appwrite/Messaging/Adapter/Realtime.php
📚 Learning: 2025-08-22T06:23:32.026Z
Learnt from: ItzNotABug
Repo: appwrite/appwrite PR: 10348
File: app/controllers/shared/api.php:546-546
Timestamp: 2025-08-22T06:23:32.026Z
Learning: In the Utopia Queue system, the Pool class implements both Publisher and Consumer interfaces, so BrokerPool (Pool) can be passed directly to constructors expecting Publisher parameters like Func::__construct(protected Publisher $publisher).

Applied to files:

  • src/Appwrite/Messaging/Adapter/Realtime.php
🧬 Code graph analysis (8)
tests/unit/Migration/MigrationVersionsTest.php (1)
src/Appwrite/Platform/Appwrite.php (1)
  • Appwrite (17-32)
tests/e2e/Services/Account/AccountBase.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
tests/e2e/Services/Account/AccountConsoleClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
tests/e2e/Scopes/Scope.php (1)
src/Appwrite/Event/Event.php (1)
  • getProject (156-159)
tests/e2e/Services/Account/AccountCustomServerTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
tests/e2e/Services/Teams/TeamsBaseClient.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
tests/e2e/Services/Teams/TeamsCustomClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
tests/e2e/Services/Account/AccountCustomClientTest.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: E2E Service Test (Proxy)
  • GitHub Check: E2E Service Test (Databases)
  • GitHub Check: E2E Service Test (Webhooks)
  • GitHub Check: E2E Service Test (Messaging)
  • GitHub Check: E2E Service Test (Locale)
  • GitHub Check: E2E Service Test (GraphQL)
  • GitHub Check: E2E Service Test (Account)
  • GitHub Check: E2E Service Test (Console)
  • GitHub Check: E2E Service Test (Users)
  • GitHub Check: E2E Service Test (Sites)
  • GitHub Check: E2E Service Test (Tokens)
  • GitHub Check: E2E Service Test (VCS)
  • GitHub Check: E2E Service Test (Functions)
  • GitHub Check: E2E Service Test (Projects)
  • GitHub Check: E2E Service Test (FunctionsSchedule)
  • GitHub Check: Unit Test
  • GitHub Check: E2E General Test
  • GitHub Check: E2E Service Test (Abuse enabled)
  • GitHub Check: Benchmark
  • GitHub Check: scan
🔇 Additional comments (6)
src/Appwrite/Messaging/Adapter/Realtime.php (1)

154-165: Lazy initialization pattern is correct and safe.

The change properly implements lazy initialization—the PubSubPool is only created on first send() call and cached thereafter. The early return guard (lines 147-149) ensures the pool is never instantiated for no-op calls when channels, roles, or projectId are empty.

This maintains identical functionality while improving performance for Realtime adapter instances that are instantiated but never used to publish messages. The parallel test execution concern is not applicable here since paratest uses process isolation (separate PHP instances), not threading, so each test process has its own isolated $register and $pubSubPool instance.

tests/e2e/Services/Account/AccountCustomClientTest.php (1)

943-946: Good change: address-scoped email lookup reduces cross-test interference; watch for shared OTP addresses.

The assertNotEmpty($lastEmail, ...) gate is a solid improvement before dereferencing ['html']/['text']. Only remaining flake vector is places that use fixed recipient addresses (e.g., otpuser2@appwrite.io) across multiple suites: address-scoping won’t help if multiple parallel tests send to the same address—consider making those addresses unique per test where possible (or additionally filter by subject/body marker).

Also applies to: 1247-1250, 1430-1433, 1460-1463, 1487-1489, 3011-3013, 3082-3085

tests/e2e/Services/Account/AccountCustomServerTest.php (1)

217-219: LGTM: address-scoped email lookup for magic-url.

This reduces false positives when tests run in parallel.

tests/e2e/Services/Teams/TeamsBaseClient.php (1)

221-224: LGTM: invitation emails fetched by recipient address + presence guard.

This should reduce parallel flakes vs “global last email”.

Also applies to: 288-291, 311-313, 608-610

tests/e2e/Services/Account/AccountConsoleClientTest.php (1)

139-142: Please re-validate Console email behaviors (session alert + OTP) end-to-end.

The address-scoped lookup is good, but the test still assumes Console will emit OTP emails (/account/tokens/email) and session-alert emails. Based on learnings, Console OTP flows historically weren’t implemented—so this might still be flaky/failing depending on current Console behavior.

Also applies to: 169-172, 195-197

tests/e2e/Services/Account/AccountBase.php (1)

166-169: Remove Console OTP concern; parallel-collision risk with shared otpuser@appwrite.io persists.

The Console project does support OTP email flows (confirmed in app/config/locale/templates.php which includes otpSession), so that part of the concern is resolved. However, the parallel-execution risk remains: using a hardcoded shared email address otpuser@appwrite.io across the trait (lines 166-169, 245-247) can still cause collisions when AccountBase is used by multiple test classes (AccountConsoleClientTest, AccountCustomClientTest, AccountCustomServerTest) running in parallel. While getLastEmailByAddress() is an improvement, the shared address itself is the bottleneck. Consider using unique, test-scoped addresses or adding test isolation.

Also applies to: 245-247

⛔ Skipped due to learnings
Learnt from: hmacr
Repo: appwrite/appwrite PR: 10501
File: app/config/console.php:54-75
Timestamp: 2025-09-16T06:37:59.054Z
Learning: The Console project in Appwrite does not currently have email OTP flows implemented. Adding OTP session email templates to the Console configuration would cause test failures since the authentication flow is not defined for Console at this point.

# Conflicts:
#	composer.lock
#	tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php
#	tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php
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: 0

🧹 Nitpick comments (1)
tests/e2e/Services/Sites/SitesCustomServerTest.php (1)

2712-2725: Dynamic assertions reduce test specificity for the canceled deployment scenario.

At line 2691, the test explicitly verifies the deployment status is 'canceled'. Re-fetching and dynamically matching the status here could mask regressions — if a bug causes the status to unexpectedly change, the test would still pass with different assertions rather than failing.

Since this section tests the canceled deployment error page, consider asserting the expected messages directly:

Proposed fix
-        $deployment = $this->getDeployment($siteId, $deploymentId);
-        $status = $deployment['body']['status'] ?? '';
-        $expectedMessage = match ($status) {
-            'failed' => 'Deployment build failed',
-            'canceled' => 'Deployment build canceled',
-            default => 'Deployment is still building',
-        };
-        $this->assertStringContainsString($expectedMessage, $response['body']);
-        $expectedCta = match ($status) {
-            'failed' => 'View logs',
-            'canceled' => 'View deployments',
-            default => 'Reload',
-        };
-        $this->assertStringContainsString($expectedCta, $response['body']);
+        $this->assertStringContainsString('Deployment build canceled', $response['body']);
+        $this->assertStringContainsString('View deployments', $response['body']);
📜 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 5153617 and bb09abc.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • tests/e2e/Services/Sites/SitesCustomServerTest.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: scan

# Conflicts:
#	composer.lock
#	tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php
#	tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php
# Conflicts:
#	composer.lock
#	tests/e2e/Services/Databases/Legacy/Permissions/DatabasesPermissionsGuestTest.php
#	tests/e2e/Services/Databases/TablesDB/Permissions/DatabasesPermissionsGuestTest.php
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: 1

📜 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 0f953e4 and d7a6a7b.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • composer.json
🧰 Additional context used
🪛 GitHub Actions: Linter
composer.json

[error] 1-1: Composer.json is valid, but the lock file has some errors. Please run 'composer update' to resolve the mismatch.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 8

Caution

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

⚠️ Outside diff range comments (24)
tests/e2e/Services/GraphQL/TeamsClientTest.php (2)

136-157: Missing #[Depends] attributes will cause test failure.

The @depends annotations for testCreateTeam and testCreateTeamMembership were removed but not replaced with PHP 8 attributes. This method requires both $team and $membership parameters, which PHPUnit will not provide without the dependency declarations.

🐛 Proposed fix
-    /**
-     */
+    #[Depends('testCreateTeam')]
+    #[Depends('testCreateTeamMembership')]
     public function testGetTeamMembership($team, $membership)

159-180: Missing #[Depends] attributes will cause test failure.

Same issue as testGetTeamMembership. The @depends annotations were removed without adding the equivalent PHP 8 attributes. Both $team and $membership parameters are required but will not be provided.

🐛 Proposed fix
-    /**
-     */
+    #[Depends('testCreateTeam')]
+    #[Depends('testCreateTeamMembership')]
     public function testDeleteTeamMembership($team, $membership)
tests/e2e/Services/GraphQL/TeamsServerTest.php (3)

188-210: Missing #[Depends] attributes cause test failure.

This method expects $team and $membership parameters but has no dependency attributes. PHPUnit 11 will not know where to source these values, causing the test to fail at runtime.

The empty docblock suggests the migration from @depends annotations is incomplete.

🐛 Proposed fix
-    /**
-     */
+    #[Depends('testCreateTeam')]
+    #[Depends('testCreateTeamMembership')]
     public function testGetTeamMembership($team, $membership)

235-259: Missing #[Depends] attributes cause test failure.

Same incomplete migration as above - method requires $team and $membership but has no dependency attributes.

🐛 Proposed fix
-    /**
-     */
+    #[Depends('testCreateTeam')]
+    #[Depends('testCreateTeamMembership')]
     public function testUpdateTeamMembershipRoles($team, $membership)

261-282: Missing #[Depends] attributes cause test failure.

Same issue - method requires $team and $membership parameters but has no dependency attributes.

🐛 Proposed fix
-    /**
-     */
+    #[Depends('testCreateTeam')]
+    #[Depends('testCreateTeamMembership')]
     public function testDeleteTeamMembership($team, $membership)
tests/e2e/Services/GraphQL/StorageClientTest.php (1)

91-286: Missing #[Depends] attributes will break test execution.

The @depends annotations were removed from these test methods, but the corresponding #[Depends] attributes were not added. These methods have parameters expecting data from predecessor tests:

Method Parameter(s) Likely Dependencies
testGetFiles $bucket testCreateBucket
testGetFile $bucket, $file testCreateBucket, testCreateFile
testGetFilePreview $file testCreateFile
testGetFileDownload $file testCreateFile
testGetFileView $file testCreateFile
testUpdateFile $file testCreateFile
testDeleteFile $file testUpdateFile

Without the #[Depends] attributes, PHPUnit 11 will invoke these methods without passing the required data, causing test failures.

Proposed fix (example for a few methods)
+    #[Depends('testCreateBucket')]
     public function testGetFiles($bucket): array
     {
+    #[Depends('testCreateBucket')]
+    #[Depends('testCreateFile')]
     public function testGetFile($bucket, $file)
     {
+    #[Depends('testCreateFile')]
     public function testGetFilePreview($file)
     {

Apply the same pattern for testGetFileDownload, testGetFileView, testUpdateFile, and testDeleteFile, ensuring each has the appropriate #[Depends] attributes matching the original dependency chain.

tests/e2e/Services/Webhooks/WebhooksBase.php (2)

317-320: Missing #[Depends] attribute for testDeleteDocument.

This method expects array $data containing actorsId and databaseId from the previous test, but the dependency attribute is missing. Without it, PHPUnit 11 won't pass the required data, causing the test to fail or be skipped.

🐛 Proposed fix
 /**
  */
+#[Depends('testUpdateDocument')]
 #[Retry(count: 1)]
 public function testDeleteDocument(array $data): array

661-664: Missing #[Depends] attribute for testDeleteRow.

Same issue as testDeleteDocument - this method expects array $data containing actorsId and databaseId but lacks the dependency attribute.

🐛 Proposed fix
 /**
  */
+#[Depends('testUpdateRow')]
 #[Retry(count: 1)]
 public function testDeleteRow(array $data): array
tests/e2e/Services/GraphQL/TablesDB/DatabaseClientTest.php (5)

145-147: Missing #[Depends] attribute will cause test failure.

testCreateRow accepts a $data parameter and accesses $data['database']['_id'] and $data['table']['_id'], but lacks the required #[Depends] attribute. Without it, PHPUnit 11 will not pass the return value from the prerequisite test, causing $data to be null and the test to fail.

The AI summary indicates this method should have #[Depends('testCreateTable')], but the attribute is missing from the code.

Proposed fix
-    /**
-     */
+    #[Depends('testCreateIntegerColumn')]
     public function testCreateRow($data): array

Note: Should depend on testCreateIntegerColumn (or testCreateStringColumn) to ensure columns exist before creating rows.


189-192: Missing #[Depends] attribute will cause test failure.

testGetRows takes $data parameter and accesses $data['database']['_id'] and $data['table']['_id'], but has no #[Depends] attribute.

Proposed fix
     /**
      * `@throws` \Exception
      */
+    #[Depends('testCreateRow')]
     public function testGetRows($data): void

214-217: Missing #[Depends] attribute will cause test failure.

testGetDocument accesses $data['row']['_id'] but lacks dependency declaration.

Proposed fix
     /**
      * `@throws` \Exception
      */
+    #[Depends('testCreateRow')]
     public function testGetDocument($data): void

240-243: Missing #[Depends] attribute will cause test failure.

testUpdateRow accesses $data['row']['_id'] but lacks dependency declaration.

Proposed fix
     /**
      * `@throws` \Exception
      */
+    #[Depends('testCreateRow')]
     public function testUpdateRow($data): void

272-275: Missing #[Depends] attribute will cause test failure.

testDeleteRow accesses $data['row']['_id'] but lacks dependency declaration.

Proposed fix
     /**
      * `@throws` \Exception
      */
+    #[Depends('testCreateRow')]
     public function testDeleteRow($data): void
tests/e2e/Services/Messaging/MessagingBase.php (2)

1426-1434: Undefined variable $from will cause test to always skip.

The variable $from on line 1432 is never defined in this method. Since undefined variables evaluate to null (and thus empty()), this condition will always trigger markTestSkipped() even when the SMS DSN is properly configured.

This appears to be a leftover from refactoring. The $from check should likely be removed since $senderId serves as the sender identifier for SMS providers.

🐛 Proposed fix
-        if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) {
+        if (empty($to) || empty($senderId) || empty($authKey)) {
             $this->markTestSkipped('SMS provider not configured');
         }

1742-1750: Missing #[Depends] attribute will cause test failure.

The testDeleteMessage method expects $params with message and topic keys, which matches the return value of testSendEmail. However, the #[Depends('testSendEmail')] attribute is missing, so PHPUnit won't inject the dependency data and $params will be empty/null at runtime.

🐛 Proposed fix
+    #[Depends('testSendEmail')]
     /**
      * `@return` void
      * `@throws` \Exception
      */
     public function testDeleteMessage(array $params): void
tests/e2e/Services/Functions/FunctionsCustomServerTest.php (2)

710-715: Missing #[Depends] attribute causes test failure.

The method testCancelDeploymentBuild expects $data containing functionId, but lacks a #[Depends] attribute. Without it, PHPUnit 11 won't inject the dependent data, causing a runtime error when accessing $data['functionId'].

🐛 Proposed fix
     /**
      */
     #[Retry(count: 3)]
+    #[Depends('testCreateDeployment')]
     public function testCancelDeploymentBuild($data): void
     {
         $functionId = $data['functionId'];

2150-2152: Redundant double assignment.

There's a typo with duplicate assignment that should be cleaned up.

✏️ Suggested fix
-        $functionId = $functionId = $function['body']['$id'] ?? '';
+        $functionId = $function['body']['$id'] ?? '';
tests/e2e/Services/Storage/StorageCustomClientTest.php (1)

420-436: Fix CLient::METHOD_PUT typo (fatal error).

CLient::METHOD_PUT will error (undefined class). Should be Client::METHOD_PUT.

Proposed diff
-        $this->client->call(CLient::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
+        $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], [
             'name' => 'permissions.png',
         ]);
tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php (1)

409-412: Missing #[Depends] attribute on testUpdateAccountName.

This method accepts a $data parameter (containing id, email, session), indicating it relies on data from a prior test. However, no #[Depends(...)] attribute is present. In PHPUnit 11, this will result in $data being uninitialized, causing test failures.

Based on the dependency chain, this method should depend on testDeleteAccountSessions which returns the session data.

🐛 Proposed fix to add the missing dependency
 /**
  */
 #[Retry(count: 1)]
+#[Depends('testDeleteAccountSessions')]
 public function testUpdateAccountName($data): array
tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php (1)

144-146: Add missing #[Depends] attribute to testCreateDocument.

The method takes $data as a parameter but has no #[Depends] attribute. It should depend on either testCreateStringAttribute or testCreateIntegerAttribute (both return the same structure). Without this, the test will fail in PHPUnit 11 since the parameter won't be populated by the test framework.

#[Depends('testCreateStringAttribute')]
public function testCreateDocument($data): array
tests/e2e/Services/Account/AccountCustomClientTest.php (3)

482-486: Missing #[Depends] attribute on testUpdateAccountPassword.

The method signature testUpdateAccountPassword($data): array expects $data parameter from a previous test, but there's no #[Depends] attribute. Based on the test flow and the fact that it uses $data['email'], $data['password'], and $data['session'], this should depend on testCreateAccountSession.

Proposed fix
-    /**
-     */
+    #[Depends('testCreateAccountSession')]
     #[Retry(count: 1)]
     public function testUpdateAccountPassword($data): array

1288-1291: Missing #[Depends] attribute on testUpdateAccountRecovery.

The method expects $data containing 'id' and 'recovery' keys which are returned from testCreateAccountRecovery. The #[Depends] attribute is missing.

Proposed fix
-    /**
-     */
+    #[Depends('testCreateAccountRecovery')]
     #[Retry(count: 1)]
     public function testUpdateAccountRecovery($data): array

2709-2711: Missing #[Depends] attribute on testCreateSession.

The method signature testCreateSession(array $data): array expects data containing 'id' from a previous test (likely from the phone authentication chain), but there's no #[Depends] attribute.

Proposed fix
-    /**
-     */
+    #[Depends('testUpdatePhone')]
     public function testCreateSession(array $data): array
tests/unit/Utopia/Response/Filters/V17Test.php (1)

160-173: Remove unused webhookProvider() method or add the corresponding test method.

The static provider webhookProvider() at line 160 is defined but never referenced by any test method. This indicates either dead code or an incomplete implementation from the PHPUnit 11 migration.

🤖 Fix all issues with AI agents
In `@tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php`:
- Around line 322-331: The schema POST call using $this->client->call(...,
$this->getSchemaUrl($databaseId, $moviesId, 'string'), $this->getServerHeader(),
...) is missing any verification; capture the response returned by that call
(e.g. $response = $this->client->call(...)) and add assertions similar to the
earlier createCollection test: assert the expected HTTP status (created/201 or
appropriate), assert the response body contains the created schema key/name
('key' => 'title') and any expected fields (size, required) or an id, so the
test verifies the schema was actually created.
- Around line 66-85: The two Client::call() invocations that create schemas in
TablesDBPermissionsGuestTest.php are not checked; capture each call's response
(for the public and private collection schema creations), assert the response
indicates success (e.g., HTTP 201/200 or SDK success flag) and assert the
response body contains the expected schema attributes (key 'title', size 256,
required true) so failures surface immediately; reference the existing calls to
Client::METHOD_POST with getSchemaUrl($databaseId, $publicCollection['id'],
'string') and getSchemaUrl($databaseId, $privateCollection['id'], 'string') and
add assertions after each call validating status and returned schema fields.

In `@tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php`:
- Around line 56-72: Add an assertion that the $collection1 response from
$this->client->call succeeded before using its body: verify the HTTP status is
201 (created) and that $collection1['body']['$id'] exists (mirroring the
validation used for $db creation) so the subsequent assignment
$this->collections['collection1'] = $collection1['body']['$id'] is safe.
- Around line 85-101: The creation response for $collection2 is not being
asserted; after the Client::METHOD_POST call that sets $collection2, add the
same status-code assertion used for collection1 (e.g. $this->assertEquals(201,
$collection2['headers']['status-code']) or the exact assertion style used for
$collection1) before assigning $this->collections['collection2'] =
$collection2['body']['$id']; to ensure the POST succeeded.

In `@tests/e2e/Services/Projects/ProjectsConsoleClientTest.php`:
- Around line 228-230: Several test methods (e.g., testListProject) accept a
$data parameter but lack the PHPUnit dependency attribute, causing $data to be
undefined at runtime; add the attribute #[Depends('testCreateProject')] above
each test method that declares a $data parameter (including testListProject and
the other methods referenced) so PHPUnit injects the result from
testCreateProject, and ensure the attribute is applied to the methods that use
$data['projectId'] (match by method names like testListProject, and the other
methods in the ranges noted).

In `@tests/e2e/Services/Users/UsersBase.php`:
- Around line 1164-1166: The tests use the #[Retry] attribute (e.g., on
testUpdateUserStatus) and the Retryable trait but there is no PHPUnit extension
to perform retries, so implement a RetryExtension that inspects the Retry
attribute (via Retryable::getNumberOfRetries()) and re-schedules failed tests,
register that extension alongside TestHook in the test bootstrap/extension
registration, or alternatively remove the unused #[Retry] attributes from tests
like testUpdateUserStatus to avoid dead code; ensure the new class is named
RetryExtension and wired into PHPUnit so Retryable::getNumberOfRetries() is
actually invoked when tests fail.
♻️ Duplicate comments (1)
composer.json (1)

90-100: Confirm PHPUnit 11 + ParaTest 7 compatibility and lockfile/config updates.

You’re pinning "phpunit/phpunit": "11.*" and adding "brianium/paratest": "7.*". Please confirm:

  • composer.lock is updated and committed for 1.8.x.
  • phpunit.xml* is compatible with PHPUnit 11.
  • CI/locally, ParaTest executes this suite without breaking #[Depends] chains (especially for e2e tests that pass arrays between methods).
#!/bin/bash
set -euo pipefail

# 1) Ensure lockfile exists and includes the intended packages/versions
if [[ ! -f composer.lock ]]; then
  echo "composer.lock is missing (expected committed change for pinned dev deps)." >&2
  exit 1
fi

echo "== composer.lock: phpunit/phpunit & brianium/paratest entries =="
rg -n '"name":\s*"phpunit/phpunit"|\"name\":\s*\"brianium/paratest\"' -C2 composer.lock || true
rg -n '"version":\s*"v11\.' -C1 composer.lock || true
rg -n '"version":\s*"v7\.' -C1 composer.lock || true

# 2) Quick scan for leftover docblock annotations that PHPUnit 10/11 may not accept
echo "== leftover phpunit docblock annotations (spot check) =="
rg -n --type=php '@depends|@dataProvider|@test\b' tests/ | head -n 50 || true
Does ParaTest 7 support PHPUnit 11 (and does it preserve PHPUnit test method dependencies / #[Depends] execution order under parallel runs)?
🧹 Nitpick comments (12)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php (2)

28-31: Consider a more descriptive database name.

The database name 'InvalidDocumentDatabase' doesn't align with this test's purpose of testing guest permissions. A name like 'GuestPermissionsDatabase' would better convey intent.


87-87: Consider consistent sleep durations or polling mechanism.

The sleep(2) here and sleep(1) at line 333 have inconsistent durations. If schema propagation timing is non-deterministic, consider either:

  1. Using a consistent duration across all schema waits
  2. Implementing a polling helper that waits until the schema is ready
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (2)

16-24: Hardcoded databaseId may cause test isolation issues.

The $databaseId is hardcoded as 'testpermissiondb'. If tests run in parallel (e.g., with paratest as mentioned in the PR summary), this could cause conflicts. Consider using ID::unique() like in TablesDBPermissionsMemberTest.

♻️ Suggested fix
 public array $collections = [];
-public string $databaseId = 'testpermissiondb';
+public string $databaseId = '';

Then in testSetupDatabase():

+$this->databaseId = ID::unique();
 $this->createUsers();
 $this->createTeams();

201-221: Test uses collection name string instead of collection ID - potential bug.

The data provider passes collection names like 'collection1' and 'collection2' as strings (line 207), but getRecordUrl() expects the actual collection ID. However, since createCollections() uses ID::custom('collection1'), the custom ID matches the string name, so this works correctly.

The success path assertion (line 217) checks the count but doesn't verify the status code is 200. Consider adding a status assertion for completeness.

♻️ Suggested improvement
     if ($success) {
+        $this->assertEquals(200, $documents['headers']['status-code']);
         $this->assertCount(1, $documents['body'][$this->getRecordResource()]);
     } else {
tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php (1)

116-117: Pre-existing: Many test methods have undeclared dependencies.

Several test methods like testCreateStringColumn, testGetDatabase, and testUpdateDatabase accept $data or $database parameters but lack #[Depends] attributes. Without explicit dependencies, PHPUnit will call these methods with null arguments, causing failures.

This isn't introduced by this PR, but consider adding the missing dependency attributes for consistency with the PHPUnit 11 migration.

Also applies to: 922-923, 1197-1198

tests/e2e/Services/Teams/TeamsBase.php (1)

5-6: Add parameter types for depended-on data to avoid silent shape bugs.

These methods are fed the return from testCreateTeam(): array, so declaring array $data makes failures clearer.

Proposed diff
 #[Depends('testCreateTeam')]
-    public function testGetTeam($data): array
+    public function testGetTeam(array $data): array
     {

 #[Depends('testCreateTeam')]
-    public function testListTeams($data): array
+    public function testListTeams(array $data): array
     {

 #[Depends('testCreateTeam')]
-    public function testUpdateAndGetUserPrefs(array $data): void
+    public function testUpdateAndGetUserPrefs(array $data): void
     {

Also applies to: 130-132, 159-161, 437-439

.github/workflows/tests.yml (1)

309-317: Consider using consistent process count strategy.

The e2e_service_test uses $(nproc) for dynamic scaling while e2e_shared_mode_test uses a fixed --processes 4. This inconsistency could lead to different behavior across environments.

💡 Suggestion for consistent parallelism

Consider using the same strategy for both jobs, or document why shared mode needs a fixed count:

-            appwrite vendor/bin/paratest --processes 4 "$SERVICE_PATH" --exclude-group abuseEnabled,screenshots
+            appwrite vendor/bin/paratest --processes $(nproc) "$SERVICE_PATH" --exclude-group abuseEnabled,screenshots

Alternatively, if there's a resource constraint reason for the fixed count in shared mode, consider adding a comment explaining it.

tests/e2e/Services/Account/AccountCustomServerTest.php (1)

214-216: Good: email lookup is now recipient-scoped; add a small guard before parsing text.
Right after Line 215, consider asserting text exists to avoid a noisy undefined-index failure if Maildev returns a different payload.

Proposed tweak
         $lastEmail = $this->getLastEmailByAddress($email);
         $this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);
+        $this->assertArrayHasKey('text', $lastEmail);
         $this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']);
tests/e2e/Services/Databases/Permissions/LegacyPermissionsGuestTest.php (2)

108-171: Avoid order-sensitive $permissions assertions in responses.

$permissions may come back in a different order than sent; using canonicalizing comparison prevents flaky failures. Also consider asserting list response status codes explicitly.

Proposed fix (canonicalizing compare + strict in_array)
-        $this->assertEquals($permissions, $publicDocuments['body'][$recordKey][0]['$permissions']);
+        $this->assertEqualsCanonicalizing($permissions, $publicDocuments['body'][$recordKey][0]['$permissions']);

-        if (\in_array(Permission::read(Role::any()), $permissions)) {
+        if (\in_array(Permission::read(Role::any()), $permissions, true)) {
             $this->assertEquals(1, $privateDocuments['body']['total']);
-            $this->assertEquals($permissions, $privateDocuments['body'][$recordKey][0]['$permissions']);
+            $this->assertEqualsCanonicalizing($permissions, $privateDocuments['body'][$recordKey][0]['$permissions']);
         } else {
             $this->assertEquals(0, $privateDocuments['body']['total']);
         }

287-355: Remove redundant array_merge([...]) and assert setup calls in this test too.

This test currently doesn’t assert collection/schema creation responses, and the single-argument array_merge is a no-op.

tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php (1)

43-117: Add response assertions for collection/schema creation (fail fast) and prefer polling over sleep(2).

Right now the test can proceed with partially-created schema/collections and fail later with harder-to-debug errors.

tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php (1)

119-225: Replace sleep(2) with a readiness/polling helper if available.

Fixed sleeps tend to be the top source of e2e flakes (too short) and slow builds (too long).

📜 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 84f9485 and 714b8d7.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (60)
  • .github/workflows/tests.yml
  • composer.json
  • tests/e2e/General/UsageTest.php
  • tests/e2e/Services/Account/AccountBase.php
  • tests/e2e/Services/Account/AccountCustomClientTest.php
  • tests/e2e/Services/Account/AccountCustomServerTest.php
  • tests/e2e/Services/Databases/DatabasesBase.php
  • tests/e2e/Services/Databases/Permissions/LegacyPermissionsGuestTest.php
  • tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php
  • tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsMemberTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php
  • tests/e2e/Services/Functions/FunctionsConsoleClientTest.php
  • tests/e2e/Services/Functions/FunctionsCustomServerTest.php
  • tests/e2e/Services/GraphQL/AccountTest.php
  • tests/e2e/Services/GraphQL/FunctionsClientTest.php
  • tests/e2e/Services/GraphQL/FunctionsServerTest.php
  • tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
  • tests/e2e/Services/GraphQL/Legacy/DatabaseServerTest.php
  • tests/e2e/Services/GraphQL/MessagingTest.php
  • tests/e2e/Services/GraphQL/StorageClientTest.php
  • tests/e2e/Services/GraphQL/StorageServerTest.php
  • tests/e2e/Services/GraphQL/TablesDB/DatabaseClientTest.php
  • tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
  • tests/e2e/Services/GraphQL/TeamsClientTest.php
  • tests/e2e/Services/GraphQL/TeamsServerTest.php
  • tests/e2e/Services/GraphQL/UsersTest.php
  • tests/e2e/Services/Messaging/MessagingBase.php
  • tests/e2e/Services/Messaging/MessagingConsoleClientTest.php
  • tests/e2e/Services/Migrations/MigrationsBase.php
  • tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
  • tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php
  • tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
  • tests/e2e/Services/Sites/SitesConsoleClientTest.php
  • tests/e2e/Services/Storage/StorageBase.php
  • tests/e2e/Services/Storage/StorageCustomClientTest.php
  • tests/e2e/Services/Storage/StorageCustomServerTest.php
  • tests/e2e/Services/Teams/TeamsBase.php
  • tests/e2e/Services/Teams/TeamsBaseClient.php
  • tests/e2e/Services/Teams/TeamsBaseServer.php
  • tests/e2e/Services/Teams/TeamsConsoleClientTest.php
  • tests/e2e/Services/Teams/TeamsCustomClientTest.php
  • tests/e2e/Services/Tokens/TokensBase.php
  • tests/e2e/Services/Tokens/TokensConsoleClientTest.php
  • tests/e2e/Services/Tokens/TokensCustomServerTest.php
  • tests/e2e/Services/Users/UsersBase.php
  • tests/e2e/Services/VCS/VCSConsoleClientTest.php
  • tests/e2e/Services/Webhooks/WebhooksBase.php
  • tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php
  • tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php
  • tests/unit/Utopia/Database/Validator/ProjectIdTest.php
  • tests/unit/Utopia/Request/Filters/V16Test.php
  • tests/unit/Utopia/Request/Filters/V17Test.php
  • tests/unit/Utopia/Request/Filters/V18Test.php
  • tests/unit/Utopia/Request/Filters/V19Test.php
  • tests/unit/Utopia/Response/Filters/V16Test.php
  • tests/unit/Utopia/Response/Filters/V17Test.php
  • tests/unit/Utopia/Response/Filters/V18Test.php
  • tests/unit/Utopia/Response/Filters/V19Test.php
💤 Files with no reviewable changes (3)
  • tests/e2e/Services/GraphQL/FunctionsServerTest.php
  • tests/e2e/Services/GraphQL/AccountTest.php
  • tests/e2e/Services/GraphQL/FunctionsClientTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/Services/Teams/TeamsCustomClientTest.php
🧰 Additional context used
🧬 Code graph analysis (31)
tests/unit/Utopia/Request/Filters/V18Test.php (3)
tests/unit/Utopia/Request/Filters/V16Test.php (1)
  • DataProvider (46-54)
tests/unit/Utopia/Request/Filters/V17Test.php (2)
  • DataProvider (45-53)
  • DataProvider (77-85)
tests/unit/Utopia/Request/Filters/V19Test.php (2)
  • DataProvider (63-71)
  • DataProvider (73-81)
tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php (2)
tests/e2e/General/UsageTest.php (11)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
  • Depends (418-582)
  • Depends (584-656)
  • Depends (658-828)
  • Depends (830-907)
  • Depends (909-1060)
  • Depends (1062-1124)
  • Depends (1192-1266)
  • Depends (1268-1375)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Messaging/MessagingConsoleClientTest.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/unit/Utopia/Request/Filters/V16Test.php (4)
tests/unit/Utopia/Database/Validator/ProjectIdTest.php (1)
  • DataProvider (35-39)
tests/unit/Utopia/Request/Filters/V17Test.php (2)
  • DataProvider (45-53)
  • DataProvider (77-85)
tests/unit/Utopia/Request/Filters/V18Test.php (1)
  • DataProvider (41-49)
tests/unit/Utopia/Request/Filters/V19Test.php (2)
  • DataProvider (63-71)
  • DataProvider (73-81)
tests/e2e/Services/Storage/StorageBase.php (2)
tests/e2e/Services/GraphQL/StorageServerTest.php (1)
  • Depends (53-87)
tests/e2e/Services/Storage/StorageCustomServerTest.php (4)
  • Depends (75-170)
  • Depends (172-227)
  • Depends (229-268)
  • Depends (270-305)
tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/unit/Utopia/Response/Filters/V16Test.php (3)
tests/unit/Utopia/Response/Filters/V17Test.php (5)
  • DataProvider (56-64)
  • DataProvider (80-88)
  • DataProvider (103-111)
  • DataProvider (126-134)
  • projectProvider (26-54)
tests/unit/Utopia/Response/Filters/V19Test.php (3)
  • deploymentProvider (113-139)
  • functionProvider (26-47)
  • projectProvider (222-243)
tests/unit/Utopia/Response/Filters/V18Test.php (3)
  • executionProvider (53-104)
  • functionProvider (26-40)
  • projectProvider (116-134)
tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (16)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
  • Depends (419-480)
  • Depends (719-793)
  • Depends (795-898)
  • Depends (900-973)
  • Depends (975-1025)
  • Depends (1027-1091)
  • Depends (1093-1146)
  • Depends (1148-1194)
  • Depends (1196-1286)
  • Depends (1462-1527)
  • Depends (1962-1978)
tests/e2e/Services/Teams/TeamsConsoleClientTest.php (2)
tests/e2e/General/UsageTest.php (11)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
  • Depends (418-582)
  • Depends (584-656)
  • Depends (658-828)
  • Depends (830-907)
  • Depends (909-1060)
  • Depends (1062-1124)
  • Depends (1192-1266)
  • Depends (1268-1375)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Migrations/MigrationsBase.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (16)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
  • Depends (419-480)
  • Depends (719-793)
  • Depends (795-898)
  • Depends (900-973)
  • Depends (975-1025)
  • Depends (1027-1091)
  • Depends (1093-1146)
  • Depends (1148-1194)
  • Depends (1196-1286)
  • Depends (1462-1527)
  • Depends (1962-1978)
tests/e2e/Services/Sites/SitesConsoleClientTest.php (2)
tests/e2e/Services/GraphQL/TeamsServerTest.php (1)
  • Group (284-305)
tests/e2e/Services/Projects/ProjectsConsoleClientTest.php (14)
  • Group (27-113)
  • Group (161-226)
  • Group (228-434)
  • Group (436-693)
  • Group (868-940)
  • Group (942-1044)
  • Group (1046-1133)
  • Group (1975-2175)
  • Group (4708-4764)
  • Group (4767-4853)
  • Group (4856-4896)
  • Group (4898-4951)
  • Group (4953-5030)
  • Group (5032-5085)
tests/unit/Utopia/Response/Filters/V17Test.php (3)
tests/unit/Utopia/Request/Filters/V16Test.php (1)
  • DataProvider (46-54)
tests/unit/Utopia/Response/Filters/V18Test.php (1)
  • projectProvider (116-134)
tests/unit/Utopia/Response/Filters/V19Test.php (1)
  • projectProvider (222-243)
tests/e2e/Services/GraphQL/TablesDB/DatabaseClientTest.php (2)
tests/e2e/General/UsageTest.php (11)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
  • Depends (418-582)
  • Depends (584-656)
  • Depends (658-828)
  • Depends (830-907)
  • Depends (909-1060)
  • Depends (1062-1124)
  • Depends (1192-1266)
  • Depends (1268-1375)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Webhooks/WebhooksBase.php (2)
tests/e2e/General/UsageTest.php (3)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php (13)
  • Depends (26-67)
  • Depends (69-136)
  • Depends (209-250)
  • Depends (252-318)
  • Depends (442-479)
  • Depends (481-524)
  • Depends (526-564)
  • Depends (603-653)
  • Depends (655-701)
  • Depends (703-748)
  • Depends (750-817)
  • Depends (819-860)
  • Depends (862-897)
tests/e2e/Services/Teams/TeamsBaseClient.php (1)
tests/e2e/Scopes/Scope.php (1)
  • getLastEmailByAddress (66-87)
tests/e2e/Services/Realtime/RealtimeCustomClientTest.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php (1)
tests/e2e/Scopes/ApiTablesDB.php (4)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Services/GraphQL/UsersTest.php (2)
tests/e2e/General/UsageTest.php (11)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
  • Depends (418-582)
  • Depends (584-656)
  • Depends (658-828)
  • Depends (830-907)
  • Depends (909-1060)
  • Depends (1062-1124)
  • Depends (1192-1266)
  • Depends (1268-1375)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Tokens/TokensCustomServerTest.php (2)
tests/e2e/General/UsageTest.php (11)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
  • Depends (418-582)
  • Depends (584-656)
  • Depends (658-828)
  • Depends (830-907)
  • Depends (909-1060)
  • Depends (1062-1124)
  • Depends (1192-1266)
  • Depends (1268-1375)
tests/e2e/Services/Tokens/TokensConsoleClientTest.php (3)
  • Depends (124-187)
  • Depends (189-241)
  • Depends (243-255)
tests/e2e/Services/GraphQL/TeamsServerTest.php (2)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Projects/ProjectsConsoleClientTest.php (15)
  • Group (27-113)
  • Group (161-226)
  • Group (228-434)
  • Group (436-693)
  • Group (868-940)
  • Group (942-1044)
  • Group (1046-1133)
  • Group (1975-2175)
  • Group (4708-4764)
  • Group (4767-4853)
  • Group (4856-4896)
  • Group (4898-4951)
  • Group (4953-5030)
  • Group (5032-5085)
  • Group (5087-5188)
tests/unit/Utopia/Response/Filters/V19Test.php (2)
tests/unit/Utopia/Request/Filters/V16Test.php (1)
  • DataProvider (46-54)
tests/unit/Utopia/Response/Filters/V18Test.php (1)
  • functionProvider (26-40)
tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php (1)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (2)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (3)
  • createUser (47-81)
  • getServerHeader (116-123)
  • addToTeam (100-113)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Services/GraphQL/StorageClientTest.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsMemberTest.php (3)
tests/e2e/Services/Databases/Permissions/DatabasesPermissionsBase.php (1)
  • createUser (47-81)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getSecurityParam (51-54)
  • getRecordIdParam (46-49)
tests/unit/Utopia/Response/Filters/V18Test.php (1)
tests/unit/Utopia/Response/Filters/V16Test.php (7)
  • DataProvider (68-76)
  • DataProvider (116-124)
  • DataProvider (142-150)
  • DataProvider (210-218)
  • functionProvider (126-140)
  • executionProvider (78-114)
  • projectProvider (173-208)
tests/e2e/Services/GraphQL/TeamsClientTest.php (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php (1)
tests/e2e/Services/Webhooks/WebhooksBase.php (16)
  • Depends (103-196)
  • Depends (198-257)
  • Depends (259-315)
  • Depends (449-540)
  • Depends (542-601)
  • Depends (603-659)
  • Depends (777-818)
  • Depends (820-890)
  • Depends (892-944)
  • Depends (946-992)
  • Depends (994-1028)
  • Depends (1074-1115)
  • Depends (1117-1160)
  • Depends (1211-1287)
  • Depends (1289-1347)
  • Depends (1406-1469)
tests/e2e/Services/GraphQL/MessagingTest.php (1)
tests/e2e/Services/Messaging/MessagingBase.php (16)
  • Depends (133-242)
  • Depends (270-283)
  • Depends (285-295)
  • Depends (297-308)
  • Depends (342-367)
  • Depends (369-403)
  • Depends (405-418)
  • Depends (420-508)
  • Depends (559-574)
  • Depends (576-647)
  • Depends (649-772)
  • Depends (774-795)
  • Depends (797-806)
  • Depends (808-874)
  • Depends (1367-1418)
  • Depends (1532-1580)
tests/e2e/Services/GraphQL/Legacy/DatabaseServerTest.php (2)
tests/e2e/General/UsageTest.php (11)
  • Depends (184-226)
  • Depends (228-359)
  • Depends (361-416)
  • Depends (418-582)
  • Depends (584-656)
  • Depends (658-828)
  • Depends (830-907)
  • Depends (909-1060)
  • Depends (1062-1124)
  • Depends (1192-1266)
  • Depends (1268-1375)
tests/e2e/Services/Account/AccountCustomClientTest.php (5)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
tests/e2e/Services/Projects/ProjectsConsoleClientTest.php (5)
tests/e2e/Services/Account/AccountCustomClientTest.php (16)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
  • Depends (419-480)
  • Depends (719-793)
  • Depends (795-898)
  • Depends (900-973)
  • Depends (975-1025)
  • Depends (1027-1091)
  • Depends (1093-1146)
  • Depends (1148-1194)
  • Depends (1196-1286)
  • Depends (1462-1527)
  • Depends (1962-1978)
tests/e2e/Services/Account/AccountBase.php (1)
  • Group (369-439)
tests/e2e/Services/GraphQL/TeamsServerTest.php (1)
  • Group (284-305)
tests/e2e/Services/Sites/SitesConsoleClientTest.php (1)
  • Group (18-149)
tests/e2e/Services/Storage/StorageBase.php (1)
  • Group (18-299)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 0

Caution

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

⚠️ Outside diff range comments (1)
tests/extensions/Retryable.php (1)

1-13: Missing import for Retry class.

Similar to RetrySubscriber.php, this file references Retry::class on line 31 without a corresponding use statement.

🐛 Proposed fix
 <?php

 namespace Appwrite\Tests;

+use Appwrite\Tests\Retry;
+
 /**
  * Marker trait for classes that support retry functionality.
♻️ Duplicate comments (1)
tests/extensions/Retryable.php (1)

21-36: getNumberOfRetries() remains unused by the retry infrastructure.

The RetrySubscriber implements its own getRetryCountForTest() method rather than calling this trait method. This creates duplication and leaves the trait's method effectively dead.

Consider either:

  1. Having RetrySubscriber use this trait method on test instances that implement Retryable
  2. Removing this method if the trait is purely a marker
🧹 Nitpick comments (4)
tests/extensions/RetrySubscriber.php (3)

19-24: $pendingRetries is declared but never used.

This static property is only cleared in reset() but never populated or read. Either remove it or complete the pending retry queue implementation.

♻️ Remove dead code
-    /**
-     * Track tests that should be retried
-     *
-     * `@var` array<string, array{test: TestMethod, remainingRetries: int, lastError: \Throwable|null}>
-     */
-    private static array $pendingRetries = [];

And update reset():

     public static function reset(): void
     {
         self::$retryCounts = [];
-        self::$pendingRetries = [];
     }

31-35: Consider using $errorMessage for enhanced retry logging or remove the unused parameter.

The error message is extracted but never displayed. Including it in the retry output could help developers diagnose flaky tests.

♻️ Option 1: Use the error message in retry output
             fwrite(
                 STDOUT,
                 sprintf(
-                    "\e[33m[RETRY] Test %s failed (attempt %d/%d). %s\e[0m\n",
+                    "\e[33m[RETRY] Test %s failed (attempt %d/%d). %s\nError: %s\e[0m\n",
                     $testId,
                     $currentAttempt + 1,
                     $retryCount + 1,
-                    $remainingRetries > 0 ? "Will retry {$remainingRetries} more time(s)." : "No more retries."
+                    $remainingRetries > 0 ? "Will retry {$remainingRetries} more time(s)." : "No more retries.",
+                    substr($errorMessage, 0, 200) // Truncate for readability
                 )
             );

63-93: This method duplicates logic in Retryable::getNumberOfRetries().

Both implementations use reflection to read the Retry attribute from test methods. Consider extracting shared logic to avoid divergence.

tests/extensions/Retryable.php (1)

23-28: Consider adding a type assertion or documenting the TestCase requirement.

$this->name() assumes usage within a PHPUnit\Framework\TestCase subclass. While this is likely the intended use, the trait doesn't enforce this constraint.

♻️ Add defensive assertion
     public function getNumberOfRetries(): int
     {
+        if (!method_exists($this, 'name')) {
+            return 0;
+        }
+
         $root = new \ReflectionClass($this);
         $name = $this->name();

Or document in the trait docblock:

 /**
  * Marker trait for classes that support retry functionality.
+ * This trait must be used in a PHPUnit\Framework\TestCase subclass.
  * The actual retry logic is handled by the RetrySubscriber extension.
📜 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 714b8d7 and 46f3fad.

📒 Files selected for processing (6)
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php
  • tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
  • tests/extensions/RetrySubscriber.php
  • tests/extensions/Retryable.php
  • tests/extensions/TestHook.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/Services/Databases/Permissions/TablesDBPermissionsGuestTest.php
🧰 Additional context used
🧬 Code graph analysis (3)
tests/extensions/TestHook.php (2)
tests/extensions/TestFinishedSubscriber.php (1)
  • TestFinishedSubscriber (8-30)
tests/extensions/RetrySubscriber.php (1)
  • RetrySubscriber (10-103)
tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (2)
tests/e2e/Scopes/ApiLegacy.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Scopes/ApiTablesDB.php (3)
  • getContainerIdParam (26-29)
  • getRecordIdParam (46-49)
  • getRecordResource (41-44)
tests/e2e/Services/Projects/ProjectsConsoleClientTest.php (2)
tests/e2e/Services/Account/AccountCustomClientTest.php (14)
  • Depends (24-128)
  • Depends (130-175)
  • Depends (177-209)
  • Depends (211-264)
  • Depends (266-415)
  • Depends (419-480)
  • Depends (719-793)
  • Depends (795-898)
  • Depends (900-973)
  • Depends (975-1025)
  • Depends (1027-1091)
  • Depends (1093-1146)
  • Depends (1148-1194)
  • Depends (1196-1286)
tests/e2e/Services/GraphQL/TeamsServerTest.php (1)
  • Group (284-305)
🪛 PHPMD (2.15.0)
tests/extensions/TestHook.php

14-14: Avoid unused parameters such as '$configuration'. (undefined)

(UnusedFormalParameter)


14-14: Avoid unused parameters such as '$parameters'. (undefined)

(UnusedFormalParameter)

tests/extensions/RetrySubscriber.php

31-31: Avoid unused parameters such as '$errorMessage'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: E2E Service Test (Teams)
  • GitHub Check: E2E Service Test (Health)
  • GitHub Check: E2E Service Test (Projects)
  • GitHub Check: E2E Service Test (Proxy)
  • GitHub Check: E2E Service Test (Users)
  • GitHub Check: E2E Service Test (VCS)
  • GitHub Check: E2E Service Test (Migrations)
  • GitHub Check: E2E Service Test (FunctionsSchedule)
  • GitHub Check: E2E Service Test (Console)
  • GitHub Check: E2E Service Test (Realtime)
  • GitHub Check: E2E Service Test (Locale)
  • GitHub Check: E2E Service Test (GraphQL)
  • GitHub Check: E2E Service Test (Account)
  • GitHub Check: E2E Service Test (Functions)
  • GitHub Check: E2E Service Test (Site Screenshots)
  • GitHub Check: E2E Service Test (Abuse enabled)
  • GitHub Check: Unit Test
  • GitHub Check: E2E General Test
  • GitHub Check: Benchmark
  • GitHub Check: scan
🔇 Additional comments (17)
tests/e2e/Services/Projects/ProjectsConsoleClientTest.php (6)

7-8: LGTM!

The imports for Depends and Group attributes are correctly added for PHPUnit 11 compatibility.


27-29: LGTM!

The #[Group] attributes are correctly applied to testCreateProject, which serves as the root dependency for multiple downstream tests.


228-230: Previously flagged issue has been resolved.

The #[Depends('testCreateProject')] attribute is now correctly applied to testListProject, ensuring the $data parameter receives the project ID from the dependency chain.


869-871: Previously flagged issue has been resolved.

The #[Depends('testCreateProject')] attribute is now correctly applied to testUpdateProjectSMTP.


1048-1050: Previously flagged issue has been resolved.

The #[Depends('testCreateProject')] attribute is now correctly applied to testUpdateTemplates.


1978-1981: Previously flagged issue has been resolved.

The #[Depends('testCreateProject')] attribute is now correctly applied to testUpdateMockNumbers, along with the appropriate #[Group] attributes.

tests/e2e/Services/Databases/Permissions/TablesDBPermissionsTeamTest.php (9)

1-14: LGTM!

Namespace and imports are correctly structured for PHPUnit 11 with appropriate attribute imports and Utopia database helpers.


16-24: LGTM!

Class structure with trait composition follows the established pattern for TablesDB permission tests. The ApiTablesDB trait provides the correct URL helpers (getContainerIdParam() returns 'tableId', getRecordResource() returns 'rows').


26-32: LGTM!

Team creation helper delegates to the base trait's createTeam method. The returned array structure is consistent with usage in createCollections.


34-41: LGTM!

User creation follows the same pattern as team creation, delegating to the base trait's createUser method.


43-121: LGTM!

Collection setup is well-structured with proper assertions for all API calls. The permission model correctly grants:

  • Collection 1: read access to team1 members, write access to team1 admins
  • Collection 2: read access to team2 members, write access to team2 owners

The sleep(2) at line 118 accommodates async schema indexing. Consider using a polling helper (per the PR's SchemaPolling trait) if flaky tests arise.


123-153: LGTM!

Data providers correctly enumerate the expected permission matrix:

  • Read: Team membership grants read access (no role required), so user3 can read both collections.
  • Write: Requires specific roles (admin for team1, owner for team2), so user3 cannot write to either.

Test coverage is comprehensive for the permission combinations.


155-203: LGTM!

Test setup correctly:

  1. Creates users and teams with appropriate role assignments
  2. Creates collections with team-based permissions
  3. Seeds each collection with a document using server credentials
  4. Returns $this->users for dependent test methods (PHPUnit passes this as the last parameter)

The PHPDoc explaining data provider state loss is helpful documentation.


205-225: LGTM!

The test correctly validates read permissions using user sessions via cookie authentication. The #[Depends] attribute ensures $users is passed from testSetupDatabase, and #[DataProvider] supplies the test matrix.

Minor suggestion: Consider also asserting $this->assertEquals(200, $documents['headers']['status-code']) in the success branch for explicit status verification, though the count assertion implicitly confirms success.


227-255: LGTM!

Write permission test correctly handles the two failure modes:

  • 401: User is in the team (collection visible) but lacks the required role for write access
  • 404: User is not in the team, so the collection is not discoverable

The assertContains check appropriately validates either error response.

tests/extensions/TestHook.php (1)

10-18: LGTM! Extension implementation follows PHPUnit 11 patterns correctly.

The unused $configuration and $parameters flagged by PHPMD are false positives—these parameters are mandated by the Extension interface contract and cannot be removed.

tests/extensions/RetrySubscriber.php (1)

46-60: This feature is incomplete: tests decorated with #[Retry] will not actually re-execute on failure.

The current implementation only logs retry attempts. While FailedSubscriber is invoked after a test fails, it cannot trigger re-execution—PHPUnit's event system does not support re-running tests from this hook.

The unused $pendingRetries property (line 24) suggests an attempt to build actual retry logic that was never completed. Update the implementation to either:

  1. Implement real retry execution using a suitable PHPUnit hook (if one exists for PHPUnit 11), or
  2. Remove the #[Retry] attribute and clarify in Retryable that retry tracking is informational only

Until resolved, tests using #[Retry(count: N)] will fail despite the expectation of automatic retries.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

PHPUnit 11+ warns when using comma-separated values with --exclude-group.
This warning causes the tests to return exit code 1 despite all tests passing.

Fix by using separate --exclude-group flags for each group.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
abnegate and others added 3 commits February 5, 2026 00:16
PHPUnit 12 requires PHP 8.3+ and uses PHP enums like
CodeCoverageInitializationStatus. The old composer:2.0 image used
PHP 8.0.6 which doesn't support enums, causing autoloader issues.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace @Depends annotations with static property assertions to fix
compatibility issues with PHPUnit 12 and paratest parallel execution.

Tests now use self::$databaseId and self::$collectionId/self::$tableId
directly instead of passing data between dependent tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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 (4)
tests/e2e/Services/Databases/TablesDB/DatabasesStringTypesTest.php (2)

624-628: ⚠️ Potential issue | 🔴 Critical

Assertions expect values from skipped tests - test will fail.

Lines 626-627 assert default values of 'updated default' and 'Updated text default value', but these values would only exist if testUpdateVarcharColumn and testUpdateTextColumn had run successfully. Both tests are marked as skipped (lines 425, 479).

The actual defaults are:

  • varchar_with_default: 'hello world' (line 93)
  • text_with_default: 'This is a longer default text value that can contain more content.' (line 261)
🐛 Proposed fix to match actual default values
         $this->assertEquals(201, $row['headers']['status-code']);
         // Check that default values are applied
-        $this->assertEquals('updated default', $row['body']['varchar_with_default']);
-        $this->assertEquals('Updated text default value', $row['body']['text_with_default']);
+        $this->assertEquals('hello world', $row['body']['varchar_with_default']);
+        $this->assertEquals('This is a longer default text value that can contain more content.', $row['body']['text_with_default']);

807-823: ⚠️ Potential issue | 🔴 Critical

Deleting non-existent column varchar_max - test will fail.

The test attempts to delete column varchar_max, but this column is never created. The varchar columns created in testCreateVarcharColumn are:

  • varchar_field
  • varchar_with_default
  • varchar_required
  • varchar_array
  • varchar_min

The DELETE call on line 808 will likely return 404 instead of 204.

🐛 Proposed fix to delete an existing column
         // Test SUCCESS: Delete varchar column
-        $deleteVarchar = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/varchar_max', [
+        $deleteVarchar = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/varchar_min', [
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
             'x-appwrite-key' => $this->getProject()['apiKey']
         ]);

         $this->assertEquals(204, $deleteVarchar['headers']['status-code']);

         // Verify deletion
-        $getDeleted = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/varchar_max', [
+        $getDeleted = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/varchar_min', [
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
             'x-appwrite-key' => $this->getProject()['apiKey']
         ]);
tests/e2e/Services/Databases/Legacy/DatabasesStringTypesTest.php (2)

734-758: ⚠️ Potential issue | 🟠 Major

Delete uses varchar_max which is never created.

No attribute with key varchar_max is created in this class, so the delete likely returns 404. Delete an existing attribute or create varchar_max earlier.

✅ Suggested fix (delete an existing attribute)
-        $deleteVarchar = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/varchar_max', [
+        $deleteVarchar = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/varchar_min', [

597-628: ⚠️ Potential issue | 🟠 Major

Assertions expect "updated" defaults that never happen—align with initial defaults.

After removing @depends annotations, testCreateDocumentWithDefaultValues() now runs regardless of whether the update tests execute. However, testUpdateVarcharAttribute() and testUpdateTextAttribute() are unconditionally skipped, so attribute defaults remain at their initial values. The assertions expect the "updated" values that never get applied.

Suggested fix
-        $this->assertEquals('updated default', $document['body']['varchar_with_default']);
-        $this->assertEquals('Updated text default value', $document['body']['text_with_default']);
+        $this->assertEquals('hello world', $document['body']['varchar_with_default']);
+        $this->assertEquals('This is a longer default text value that can contain more content.', $document['body']['text_with_default']);
🤖 Fix all issues with AI agents
In `@tests/e2e/Services/Databases/Legacy/DatabasesStringTypesTest.php`:
- Around line 36-39: Initialize the test class's static ID properties so PHP
won't throw "Typed property must not be accessed before initialization" when
assertions like assertNotEmpty(self::$databaseId) run; update the class
declaration for DatabasesStringTypesTest to give each static ID a safe default
(e.g., empty string or null-acceptable default) for properties such as
self::$databaseId and any other static IDs referenced across the file
(collectionId, documentId, etc.), ensuring the typed static properties are
initialized before tests run so assertions execute instead of a fatal Error.

In `@tests/e2e/Services/Databases/TablesDB/DatabasesStringTypesTest.php`:
- Line 21: The test suite relies on ordering because testCreateDatabase() sets
self::$databaseId used by testCreateTable(); restore explicit PHPUnit
dependencies by adding a `@depends` annotation on testCreateTable (and any other
tests that use self::$databaseId) referencing testCreateDatabase, or
alternatively make tests independent by creating their own database in
setUp()/setUpBeforeClass() and storing it on the test class; the quick fix is to
add `@depends` on testCreateTable to reference testCreateDatabase (ensure the
static property self::$databaseId remains populated in testCreateDatabase).
🧹 Nitpick comments (1)
tests/e2e/Services/Databases/TablesDB/DatabasesStringTypesTest.php (1)

58-65: Consider extracting repeated precondition boilerplate.

The pattern of asserting prerequisites and assigning local variables is repeated in every test method:

$this->assertNotEmpty(self::$databaseId, 'Database must be created first');
$this->assertNotEmpty(self::$tableId, 'Table must be created first');

$databaseId = self::$databaseId;
$tableId = self::$tableId;

A helper method could reduce duplication:

♻️ Optional: Extract to helper method
private function requireDatabaseAndTable(): array
{
    $this->assertNotEmpty(self::$databaseId, 'Database must be created first');
    $this->assertNotEmpty(self::$tableId, 'Table must be created first');
    
    return [self::$databaseId, self::$tableId];
}

Usage:

public function testCreateVarcharColumn(): void
{
    [$databaseId, $tableId] = $this->requireDatabaseAndTable();
    // ...
}

Also applies to: 143-149, 230-236, 295-301, 359-365

abnegate and others added 2 commits February 5, 2026 01:30
Remove @Depends annotation and create a new database within the test
to fix compatibility with PHPUnit 12 and paratest parallel execution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove @Depends annotation and set up phone number within the test
to ensure it works with PHPUnit 12 and paratest parallel execution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 1

🤖 Fix all issues with AI agents
In `@tests/e2e/Services/GraphQL/AccountTest.php`:
- Around line 126-140: Capture and validate the response from the precondition
phone update call so failures are detected immediately: call
$this->client->call(...) with $phonePayload into a $response variable, decode
the response body and assert the operation succeeded (e.g., no errors and
expected data present) before continuing the test; update references around
getQuery(self::UPDATE_ACCOUNT_PHONE), $phonePayload, client->call and
$this->getHeaders() to include the response capture and appropriate assertions.

abnegate and others added 10 commits February 5, 2026 01:48
- Update expected default values to match the actual defaults (update
  tests are skipped, so original defaults apply)
- Fix varchar_max -> varchar_min (varchar_max was never created)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Attribute deletion is asynchronous - add sleep to wait for deletion
to complete before verifying the attribute no longer exists.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Skip FunctionsCustomServerTest::testCreateFunctionAndDeploymentFromTemplateCommit when GitHub API is rate-limited or unavailable
- Increase waitForAllAttributes timeout to 3 minutes for testBulkDeleteMatchingUpdatedDocuments to handle slow attribute processing in CI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove explicit sleep and increase assertEventually timeout from 60s to
180s to account for slower deployment and scheduling in CI. Also use
assertGreaterThanOrEqual to handle cases where multiple executions
may have run.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Increase all default timeouts in SchemaPolling trait from 2 minutes to
3 minutes to handle slower attribute processing in CI environments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
# Conflicts:
#	tests/e2e/Services/Databases/DatabasesBase.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
Increase all default timeouts in SchemaPolling trait from 3 minutes to
5 minutes to handle slower attribute processing when running tests in
parallel under heavy CI load.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Increase all default timeouts in SchemaPolling trait from 5 minutes to
10 minutes. With tests running in parallel via paratest, the attribute
processing worker can become backlogged under heavy CI load, requiring
longer wait times for attributes to transition from 'processing' to
'available' status.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The root cause of flaky attribute processing tests is that a single
appwrite-worker-databases container cannot keep up with attribute
creation jobs when tests run in parallel via paratest.

This fix:
- Scales the databases worker to 4 instances during E2E service tests
- Reverts timeout increases that masked the underlying issue

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The --functional flag cannot be used because tests rely on static
properties to share state between test methods (e.g., $databaseId
set in testCreateDatabase is used by testCreateTable). With --functional,
each test method runs in a separate process so these properties are not
initialized.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix typo CLient -> Client in DatabasesBase.php:4568
- Fix typo CLient -> Client in StorageCustomClientTest.php:428
- Fix cleanupFunction() call with extra parameter in FunctionsScheduleTest.php:175
- Increase message scheduling test timeouts from 30s to 180s for CI stability

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 1

Caution

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

⚠️ Outside diff range comments (1)
tests/e2e/Services/Storage/StorageCustomClientTest.php (1)

428-437: ⚠️ Potential issue | 🟡 Minor

Assert the PUT failure response instead of discarding it.

The PUT call’s response is ignored, so the test doesn’t actually verify update-permission failure. Capture and assert it to avoid false positives.

Suggested fix
-        $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
+        $update = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], [
             'name' => 'permissions.png',
         ]);
 
-        $this->assertEquals(401, $file['headers']['status-code']);
-
-        $this->assertEquals($file['headers']['status-code'], 401);
+        $this->assertEquals(401, $file['headers']['status-code']);
+        $this->assertEquals(401, $update['headers']['status-code']);
🤖 Fix all issues with AI agents
In `@tests/e2e/Services/Messaging/MessagingBase.php`:
- Around line 1072-1082: The assertEventually call that waits for the message to
become FAILED uses a 30000ms timeout; update the timeout argument in the
assertEventually invocation inside MessagingBase:: (the closure using $messageId
and client->call) from 30000 to 180000 to match other scheduling tests (ensure
the final call remains assertEventually(..., 180000, 1000) so CI has the same
180s tolerance).

Comment on lines +1072 to +1082
$messageId = $message['body']['$id'];
$this->assertEventually(function () use ($messageId) {
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);

$this->assertEquals(200, $message['headers']['status-code']);
$this->assertEquals(MessageStatus::FAILED, $message['body']['status']);
$this->assertEquals(200, $message['headers']['status-code']);
$this->assertEquals(MessageStatus::FAILED, $message['body']['status']);
}, 30000, 1000);
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

Inconsistent timeout: consider using 180000ms for CI stability.

The PR objective mentions increasing message scheduling test timeouts from 30s to 180s for CI stability. However, this test still uses a 30000ms (30s) timeout while similar scheduling tests at lines 1195 and 1267 use 180000ms (180s). This inconsistency may lead to flaky test failures in CI.

Proposed fix
         $this->assertEventually(function () use ($messageId) {
             $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
                 'content-type' => 'application/json',
                 'x-appwrite-project' => $this->getProject()['$id'],
                 'x-appwrite-key' => $this->getProject()['apiKey'],
             ]);

             $this->assertEquals(200, $message['headers']['status-code']);
             $this->assertEquals(MessageStatus::FAILED, $message['body']['status']);
-        }, 30000, 1000);
+        }, 180000, 1000);
📝 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
$messageId = $message['body']['$id'];
$this->assertEventually(function () use ($messageId) {
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $message['headers']['status-code']);
$this->assertEquals(MessageStatus::FAILED, $message['body']['status']);
$this->assertEquals(200, $message['headers']['status-code']);
$this->assertEquals(MessageStatus::FAILED, $message['body']['status']);
}, 30000, 1000);
$messageId = $message['body']['$id'];
$this->assertEventually(function () use ($messageId) {
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $message['headers']['status-code']);
$this->assertEquals(MessageStatus::FAILED, $message['body']['status']);
}, 180000, 1000);
🤖 Prompt for AI Agents
In `@tests/e2e/Services/Messaging/MessagingBase.php` around lines 1072 - 1082, The
assertEventually call that waits for the message to become FAILED uses a 30000ms
timeout; update the timeout argument in the assertEventually invocation inside
MessagingBase:: (the closure using $messageId and client->call) from 30000 to
180000 to match other scheduling tests (ensure the final call remains
assertEventually(..., 180000, 1000) so CI has the same 180s tolerance).

abnegate and others added 2 commits February 6, 2026 04:15
Parallel test execution with paratest overwhelms the single databases
worker when many tests create attributes simultaneously. Run these
specific services sequentially with PHPUnit to ensure the worker
can keep up with attribute processing jobs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add helper methods createCollectionWithAttribute(), createTableWithAttribute(),
  createCollectionWithIndex(), createTableWithIndex() to RealtimeConsoleClientTest
- Add createTeam() helper to RealtimeCustomClientTest
- Replace all @Depends annotations with direct helper method calls
- Enable tests to run in parallel with --functional flag

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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 (1)
tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php (1)

259-369: ⚠️ Potential issue | 🔴 Critical

Fix return type mismatch in testAttributesCollectionsAPI.

The method declares : array but returns nothing; PHP will throw a TypeError when the test completes. Change to : void.

Suggested fix
-    public function testAttributesCollectionsAPI(): array
+    public function testAttributesCollectionsAPI(): void
🤖 Fix all issues with AI agents
In `@tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php`:
- Around line 53-65: Replace the fixed sleep(2) calls with a polling loop that
queries the collection attributes until the new attribute appears or a timeout
is reached; specifically, after the POST that creates the attribute (around the
call using $this->client->call and the 'attributes/string' path in
RealtimeConsoleClientTest), repeatedly call the collection attributes GET
endpoint via $this->client->call and check for the 'name' attribute presence
with short sleeps between attempts, failing the test if the timeout elapses;
apply the same replacement for the other occurrences noted (near lines 104-105,
126-128, 148-150) so tests wait deterministically for schema readiness instead
of using hard-coded sleep.
- Around line 94-102: The test is calling the wrong endpoint for TablesDB
attribute creation: change the POST path used in RealtimeConsoleClientTest from
'/attributes/string' to the TablesDB endpoint '/columns/string' (keeping the
same concatenation with $databaseId and $actorsId and the same headers and
payload), so the call using Client::METHOD_POST targets '/tablesdb/' .
$databaseId . '/tables/' . $actorsId . '/columns/string' instead of
'/attributes/string'.
🧹 Nitpick comments (1)
tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php (1)

27-52: Add status-code assertions in setup helpers.

Without asserts, failed setup calls can cascade into confusing errors later. Consider asserting 201/202 after each create call.

♻️ Example pattern
         $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], $this->getHeaders()), [
             'databaseId' => ID::unique(),
             'name' => 'Actors DB',
         ]);
+        $this->assertEquals(201, $database['headers']['status-code']);

Also applies to: 74-92, 117-124, 139-146

Comment on lines +53 to +65
// Create attribute and wait for it to be available
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'name',
'size' => 256,
'required' => true,
]);

// Wait for attribute to be available
sleep(2);

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

Replace fixed sleeps with polling for schema readiness.

Hard-coded sleep(2) can be flaky under parallel load. Prefer polling with a timeout (or an existing schema polling helper) to avoid intermittent failures.

Also applies to: 104-105, 126-128, 148-150

🤖 Prompt for AI Agents
In `@tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php` around lines 53 -
65, Replace the fixed sleep(2) calls with a polling loop that queries the
collection attributes until the new attribute appears or a timeout is reached;
specifically, after the POST that creates the attribute (around the call using
$this->client->call and the 'attributes/string' path in
RealtimeConsoleClientTest), repeatedly call the collection attributes GET
endpoint via $this->client->call and check for the 'name' attribute presence
with short sleeps between attempts, failing the test if the timeout elapses;
apply the same replacement for the other occurrences noted (near lines 104-105,
126-128, 148-150) so tests wait deterministically for schema readiness instead
of using hard-coded sleep.

abnegate and others added 4 commits February 6, 2026 04:36
- Use /databases endpoint instead of /tablesdb for database creation
- Use /columns/string instead of /attributes/string for table columns
- Fix testAttributesCollectionsAPI return type from array to void

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enables --functional mode for parallel test execution by:
- Adding helper methods for independent test setup
- Removing all #[Depends] annotations
- Using static caching for shared resources

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enables parallel test execution for Transactions tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 4

Caution

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

⚠️ Outside diff range comments (4)
tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php (1)

487-640: ⚠️ Potential issue | 🟠 Major

Fix stale name expectations after removing test dependencies.
Line 529, Line 580, and Line 631 still expect "New Name", but the new setup only creates "User Name". These assertions will fail once tests run independently. Prefer asserting the name from the setup data (or update the name within each test).

💡 Suggested fix
-        $this->assertEquals($webhook['data']['name'], 'New Name');
+        $this->assertEquals($webhook['data']['name'], $data['name']);
-        $this->assertEquals($webhook['data']['name'], 'New Name');
+        $this->assertEquals($webhook['data']['name'], $data['name']);
-        $this->assertEquals($webhook['data']['name'], 'New Name');
+        $this->assertEquals($webhook['data']['name'], $data['name']);
tests/e2e/Services/Webhooks/WebhooksBase.php (3)

422-544: ⚠️ Potential issue | 🟠 Major

Fix undefined $data usage in attribute deletion.
Line 519 references $data['actorsId'], but $data is not defined in this method. This will raise a warning and can cause the delete call to target a null path. Use $actorsId instead.

✅ Suggested fix
-        $removed = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['actorsId'] . '/attributes/' . $extra['body']['key'], array_merge([
+        $removed = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/' . $extra['body']['key'], array_merge([

791-911: ⚠️ Potential issue | 🟠 Major

Fix undefined $data usage in column deletion.
Line 886 references $data['actorsId'], but $data is not defined in this method. Use $actorsId to avoid runtime warnings and an invalid URL.

✅ Suggested fix
-        $removed = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $data['actorsId'] . '/columns/' . $extra['body']['key'], array_merge([
+        $removed = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/columns/' . $extra['body']['key'], array_merge([

1343-1378: ⚠️ Potential issue | 🟠 Major

Update expected bucket name for delete test.
Line 1375 expects "Test Bucket Updated", but this test now deletes a freshly created bucket from setupStorageBucket() (name "Test Bucket"). The assertion will fail without the prior update test.

✅ Suggested fix
-        $this->assertEquals('Test Bucket Updated', $webhook['data']['name']);
+        $this->assertEquals('Test Bucket', $webhook['data']['name']);
🤖 Fix all issues with AI agents
In `@tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php`:
- Around line 139-146: The test uses the wrong payload key when creating a
TablesDB index: in the Client::call invocation that posts to '/tablesdb/' .
$data['databaseId'] . '/tables/' . $data['actorsId'] . '/indexes' replace the
'attributes' field with 'columns' (i.e., send 'columns' => ['name']) so the
request matches the TablesDB API and mirrors testIndexesTablesAPI(); ensure the
rest of the parameters (key, type) remain unchanged.

In `@tests/e2e/Services/Teams/TeamsBaseClient.php`:
- Around line 73-113: The method createPendingMembershipHelper has an unused
$teamName parameter (PHPMD flagged); remove $teamName from its PHPDoc and
signature and update every caller to stop passing a second argument so callers
only pass the team UID, then run tests/static analysis to ensure no remaining
call-site mismatches.

In `@tests/e2e/Services/Teams/TeamsBaseServer.php`:
- Around line 11-38: The helper method createServerMembershipHelper has an
unused parameter $teamName; remove it from the method signature and update all
call sites that pass that extra argument (the three tests calling
createServerMembershipHelper) so they only pass the team UID. Ensure the
function signature becomes createServerMembershipHelper(string $teamUid): array
and adjust callers to match (update any calls at the three locations referenced
in the review to drop the second argument) while keeping the method body and
returned array unchanged.

In `@tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php`:
- Around line 540-545: Remove the unused local variables introduced by the
refactor: delete the unused $email variables and the unused $session variable in
the WebhooksCustomClientTest setup blocks (they are the locals assigned from
$data in the sequence with $data = $this->setupAccountWithSession(); $id =
$data['id']; $email = $data['email']; $password = $data['password']; $newEmail =
...; $session = $data['session'];). Keep assignments that are actually used
(e.g., $id, $password, $newEmail) and remove only the unused $email and $session
binds to eliminate PHPMD warnings and dead locals.
🧹 Nitpick comments (5)
tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php (2)

25-67: Add response status assertions to prevent silent failures.

The helper extracts IDs from response bodies without verifying the API calls succeeded. If a call fails, the test will fail later with a confusing error when trying to use an invalid ID.

🛡️ Proposed fix to add assertions
         $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], $this->getHeaders()), [
             'databaseId' => ID::unique(),
             'name' => 'Actors DB',
         ]);
 
+        $this->assertEquals(201, $database['headers']['status-code']);
         $databaseId = $database['body']['$id'];
 
         $actors = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], $this->getHeaders()), [
             'collectionId' => ID::unique(),
             'name' => 'Actors',
             'permissions' => [
                 Permission::read(Role::any()),
                 Permission::create(Role::any()),
                 Permission::update(Role::any()),
                 Permission::delete(Role::any()),
             ],
         ]);
 
+        $this->assertEquals(201, $actors['headers']['status-code']);
         $actorsId = $actors['body']['$id'];
 
         // Create attribute and wait for it to be available
-        $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/string', array_merge([
+        $attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/string', array_merge([
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], $this->getHeaders()), [
             'key' => 'name',
             'size' => 256,
             'required' => true,
         ]);
+        $this->assertEquals(202, $attribute['headers']['status-code']);

72-108: Add response status assertions (same as collections helper).

Same recommendation applies here—add assertions after each API call to fail fast with clear errors.

tests/e2e/Services/Databases/Transactions/TransactionPermissionsBase.php (3)

104-128: Transaction created but never cleaned up.

The transaction created at lines 105-110 is left orphaned after the test completes. Even though the staging operation fails with 401, the transaction itself was successfully created and should be deleted or rolled back to maintain test isolation.

Consider adding cleanup in a finally block or at the end of the test:

Suggested cleanup pattern
         // This should fail with 401 Unauthorized
         $this->assertEquals(401, $staged['headers']['status-code']);
+
+        // Cleanup: delete the transaction
+        $this->client->call(Client::METHOD_DELETE, $this->getTransactionUrl($transaction['body']['$id']), array_merge([
+            'content-type' => 'application/json',
+            'x-appwrite-project' => $this->getProject()['$id'],
+        ], $this->getHeaders()));
     }

329-353: Successful staging tests leave transactions uncommitted.

Both testDocumentLevelUpdatePermissionGranted and testDocumentLevelDeletePermissionGranted successfully stage operations but never commit or rollback the transactions. While the tests verify the correct permission behavior, the orphaned transactions may affect subsequent tests or accumulate resources.

Also applies to: 409-433


1145-1173: Consider cleaning up transactions created in the list test.

The test creates 3 transactions in a loop (lines 1147-1156) to verify listing functionality, but none are cleaned up afterward. These orphaned transactions may affect other tests that rely on transaction counts.

Suggested cleanup
         // Verify transaction structure
         foreach ($list['body']['transactions'] as $transaction) {
             $this->assertArrayHasKey('$id', $transaction);
             $this->assertArrayHasKey('$createdAt', $transaction);
             $this->assertArrayHasKey('$updatedAt', $transaction);
             $this->assertArrayHasKey('status', $transaction);
             $this->assertArrayHasKey('operations', $transaction);
         }
+
+        // Cleanup created transactions
+        foreach ($transactionIds as $transactionId) {
+            $this->client->call(Client::METHOD_DELETE, $this->getTransactionUrl($transactionId), array_merge([
+                'content-type' => 'application/json',
+                'x-appwrite-project' => $this->getProject()['$id'],
+            ], $userHeaders));
+        }
     }

Comment on lines +139 to +146
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['actorsId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'key_name',
'type' => 'key',
'attributes' => ['name'],
]);
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 | 🟠 Major

Use 'columns' instead of 'attributes' for TablesDB index creation.

The helper uses 'attributes' but the TablesDB API expects 'columns'. Compare with testIndexesTablesAPI() at line 594 which correctly uses 'columns' => ['name']. This inconsistency will likely cause the index creation to fail or behave unexpectedly.

🐛 Proposed fix
         $this->client->call(Client::METHOD_POST, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['actorsId'] . '/indexes', array_merge([
             'content-type' => 'application/json',
             'x-appwrite-project' => $this->getProject()['$id'],
         ], $this->getHeaders()), [
             'key' => 'key_name',
             'type' => 'key',
-            'attributes' => ['name'],
+            'columns' => ['name'],
         ]);

Based on learnings: "In table-related endpoints...parameter descriptions should use 'table' and 'row' terminology instead of 'collection' and 'document' for clarity and consistency."

📝 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
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['actorsId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'key_name',
'type' => 'key',
'attributes' => ['name'],
]);
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $data['databaseId'] . '/tables/' . $data['actorsId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'key_name',
'type' => 'key',
'columns' => ['name'],
]);
🤖 Prompt for AI Agents
In `@tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php` around lines 139 -
146, The test uses the wrong payload key when creating a TablesDB index: in the
Client::call invocation that posts to '/tablesdb/' . $data['databaseId'] .
'/tables/' . $data['actorsId'] . '/indexes' replace the 'attributes' field with
'columns' (i.e., send 'columns' => ['name']) so the request matches the TablesDB
API and mirrors testIndexesTablesAPI(); ensure the rest of the parameters (key,
type) remain unchanged.

Comment on lines +73 to +113
/**
* Helper method to create a pending membership (not accepted).
* Returns membership data for tests that need unaccepted memberships.
*
* @param string $teamUid Team ID
* @param string $teamName Team name
* @return array{teamUid: string, teamName: string, secret: string, membershipUid: string, userUid: string, email: string, name: string}
*/
protected function createPendingMembershipHelper(string $teamUid, string $teamName): array
{
$email = uniqid() . 'friend@localhost.test';
$name = 'Friend User';

// Create membership invitation
$response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'email' => $email,
'name' => $name,
'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);

$this->assertEquals(201, $response['headers']['status-code']);

// Get invitation details from email
$lastEmail = $this->getLastEmailByAddress($email);
$this->assertNotEmpty($lastEmail, 'Email not found for address: ' . $email);

$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);

return [
'teamUid' => $tokens['teamId'],
'teamName' => $tokens['teamName'],
'secret' => $tokens['secret'],
'membershipUid' => $tokens['membershipId'],
'userUid' => $tokens['userId'],
'email' => $email,
'name' => $name,
];
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

🧩 Analysis chain

🏁 Script executed:

rg -n "createPendingMembershipHelper" --type=php -A 2

Repository: appwrite/appwrite

Length of output: 2227


🏁 Script executed:

# Check line numbers around 438-440 and context
sed -n '435,450p' tests/e2e/Services/Teams/TeamsBaseClient.php | cat -n

# Also check TeamsConsoleClientTest.php context around line 169
sed -n '165,175p' tests/e2e/Services/Teams/TeamsConsoleClientTest.php | cat -n

Repository: appwrite/appwrite

Length of output: 1431


🏁 Script executed:

# Check TeamsConsoleClientTest class definition
head -30 tests/e2e/Services/Teams/TeamsConsoleClientTest.php

# Also double-check for any other files that might use this method
rg "createPendingMembershipHelper" --type=php -l

Repository: appwrite/appwrite

Length of output: 1056


Remove unused $teamName parameter from createPendingMembershipHelper method.

The parameter is not used in the method body and PHPMD flags it. Update all call sites:

  • Line 242
  • Lines 443–444 (note: not 438–440)
  • Line 475
  • Lines 792–793
  • Line 169 in TeamsConsoleClientTest.php
🧹 Proposed fix
-protected function createPendingMembershipHelper(string $teamUid, string $teamName): array
+protected function createPendingMembershipHelper(string $teamUid): array
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 81-81: Avoid unused parameters such as '$teamName'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In `@tests/e2e/Services/Teams/TeamsBaseClient.php` around lines 73 - 113, The
method createPendingMembershipHelper has an unused $teamName parameter (PHPMD
flagged); remove $teamName from its PHPDoc and signature and update every caller
to stop passing a second argument so callers only pass the team UID, then run
tests/static analysis to ensure no remaining call-site mismatches.

Comment on lines +11 to +38
* Helper method to create a server-side team membership (auto-confirmed).
* Returns membership data for tests.
*
* @param string $teamUid Team ID
* @param string $teamName Team name
* @return array{teamUid: string, userUid: string, membershipUid: string}
*/
public function testGetTeamMemberships($data): array
protected function createServerMembershipHelper(string $teamUid, string $teamName): array
{
$id = $data['teamUid'] ?? '';
$email = uniqid() . 'friend@localhost.test';

$response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'email' => $email,
'name' => 'Friend User',
'roles' => ['admin', 'editor'],
'url' => 'http://localhost:5000/join-us#title'
]);

$this->assertEquals(201, $response['headers']['status-code']);

return [
'teamUid' => $teamUid,
'userUid' => $response['body']['userId'],
'membershipUid' => $response['body']['$id'],
];
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all usages of createServerMembershipHelper in PHP files
rg -n "createServerMembershipHelper" --type=php -A2 -B2

Repository: appwrite/appwrite

Length of output: 2067


🏁 Script executed:

#!/bin/bash
# Read the full method body to verify $teamName is not used
sed -n '18,37p' tests/e2e/Services/Teams/TeamsBaseServer.php

Repository: appwrite/appwrite

Length of output: 896


Remove unused $teamName parameter from helper method signature.
The parameter is never used within the method body. Update the method signature and all three call sites.

Proposed changes
-protected function createServerMembershipHelper(string $teamUid, string $teamName): array
+protected function createServerMembershipHelper(string $teamUid): array

Update call sites at lines 65, 254, and 300:

-$membershipData = $this->createServerMembershipHelper($teamData['teamUid'], $teamData['teamName']);
+$membershipData = $this->createServerMembershipHelper($teamData['teamUid']);
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 18-18: Avoid unused parameters such as '$teamName'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In `@tests/e2e/Services/Teams/TeamsBaseServer.php` around lines 11 - 38, The
helper method createServerMembershipHelper has an unused parameter $teamName;
remove it from the method signature and update all call sites that pass that
extra argument (the three tests calling createServerMembershipHelper) so they
only pass the team UID. Ensure the function signature becomes
createServerMembershipHelper(string $teamUid): array and adjust callers to match
(update any calls at the three locations referenced in the review to drop the
second argument) while keeping the method body and returned array unchanged.

Comment on lines +540 to +545
$data = $this->setupAccountWithSession();
$id = $data['id'];
$email = $data['email'];
$password = $data['password'];
$newEmail = uniqid() . 'new@localhost.test';
$session = $data['session'] ?? '';
$session = $data['session'];
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

Remove unused locals introduced by the refactor.
PHPMD flagged unused variables at Line 542 ($email), Line 647 ($email), Line 698 ($email), and Line 815 ($session). These can be safely removed to reduce noise.

🔧 Suggested cleanup
-        $email = $data['email'];
-        $email = $data['email'];
-        $email = $data['email'];
-        $session = $data['session'];

Also applies to: 647-648, 698-699, 815-815

🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 542-542: Avoid unused local variables such as '$email'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In `@tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php` around lines 540 -
545, Remove the unused local variables introduced by the refactor: delete the
unused $email variables and the unused $session variable in the
WebhooksCustomClientTest setup blocks (they are the locals assigned from $data
in the sequence with $data = $this->setupAccountWithSession(); $id =
$data['id']; $email = $data['email']; $password = $data['password']; $newEmail =
...; $session = $data['session'];). Keep assignments that are actually used
(e.g., $id, $password, $newEmail) and remove only the unused $email and $session
binds to eliminate PHPMD warnings and dead locals.

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