From b7df2ca4360498d692997cd8c4d05d365af31931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 6 Aug 2025 18:44:56 +0200 Subject: [PATCH] Run all cached resolver/global cache tests with DatabaseCacheBootstrapper --- .../Contracts/CachedTenantResolver.php | 3 ++ src/TenancyServiceProvider.php | 4 -- tests/CachedTenantResolverTest.php | 22 ++++++-- tests/GlobalCacheTest.php | 54 +++++++++++++------ 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/Resolvers/Contracts/CachedTenantResolver.php b/src/Resolvers/Contracts/CachedTenantResolver.php index 16966149..017f4b04 100644 --- a/src/Resolvers/Contracts/CachedTenantResolver.php +++ b/src/Resolvers/Contracts/CachedTenantResolver.php @@ -17,6 +17,9 @@ abstract class CachedTenantResolver implements TenantResolver public function __construct(Application $app) { + // globalCache should generally not be injected, however in this case + // the class is always created from scratch when calling invalidateCache() + // meaning the global cache stores are also resolved from scratch. $this->cache = $app->make('globalCache')->store(static::cacheStore()); } diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index 6e26a953..888fbb1b 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -83,10 +83,6 @@ class TenancyServiceProvider extends ServiceProvider return new Commands\Seed($app['db']); }); - // todo0 check what happens if globalCache is injected - it may be - // problematic if it's injected before adjustCacheManagerUsing - // was used - $this->app->bind('globalCache', function ($app) { // We create a separate CacheManager to be used for "global" cache -- cache that // is always central, regardless of the current context. diff --git a/tests/CachedTenantResolverTest.php b/tests/CachedTenantResolverTest.php index 8c8f1d80..920c95a1 100644 --- a/tests/CachedTenantResolverTest.php +++ b/tests/CachedTenantResolverTest.php @@ -14,6 +14,8 @@ use Illuminate\Support\Facades\Route as RouteFacade; use Illuminate\Support\Facades\Schema; use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper; use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper; +use Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper; +use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException; use Stancl\Tenancy\Events\TenancyEnded; use Stancl\Tenancy\Events\TenancyInitialized; @@ -23,6 +25,8 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Middleware\InitializeTenancyByPath; use Stancl\Tenancy\Resolvers\RequestDataTenantResolver; use function Stancl\Tenancy\Tests\pest; +use function Stancl\Tenancy\Tests\withCacheTables; +use function Stancl\Tenancy\Tests\withTenantDatabases; beforeEach($cleanup = function () { Tenant::$extraCustomColumns = []; @@ -112,11 +116,19 @@ test('cache is invalidated when the tenant is updated', function (string $resolv // Only testing update here - presumably if this works, deletes (and other things we test here) // will work as well. The main unique thing about this test is that it makes the change from // *within* the tenant context. -test('cache is invalidated when tenant is updated from within the tenant context', function (string $cacheBootstrapper) { - config(['tenancy.bootstrappers' => [$cacheBootstrapper]]); +test('cache is invalidated when tenant is updated from within the tenant context', function (string $cacheStore, array $bootstrappers) { + config([ + 'cache.default' => $cacheStore, + 'tenancy.bootstrappers' => $bootstrappers, + ]); Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); + if ($cacheStore === 'database') { + withCacheTables(); + withTenantDatabases(); + } + $resolver = PathTenantResolver::class; $tenant = Tenant::create([$tenantModelColumn = tenantModelColumn(true) => 'acme']); @@ -150,9 +162,9 @@ test('cache is invalidated when tenant is updated from within the tenant context expect(DB::getQueryLog())->not()->toBeEmpty(); // Cache was invalidated, so the tenant was retrieved from the DB })->with([ - // todo0 test this with the database cache bootstrapper too? - CacheTenancyBootstrapper::class, - CacheTagsBootstrapper::class, + ['redis', [CacheTenancyBootstrapper::class]], + ['redis', [CacheTagsBootstrapper::class]], + ['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]], ]); test('cache is invalidated when the tenant is deleted', function (string $resolver, bool $configureTenantModelColumn) { diff --git a/tests/GlobalCacheTest.php b/tests/GlobalCacheTest.php index acbfba7e..44d6c2e9 100644 --- a/tests/GlobalCacheTest.php +++ b/tests/GlobalCacheTest.php @@ -29,8 +29,13 @@ beforeEach(function () { withCacheTables(); }); -test('global cache manager stores data in global cache', function (string $bootstrapper) { - config(['tenancy.bootstrappers' => [$bootstrapper]]); +test('global cache manager stores data in global cache', function (string $store, array $bootstrappers) { + config([ + 'cache.default' => $store, + 'tenancy.bootstrappers' => $bootstrappers, + ]); + + if ($store === 'database') withTenantDatabases(true); expect(cache('foo'))->toBe(null); GlobalCache::put('foo', 'bar'); @@ -44,9 +49,16 @@ test('global cache manager stores data in global cache', function (string $boots cache(['def' => 'ghi'], 10); expect(cache('def'))->toBe('ghi'); - // different stores, same underlying connection. the prefix is set ON THE STORE + // different stores expect(cache()->store()->getStore() !== GlobalCache::store()->getStore())->toBeTrue(); - expect(cache()->store()->getStore()->connection() === GlobalCache::store()->getStore()->connection())->toBeTrue(); + if ($store === 'redis') { + // same underlying connection. the prefix is set ON THE STORE + expect(cache()->store()->getStore()->connection() === GlobalCache::store()->getStore()->connection())->toBeTrue(); + } else { + // different connections + expect(cache()->store()->getStore()->getConnection()->getName())->toBe('tenant'); + expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central'); + } tenancy()->end(); expect(GlobalCache::get('abc'))->toBe('xyz'); @@ -64,8 +76,9 @@ test('global cache manager stores data in global cache', function (string $boots tenancy()->initialize($tenant1); expect(cache('def'))->toBe('ghi'); })->with([ - CacheTagsBootstrapper::class, - CacheTenancyBootstrapper::class, + ['redis', [CacheTagsBootstrapper::class]], + ['redis', [CacheTenancyBootstrapper::class]], + ['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]], ]); test('global cache facade is not persistent', function () { @@ -76,8 +89,6 @@ test('global cache facade is not persistent', function () { expect(spl_object_id(GlobalCache::getFacadeRoot()))->not()->toBe($oldId); }); -// todo0 add database cache bootstrapper to other tests - test('global cache is always central', function (string $store, array $bootstrappers, string $initialCentralCall) { config([ 'cache.default' => $store, @@ -161,15 +172,27 @@ test('global cache is always central', function (string $store, array $bootstrap 'none', ]); -test('the global_cache helper supports the same syntax as the cache helper', function (string $bootstrapper) { - config(['tenancy.bootstrappers' => [$bootstrapper]]); +test('the global_cache helper supports the same syntax as the cache helper', function (string $store, array $bootstrappers) { + config([ + 'cache.default' => $store, + 'tenancy.bootstrappers' => $bootstrappers, + ]); + + if ($store === 'database') withTenantDatabases(true); $tenant = Tenant::create(); $tenant->enter(); - // different stores, same underlying connection. the prefix is set ON THE STORE - expect(cache()->store()->getStore() !== global_cache()->store()->getStore())->toBeTrue(); - expect(cache()->store()->getStore()->connection() === global_cache()->store()->getStore()->connection())->toBeTrue(); + // different stores + expect(cache()->store()->getStore() !== GlobalCache::store()->getStore())->toBeTrue(); + if ($store === 'redis') { + // same underlying connection. the prefix is set ON THE STORE + expect(cache()->store()->getStore()->connection() === global_cache()->store()->getStore()->connection())->toBeTrue(); + } else { + // different connections + expect(cache()->store()->getStore()->getConnection()->getName())->toBe('tenant'); + expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central'); + } expect(cache('foo'))->toBe(null); // tenant cache is empty @@ -181,6 +204,7 @@ test('the global_cache helper supports the same syntax as the cache helper', fun expect(cache('foo'))->toBe(null); // tenant cache is not affected })->with([ - CacheTagsBootstrapper::class, - CacheTenancyBootstrapper::class, + ['redis', [CacheTagsBootstrapper::class]], + ['redis', [CacheTenancyBootstrapper::class]], + ['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]], ]);