1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 09:54:03 +00:00

[4.x] Support database cache store tenancy (#1290) (resolve #852)

* Initial implementation (lukinovec)

* Make sure DatabaseCacheBootstrapper runs after DatabaseTenancyBootstrapper, misc wip changes

* Fix withTenantDatabases()

* Add failing test (GlobalCacheTest)

* Configure globalCache's DB stores to use central connection instead of default connection every time it's reinstantiated

* Make GlobalCache facade not cached. Even though it wasn't causing issues
in our existing tests, it likely was flaky, and making it not $cached
makes it now consistent with global_cache() - always getting a new
CacheManager from the globalCache container binding

* Add database connection assertions in GlobalCacheTest

* Run all cached resolver/global cache tests with DatabaseCacheBootstrapper

* Reset adjustCacheManagerUsing in revert() and TestCase

* Reset static $stores property

* Finalize GlobalCache-related changes

* tests: remove pointless cache TTLs

* Refactor DatabaseCacheBootstrapper

* Refactor tests

Co-authored-by: lukinovec <lukinovec@gmail.com>
This commit is contained in:
Samuel Štancl 2025-08-08 00:54:01 +02:00
parent 3984d64cfa
commit ecc3374293
14 changed files with 600 additions and 38 deletions

View file

@ -56,7 +56,7 @@ test('tags separate cache properly', function () {
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
cache()->put('foo', 'bar', 1);
cache()->put('foo', 'bar');
expect(cache()->get('foo'))->toBe('bar');
$tenant2 = Tenant::create();
@ -64,7 +64,7 @@ test('tags separate cache properly', function () {
expect(cache('foo'))->not()->toBe('bar');
cache()->put('foo', 'xyz', 1);
cache()->put('foo', 'xyz');
expect(cache()->get('foo'))->toBe('xyz');
});
@ -72,7 +72,7 @@ test('invoking the cache helper works', function () {
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
cache(['foo' => 'bar'], 1);
cache(['foo' => 'bar']);
expect(cache('foo'))->toBe('bar');
$tenant2 = Tenant::create();
@ -80,7 +80,7 @@ test('invoking the cache helper works', function () {
expect(cache('foo'))->not()->toBe('bar');
cache(['foo' => 'xyz'], 1);
cache(['foo' => 'xyz']);
expect(cache('foo'))->toBe('xyz');
});
@ -88,7 +88,7 @@ test('cache is persisted', function () {
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
cache(['foo' => 'bar'], 10);
cache(['foo' => 'bar']);
expect(cache('foo'))->toBe('bar');
tenancy()->end();
@ -102,7 +102,7 @@ test('cache is persisted when reidentification is used', function () {
$tenant2 = Tenant::create();
tenancy()->initialize($tenant1);
cache(['foo' => 'bar'], 10);
cache(['foo' => 'bar']);
expect(cache('foo'))->toBe('bar');
tenancy()->initialize($tenant2);

View file

@ -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([
// todo@samuel 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) {

View file

@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
use Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Tests\Etc\Tenant;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use function Stancl\Tenancy\Tests\withBootstrapping;
use function Stancl\Tenancy\Tests\withCacheTables;
use function Stancl\Tenancy\Tests\withTenantDatabases;
beforeEach(function () {
withBootstrapping();
withCacheTables();
withTenantDatabases(true);
DatabaseCacheBootstrapper::$stores = null;
config([
'cache.stores.database.connection' => 'central', // Explicitly set cache DB connection name in config
'cache.stores.database.lock_connection' => 'central', // Also set lock connection name
'cache.default' => 'database',
'tenancy.bootstrappers' => [
DatabaseTenancyBootstrapper::class,
DatabaseCacheBootstrapper::class, // Used instead of CacheTenancyBootstrapper
],
]);
});
afterEach(function () {
DatabaseCacheBootstrapper::$stores = null;
});
test('DatabaseCacheBootstrapper switches the database cache store connections correctly', function () {
expect(config('cache.stores.database.connection'))->toBe('central');
expect(config('cache.stores.database.lock_connection'))->toBe('central');
expect(Cache::store()->getConnection()->getName())->toBe('central');
expect(Cache::lock('foo')->getConnectionName())->toBe('central');
tenancy()->initialize(Tenant::create());
expect(config('cache.stores.database.connection'))->toBe('tenant');
expect(config('cache.stores.database.lock_connection'))->toBe('tenant');
expect(Cache::store()->getConnection()->getName())->toBe('tenant');
expect(Cache::lock('foo')->getConnectionName())->toBe('tenant');
tenancy()->end();
expect(config('cache.stores.database.connection'))->toBe('central');
expect(config('cache.stores.database.lock_connection'))->toBe('central');
expect(Cache::store()->getConnection()->getName())->toBe('central');
expect(Cache::lock('foo')->getConnectionName())->toBe('central');
});
test('cache is separated correctly when using DatabaseCacheBootstrapper', function() {
// We need the prefix later for lower-level assertions. Let's store it
// once now and reuse this variable rather than re-fetching it to make
// it clear that the scoping does NOT come from a prefix change.
$cachePrefix = config('cache.prefix');
$getCacheUsingDbQuery = fn (string $cacheKey) =>
DB::selectOne("SELECT * FROM `cache` WHERE `key` = '{$cachePrefix}{$cacheKey}'")?->value;
$tenant = Tenant::create();
$tenant2 = Tenant::create();
// Write to cache in central context
cache()->set('foo', 'central');
expect(Cache::get('foo'))->toBe('central');
// The value retrieved by the DB query is formatted like "s:7:"central";".
// We use toContain() because of this formatting instead of just toBe().
expect($getCacheUsingDbQuery('foo'))->toContain('central');
tenancy()->initialize($tenant);
// Central cache doesn't leak to tenant context
expect(Cache::has('foo'))->toBeFalse();
expect($getCacheUsingDbQuery('foo'))->toBeNull();
cache()->set('foo', 'bar');
expect(Cache::get('foo'))->toBe('bar');
expect($getCacheUsingDbQuery('foo'))->toContain('bar');
tenancy()->initialize($tenant2);
// Assert one tenant's cache doesn't leak to another tenant
expect(Cache::has('foo'))->toBeFalse();
expect($getCacheUsingDbQuery('foo'))->toBeNull();
cache()->set('foo', 'xyz');
expect(Cache::get('foo'))->toBe('xyz');
expect($getCacheUsingDbQuery('foo'))->toContain('xyz');
tenancy()->initialize($tenant);
// Assert cache didn't leak to the original tenant
expect(Cache::get('foo'))->toBe('bar');
expect($getCacheUsingDbQuery('foo'))->toContain('bar');
tenancy()->end();
// Assert central 'foo' cache is still the same ('central')
expect(Cache::get('foo'))->toBe('central');
expect($getCacheUsingDbQuery('foo'))->toContain('central');
});
test('DatabaseCacheBootstrapper auto-detects all database driver stores by default', function() {
config([
'cache.stores.database' => [
'driver' => 'database',
'connection' => 'central',
'table' => 'cache',
],
'cache.stores.sessions' => [
'driver' => 'database',
'connection' => 'central',
'table' => 'sessions_cache',
],
'cache.stores.redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'cache.stores.file' => [
'driver' => 'file',
'path' => '/foo/bar',
],
]);
// Here, we're using auto-detection (default behavior)
expect(config('cache.stores.database.connection'))->toBe('central');
expect(config('cache.stores.sessions.connection'))->toBe('central');
expect(config('cache.stores.redis.connection'))->toBe('default');
expect(config('cache.stores.file.path'))->toBe('/foo/bar');
tenancy()->initialize(Tenant::create());
// Using auto-detection (default behavior),
// all database driver stores should be configured,
// and stores with non-database drivers are ignored.
expect(config('cache.stores.database.connection'))->toBe('tenant');
expect(config('cache.stores.sessions.connection'))->toBe('tenant');
expect(config('cache.stores.redis.connection'))->toBe('default'); // unchanged
expect(config('cache.stores.file.path'))->toBe('/foo/bar'); // unchanged
tenancy()->end();
// All database stores should be reverted, others unchanged
expect(config('cache.stores.database.connection'))->toBe('central');
expect(config('cache.stores.sessions.connection'))->toBe('central');
expect(config('cache.stores.redis.connection'))->toBe('default');
expect(config('cache.stores.file.path'))->toBe('/foo/bar');
});
test('manual $stores configuration takes precedence over auto-detection', function() {
// Configure multiple database stores
config([
'cache.stores.sessions' => [
'driver' => 'database',
'connection' => 'central',
'table' => 'sessions_cache',
],
'cache.stores.redis' => [
'driver' => 'redis',
'connection' => 'default',
],
]);
// Specific store overrides (including non-database stores)
DatabaseCacheBootstrapper::$stores = ['sessions', 'redis']; // Note: excludes 'database'
expect(config('cache.stores.database.connection'))->toBe('central');
expect(config('cache.stores.sessions.connection'))->toBe('central');
expect(config('cache.stores.redis.connection'))->toBe('default');
tenancy()->initialize(Tenant::create());
// Manual config takes precedence: only 'sessions' is configured
// - redis filtered out by driver check
// - database store not included in $stores
expect(config('cache.stores.database.connection'))->toBe('central'); // Excluded in manual config
expect(config('cache.stores.sessions.connection'))->toBe('tenant'); // Included and is database driver
expect(config('cache.stores.redis.connection'))->toBe('default'); // Included but filtered out (not database driver)
tenancy()->end();
// Only the manually configured stores' config will be reverted
expect(config('cache.stores.database.connection'))->toBe('central');
expect(config('cache.stores.sessions.connection'))->toBe('central');
expect(config('cache.stores.redis.connection'))->toBe('default');
});

View file

@ -11,6 +11,11 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use function Stancl\Tenancy\Tests\withCacheTables;
use function Stancl\Tenancy\Tests\withTenantDatabases;
beforeEach(function () {
config([
@ -20,26 +25,40 @@ beforeEach(function () {
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
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'], 1);
GlobalCache::put('foo', 'bar');
expect(GlobalCache::get('foo'))->toBe('bar');
$tenant1 = Tenant::create();
tenancy()->initialize($tenant1);
expect(GlobalCache::get('foo'))->toBe('bar');
GlobalCache::put(['abc' => 'xyz'], 1);
cache(['def' => 'ghi'], 10);
GlobalCache::put('abc', 'xyz');
cache(['def' => 'ghi']);
expect(cache('def'))->toBe('ghi');
// different stores, same underlying connection. the prefix is set ON THE STORE
expect(cache()->store()->getStore() === GlobalCache::store()->getStore())->toBeFalse();
expect(cache()->store()->getStore()->connection() === GlobalCache::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() === 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');
@ -51,25 +70,129 @@ test('global cache manager stores data in global cache', function (string $boots
expect(GlobalCache::get('abc'))->toBe('xyz');
expect(GlobalCache::get('foo'))->toBe('bar');
expect(cache('def'))->toBe(null);
cache(['def' => 'xxx'], 1);
cache(['def' => 'xxx']);
expect(cache('def'))->toBe('xxx');
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('the global_cache helper supports the same syntax as the cache helper', function (string $bootstrapper) {
config(['tenancy.bootstrappers' => [$bootstrapper]]);
test('global cache facade is not persistent', function () {
$oldId = spl_object_id(GlobalCache::getFacadeRoot());
$_ = new class {};
expect(spl_object_id(GlobalCache::getFacadeRoot()))->not()->toBe($oldId);
});
test('global cache is always central', function (string $store, array $bootstrappers, string $initialCentralCall) {
config([
'cache.default' => $store,
'tenancy.bootstrappers' => $bootstrappers,
]);
if ($store === 'database') {
withTenantDatabases(true);
}
// This tells us which "accessor" for the global cache should be instantiated first, before we go
// into the tenant context. We make sure to not touch the other one here. This tests that whether
// a particular accessor is used "early" makes no difference in the later behavior.
if ($initialCentralCall === 'helper') {
if ($store === 'database') expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central');
global_cache()->put('central-helper', true);
} else if ($initialCentralCall === 'facade') {
if ($store === 'database') expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central');
GlobalCache::put('central-facade', true);
} else if ($initialCentralCall === 'both') {
if ($store === 'database') expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central');
global_cache()->put('central-helper', true);
if ($store === 'database') expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central');
GlobalCache::put('central-facade', 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())->toBeFalse();
expect(cache()->store()->getStore()->connection() === global_cache()->store()->getStore()->connection())->toBeTrue();
// Here we use both the helper and the facade to ensure the value is accessible via either one
if ($initialCentralCall === 'helper') {
if ($store === 'database') expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central');
if ($store === 'database') expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central');
expect(global_cache('central-helper'))->toBe(true);
expect(GlobalCache::get('central-helper'))->toBe(true);
} else if ($initialCentralCall === 'facade') {
if ($store === 'database') expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central');
if ($store === 'database') expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central');
expect(global_cache('central-facade'))->toBe(true);
expect(GlobalCache::get('central-facade'))->toBe(true);
} else if ($initialCentralCall === 'both') {
if ($store === 'database') expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central');
if ($store === 'database') expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central');
expect(global_cache('central-helper'))->toBe(true);
expect(GlobalCache::get('central-helper'))->toBe(true);
expect(global_cache('central-facade'))->toBe(true);
expect(GlobalCache::get('central-facade'))->toBe(true);
}
global_cache()->put('tenant-helper', true);
GlobalCache::put('tenant-facade', true);
tenancy()->end();
if ($store === 'database') expect(global_cache()->store()->getStore()->getConnection()->getName())->toBe('central');
if ($store === 'database') expect(GlobalCache::store()->getStore()->getConnection()->getName())->toBe('central');
expect(global_cache('tenant-helper'))->toBe(true);
expect(GlobalCache::get('tenant-helper'))->toBe(true);
expect(global_cache('tenant-facade'))->toBe(true);
expect(GlobalCache::get('tenant-facade'))->toBe(true);
if ($initialCentralCall === 'helper') {
expect(GlobalCache::get('central-helper'))->toBe(true);
} else if ($initialCentralCall === 'facade') {
expect(global_cache('central-facade'))->toBe(true);
} else if ($initialCentralCall === 'both') {
expect(global_cache('central-helper'))->toBe(true);
expect(GlobalCache::get('central-helper'))->toBe(true);
expect(global_cache('central-facade'))->toBe(true);
expect(GlobalCache::get('central-facade'))->toBe(true);
}
})->with([
['redis', [CacheTagsBootstrapper::class]],
['redis', [CacheTenancyBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
])->with([
'helper',
'facade',
'both',
'none',
]);
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
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
@ -81,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]],
]);

View file

@ -2,21 +2,52 @@
namespace Stancl\Tenancy\Tests;
use Illuminate\Database\Schema\Blueprint;
use Stancl\Tenancy\Tests\TestCase;
use Stancl\JobPipeline\JobPipeline;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\MigrateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
uses(TestCase::class)->in(__DIR__);
function withTenantDatabases()
function withBootstrapping()
{
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
}
function withTenantDatabases(bool $migrate = false)
{
Event::listen(TenantCreated::class, JobPipeline::make($migrate
? [CreateDatabase::class, MigrateDatabase::class]
: [CreateDatabase::class]
)->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
}
function withCacheTables()
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
function pest(): TestCase
{
return \Pest\TestSuite::getInstance()->test;

View file

@ -24,6 +24,7 @@ use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
use function Stancl\Tenancy\Tests\pest;
use Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper;
abstract class TestCase extends \Orchestra\Testbench\TestCase
{
@ -38,6 +39,10 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
ini_set('memory_limit', '1G');
TenancyServiceProvider::$registerForgetTenantParameterListener = true;
TenancyServiceProvider::$migrateFreshOverride = true;
TenancyServiceProvider::$adjustCacheManagerUsing = null;
Redis::connection('default')->flushdb();
Redis::connection('cache')->flushdb();
Artisan::call('cache:clear memcached'); // flush memcached
@ -180,6 +185,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
// to manually register bootstrappers as singletons here.
$app->singleton(RedisTenancyBootstrapper::class);
$app->singleton(CacheTenancyBootstrapper::class);
$app->singleton(DatabaseCacheBootstrapper::class);
$app->singleton(BroadcastingConfigBootstrapper::class);
$app->singleton(BroadcastChannelPrefixBootstrapper::class);
$app->singleton(PostgresRLSBootstrapper::class);