diff --git a/composer.json b/composer.json index 9717c6434..341403b59 100755 --- a/composer.json +++ b/composer.json @@ -33,13 +33,13 @@ "coverage": "./vendor/bin/coverage-check ./tmp/clover.xml 90" }, "require": { - "php": ">=8.1", + "php": ">=8.4", "ext-pdo": "*", "ext-mongodb": "*", "ext-mbstring": "*", "utopia-php/framework": "0.33.*", - "utopia-php/cache": "0.13.*", - "utopia-php/pools": "0.8.*", + "utopia-php/cache": "1.0.*", + "utopia-php/pools": "1.0.*", "utopia-php/mongo": "0.11.*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 9b21d4196..a9b5f100f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1ef51fb364b438b677b5bce17887153c", + "content-hash": "b16f905a54fc178bd3a93e89c9f8cf27", "packages": [ { "name": "brick/math", @@ -1383,16 +1383,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.4", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "d63c23357d74715a589454c141c843f0172bec6c" + "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/d63c23357d74715a589454c141c843f0172bec6c", - "reference": "d63c23357d74715a589454c141c843f0172bec6c", + "url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f", + "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f", "shasum": "" }, "require": { @@ -1460,7 +1460,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.4" + "source": "https://github.com/symfony/http-client/tree/v7.4.5" }, "funding": [ { @@ -1480,7 +1480,7 @@ "type": "tidelift" } ], - "time": "2026-01-23T16:34:22+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-client-contracts", @@ -2026,16 +2026,16 @@ }, { "name": "utopia-php/cache", - "version": "0.13.2", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6" + "reference": "7068870c086a6aea16173563a26b93ef3e408439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/5768498c9f451482f0bf3eede4d6452ddcd4a0f6", - "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/7068870c086a6aea16173563a26b93ef3e408439", + "reference": "7068870c086a6aea16173563a26b93ef3e408439", "shasum": "" }, "require": { @@ -2043,7 +2043,7 @@ "ext-memcached": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/pools": "0.8.*", + "utopia-php/pools": "1.*", "utopia-php/telemetry": "*" }, "require-dev": { @@ -2072,9 +2072,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.13.2" + "source": "https://github.com/utopia-php/cache/tree/1.0.0" }, - "time": "2025-12-17T08:55:43+00:00" + "time": "2026-01-28T10:55:44+00:00" }, { "name": "utopia-php/compression", @@ -2233,16 +2233,16 @@ }, { "name": "utopia-php/pools", - "version": "0.8.3", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8" + "reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/ad7d6ba946376e81c603204285ce9a674b6502b8", - "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1", + "reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1", "shasum": "" }, "require": { @@ -2252,7 +2252,8 @@ "require-dev": { "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "phpunit/phpunit": "11.*" + "phpunit/phpunit": "11.*", + "swoole/ide-helper": "6.*" }, "type": "library", "autoload": { @@ -2279,9 +2280,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/0.8.3" + "source": "https://github.com/utopia-php/pools/tree/1.0.2" }, - "time": "2025-12-17T09:35:18+00:00" + "time": "2026-01-28T13:12:36+00:00" }, { "name": "utopia-php/telemetry", @@ -4520,7 +4521,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4529,6 +4530,6 @@ "ext-mongodb": "*", "ext-mbstring": "*" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/tests/e2e/Adapter/PoolTest.php b/tests/e2e/Adapter/PoolTest.php index 42bea2c68..8f5f2bd3a 100644 --- a/tests/e2e/Adapter/PoolTest.php +++ b/tests/e2e/Adapter/PoolTest.php @@ -14,6 +14,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Limit; use Utopia\Database\PDO; +use Utopia\Pools\Adapter\Stack; use Utopia\Pools\Pool as UtopiaPool; class PoolTest extends Base @@ -43,7 +44,7 @@ public function getDatabase(): Database $redis->flushAll(); $cache = new Cache(new RedisAdapter($redis)); - $pool = new UtopiaPool('mysql', 10, function () { + $pool = new UtopiaPool(new Stack(), 'mysql', 10, function () { $dbHost = 'mysql'; $dbPort = '3307'; $dbUser = 'root'; diff --git a/tests/e2e/Adapter/Scopes/GeneralTests.php b/tests/e2e/Adapter/Scopes/GeneralTests.php index bf4280163..9ac5ccd35 100644 --- a/tests/e2e/Adapter/Scopes/GeneralTests.php +++ b/tests/e2e/Adapter/Scopes/GeneralTests.php @@ -4,6 +4,8 @@ use Exception; use Throwable; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; @@ -18,6 +20,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Mirror; use Utopia\Database\Query; trait GeneralTests @@ -697,6 +700,113 @@ public function testCacheFallback(): void $this->assertCount(1, $database->find('testRedisFallback', [Query::equal('string', ['text📝'])])); } + public function testCacheReconnect(): void + { + /** @var Database $database */ + $database = $this->getDatabase(); + + if (!$database->getAdapter()->getSupportForCacheSkipOnFailure()) { + $this->expectNotToPerformAssertions(); + return; + } + + // Wait for Redis to be fully healthy after previous test + $this->waitForRedis(); + // Create new cache with reconnection enabled + $redis = new \Redis(); + $redis->connect('redis', 6379); + $cache = new Cache((new RedisAdapter($redis))->setMaxRetries(3)); + // For Mirror, we need to set cache on both source and destination + if ($database instanceof Mirror) { + $database->getSource()->setCache($cache); + + $mirrorRedis = new \Redis(); + $mirrorRedis->connect('redis-mirror', 6379); + $mirrorCache = new Cache((new RedisAdapter($mirrorRedis))->setMaxRetries(3)); + $database->getDestination()->setCache($mirrorCache); + } + + $database->setCache($cache); + + $database->getAuthorization()->cleanRoles(); + $database->getAuthorization()->addRole(Role::any()->toString()); + + try { + $database->createCollection('testCacheReconnect', attributes: [ + new Document([ + '$id' => ID::custom('title'), + 'type' => Database::VAR_STRING, + 'size' => 255, + 'required' => true, + ]) + ], permissions: [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()) + ]); + + $database->createDocument('testCacheReconnect', new Document([ + '$id' => 'reconnect_doc', + 'title' => 'Test Document', + ])); + + // Cache the document + $doc = $database->getDocument('testCacheReconnect', 'reconnect_doc'); + $this->assertEquals('Test Document', $doc->getAttribute('title')); + + // Bring down Redis + $stdout = ''; + $stderr = ''; + Console::execute('docker ps -a --filter "name=utopia-redis" --format "{{.Names}}" | xargs -r docker stop', "", $stdout, $stderr); + sleep(1); + + // Bring back Redis + Console::execute('docker ps -a --filter "name=utopia-redis" --format "{{.Names}}" | xargs -r docker start', "", $stdout, $stderr); + $this->waitForRedis(); + + // Cache should reconnect - read should work + $doc = $database->getDocument('testCacheReconnect', 'reconnect_doc'); + $this->assertEquals('Test Document', $doc->getAttribute('title')); + + // Update should work after reconnect + $database->updateDocument('testCacheReconnect', 'reconnect_doc', new Document([ + '$id' => 'reconnect_doc', + 'title' => 'Updated Title', + ])); + + $doc = $database->getDocument('testCacheReconnect', 'reconnect_doc'); + $this->assertEquals('Updated Title', $doc->getAttribute('title')); + } finally { + // Ensure Redis is running + $stdout = ''; + $stderr = ''; + Console::execute('docker ps -a --filter "name=utopia-redis" --format "{{.Names}}" | xargs -r docker start', "", $stdout, $stderr); + $this->waitForRedis(); + + // Cleanup collection if it exists + if ($database->exists() && !$database->getCollection('testCacheReconnect')->isEmpty()) { + $database->deleteCollection('testCacheReconnect'); + } + } + } + + /** + * Wait for Redis to be ready with a readiness probe + */ + private function waitForRedis(int $maxRetries = 10, int $delayMs = 500): void + { + for ($i = 0; $i < $maxRetries; $i++) { + try { + $redis = new \Redis(); + $redis->connect('redis', 6379); + $redis->ping(); + return; + } catch (\RedisException $e) { + usleep($delayMs * 1000); + } + } + } }