mirror of
https://github.com/archtechx/tenancy.git
synced 2026-08-06 05:54:03 +00:00
696 lines
24 KiB
PHP
696 lines
24 KiB
PHP
<?php
|
|
|
|
use Stancl\JobPipeline\JobPipeline;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\File;
|
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Stancl\Tenancy\Events\TenancyEnded;
|
|
use Stancl\Tenancy\Events\TenantCreated;
|
|
use Stancl\Tenancy\Events\TenantDeleted;
|
|
use Stancl\Tenancy\Events\DeletingTenant;
|
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
|
use Stancl\Tenancy\Jobs\CreateStorageSymlinks;
|
|
use Stancl\Tenancy\Jobs\RemoveStorageSymlinks;
|
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|
use Stancl\Tenancy\Jobs\DeleteTenantStorage;
|
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
|
use function Stancl\Tenancy\Tests\pest;
|
|
|
|
beforeEach(function () {
|
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
|
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
|
});
|
|
|
|
test('local storage public urls are generated correctly', function () {
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%'
|
|
]);
|
|
|
|
$tenant1 = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
$tenant1StorageUrl = 'http://localhost/public-' . $tenant1->getKey().'/';
|
|
$tenant2StorageUrl = 'http://localhost/public-' . $tenant2->getKey().'/';
|
|
|
|
tenancy()->initialize($tenant1);
|
|
|
|
$this->assertEquals(
|
|
$tenant1StorageUrl,
|
|
Storage::disk('public')->url('')
|
|
);
|
|
|
|
Storage::disk('public')->put($tenant1FileName = 'tenant1.txt', 'text');
|
|
|
|
$this->assertEquals(
|
|
$tenant1StorageUrl . $tenant1FileName,
|
|
Storage::disk('public')->url($tenant1FileName)
|
|
);
|
|
|
|
tenancy()->initialize($tenant2);
|
|
|
|
$this->assertEquals(
|
|
$tenant2StorageUrl,
|
|
Storage::disk('public')->url('')
|
|
);
|
|
|
|
Storage::disk('public')->put($tenant2FileName = 'tenant2.txt', 'text');
|
|
|
|
$this->assertEquals(
|
|
$tenant2StorageUrl . $tenant2FileName,
|
|
Storage::disk('public')->url($tenant2FileName)
|
|
);
|
|
});
|
|
|
|
test('files can get fetched using the storage url', function() {
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%'
|
|
]);
|
|
|
|
$tenant1 = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
|
|
pest()->artisan('tenants:link');
|
|
|
|
// First tenant
|
|
tenancy()->initialize($tenant1);
|
|
Storage::disk('public')->put($tenantFileName = 'tenant1.txt', $tenantKey = $tenant1->getTenantKey());
|
|
|
|
$url = Storage::disk('public')->url($tenantFileName);
|
|
$tenantDiskName = str(config('tenancy.filesystem.url_override.public'))->replace('%tenant%', $tenantKey);
|
|
$hostname = str($url)->before($tenantDiskName);
|
|
$parsedUrl = str($url)->after($hostname);
|
|
|
|
expect(file_get_contents(public_path($parsedUrl)))->toBe($tenantKey);
|
|
|
|
// Second tenant
|
|
tenancy()->initialize($tenant2);
|
|
Storage::disk('public')->put($tenantFileName = 'tenant2.txt', $tenantKey = $tenant2->getTenantKey());
|
|
|
|
$url = Storage::disk('public')->url($tenantFileName);
|
|
$tenantDiskName = str(config('tenancy.filesystem.url_override.public'))->replace('%tenant%', $tenantKey);
|
|
$hostname = str($url)->before($tenantDiskName);
|
|
$parsedUrl = str($url)->after($hostname);
|
|
|
|
expect(file_get_contents(public_path($parsedUrl)))->toBe($tenantKey);
|
|
|
|
// Central
|
|
tenancy()->end();
|
|
Storage::disk('public')->put($centralFileName = 'central.txt', $centralFileContent = 'central');
|
|
|
|
pest()->artisan('storage:link');
|
|
$url = Storage::disk('public')->url($centralFileName);
|
|
|
|
expect(file_get_contents(public_path($url)))->toBe($centralFileContent);
|
|
});
|
|
|
|
test('storage_path helper does not change if suffix_storage_path is off', function() {
|
|
$originalStoragePath = storage_path();
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class],
|
|
'tenancy.filesystem.suffix_storage_path' => false,
|
|
]);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
$this->assertEquals($originalStoragePath, storage_path());
|
|
});
|
|
|
|
test('links to storage disks with a configured root are suffixed if not overridden', function() {
|
|
config([
|
|
'filesystems.disks.public.root' => 'http://sample-s3-url.com/my-app',
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.filesystem.root_override.public' => null,
|
|
'tenancy.filesystem.url_override.public' => null,
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
|
|
$expectedStoragePath = storage_path() . '/tenant' . $tenant->getTenantKey(); // /tenant = suffix base
|
|
|
|
tenancy()->initialize($tenant);
|
|
|
|
// Check suffixing logic
|
|
expect(storage_path())->toEqual($expectedStoragePath);
|
|
});
|
|
|
|
test('create and delete storage symlinks jobs work', function() {
|
|
Event::listen(
|
|
TenantCreated::class,
|
|
JobPipeline::make([CreateStorageSymlinks::class])->send(function (TenantCreated $event) {
|
|
return $event->tenant;
|
|
})->toListener()
|
|
);
|
|
|
|
Event::listen(
|
|
TenantDeleted::class,
|
|
JobPipeline::make([RemoveStorageSymlinks::class])->send(function (TenantDeleted $event) {
|
|
return $event->tenant;
|
|
})->toListener()
|
|
);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.filesystem.suffix_base' => 'tenant-',
|
|
'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/',
|
|
'tenancy.filesystem.url_override.public' => 'public-%tenant%'
|
|
]);
|
|
|
|
/** @var Tenant $tenant */
|
|
$tenant = Tenant::create();
|
|
|
|
tenancy()->initialize($tenant);
|
|
|
|
$tenantKey = $tenant->getTenantKey();
|
|
|
|
$this->assertDirectoryExists(storage_path("app/public"));
|
|
$this->assertEquals(storage_path("app/public/"), readlink(public_path("public-$tenantKey")));
|
|
|
|
$tenant->delete();
|
|
|
|
$this->assertDirectoryDoesNotExist(public_path("public-$tenantKey"));
|
|
});
|
|
|
|
test('tenant storage gets deleted during tenant deletion when the DeletingTenant pipeline contains DeleteTenantStorage', function() {
|
|
Event::listen(DeletingTenant::class,
|
|
JobPipeline::make([DeleteTenantStorage::class])->send(function (DeletingTenant $event) {
|
|
return $event->tenant;
|
|
})->shouldBeQueued(false)->toListener()
|
|
);
|
|
|
|
$centralStoragePath = storage_path();
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
// FilesystemTenancyBootstrapper not enabled,
|
|
// tenant and central storage path is the same,
|
|
// the storage deletion will be skipped.
|
|
$tenantStoragePath = storage_path();
|
|
expect($tenantStoragePath)->toBe($centralStoragePath);
|
|
expect(File::isDirectory($centralStoragePath))->toBeTrue();
|
|
tenant()->delete();
|
|
|
|
expect(File::isDirectory($centralStoragePath))->toBeTrue();
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class],
|
|
'tenancy.filesystem.suffix_storage_path' => false,
|
|
]);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
$tenantStoragePath = storage_path();
|
|
|
|
// FilesystemTenancyBootstrapper enabled,
|
|
// but tenant and central storage path is still the same
|
|
// because suffix_storage_path is false.
|
|
// The storage deletion will be skipped.
|
|
expect($tenantStoragePath)->toBe($centralStoragePath);
|
|
expect(File::isDirectory($centralStoragePath))->toBeTrue();
|
|
tenant()->delete();
|
|
|
|
expect(File::isDirectory($centralStoragePath))->toBeTrue();
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class],
|
|
'tenancy.filesystem.suffix_storage_path' => true,
|
|
]);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
$tenantStoragePath = storage_path();
|
|
|
|
// FilesystemTenancyBootstrapper enabled,
|
|
// suffix_storage_path enabled, so the two paths are distinct.
|
|
// Tenant storage will be deleted.
|
|
expect($tenantStoragePath)->not()->toBe($centralStoragePath);
|
|
expect(File::isDirectory($tenantStoragePath))->toBeTrue();
|
|
|
|
tenant()->delete();
|
|
|
|
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
|
expect(File::isDirectory($centralStoragePath))->toBeTrue();
|
|
});
|
|
|
|
test('the framework/cache directory is created when storage_path is scoped', function (bool $suffixStoragePath) {
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.filesystem.suffix_storage_path' => $suffixStoragePath
|
|
]);
|
|
|
|
$centralStoragePath = storage_path();
|
|
|
|
tenancy()->initialize($tenant = Tenant::create());
|
|
|
|
if ($suffixStoragePath) {
|
|
expect(storage_path('framework/cache'))->toBe($centralStoragePath . "/tenant{$tenant->id}/framework/cache");
|
|
expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeTrue();
|
|
} else {
|
|
expect(storage_path('framework/cache'))->toBe($centralStoragePath . '/framework/cache');
|
|
expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeFalse();
|
|
}
|
|
})->with([true, false]);
|
|
|
|
test('scoped disks are scoped per tenant', function () {
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'filesystems.disks.scoped_disk' => [
|
|
'driver' => 'scoped',
|
|
'disk' => 'public',
|
|
'prefix' => 'scoped_disk_prefix',
|
|
],
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
|
|
Storage::disk('scoped_disk')->put('foo.txt', 'central');
|
|
expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('central');
|
|
expect(file_get_contents(storage_path() . "/app/public/scoped_disk_prefix/foo.txt"))->toBe('central');
|
|
|
|
tenancy()->initialize($tenant);
|
|
|
|
expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe(null);
|
|
Storage::disk('scoped_disk')->put('foo.txt', 'tenant');
|
|
expect(file_get_contents(storage_path() . "/app/public/scoped_disk_prefix/foo.txt"))->toBe('tenant');
|
|
expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('tenant');
|
|
|
|
tenancy()->end();
|
|
|
|
expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('central');
|
|
Storage::disk('scoped_disk')->put('foo.txt', 'central2');
|
|
expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('central2');
|
|
|
|
expect(file_get_contents(storage_path() . "/app/public/scoped_disk_prefix/foo.txt"))->toBe('central2');
|
|
expect(file_get_contents(storage_path() . "/tenant{$tenant->id}/app/public/scoped_disk_prefix/foo.txt"))->toBe('tenant');
|
|
});
|
|
|
|
test('file cache stores are separated per tenant', function () {
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['file'],
|
|
// Laravel's default 'file' store config (set explicitly here just for clarity).
|
|
'cache.stores.file' => [
|
|
'driver' => 'file',
|
|
'path' => storage_path('framework/cache/data'),
|
|
'lock_path' => storage_path('framework/cache/data'),
|
|
],
|
|
]);
|
|
|
|
$tenant1 = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
|
|
Cache::store('file')->put('key', 'central');
|
|
|
|
tenancy()->initialize($tenant1);
|
|
|
|
expect(Cache::store('file')->get('key'))->toBeNull();
|
|
Cache::store('file')->put('key', 'tenant1');
|
|
|
|
tenancy()->initialize($tenant2);
|
|
|
|
expect(Cache::store('file')->get('key'))->toBeNull();
|
|
Cache::store('file')->put('key', 'tenant2');
|
|
|
|
tenancy()->initialize($tenant1);
|
|
|
|
expect(Cache::store('file')->get('key'))->toBe('tenant1');
|
|
|
|
tenancy()->end();
|
|
|
|
expect(Cache::store('file')->get('key'))->toBe('central');
|
|
});
|
|
|
|
test('central cache is not lost when tenancy ends', function () {
|
|
$path = storage_path('framework/cache/foo_file');
|
|
File::deleteDirectory($path);
|
|
|
|
// Use a separate 'foo' store rather than reconfiguring 'file'.
|
|
// TestCase::setUp() calls `cache:clear file`, which resolves the 'file' store and leaves it
|
|
// in the CacheManager with the default path. A later config() call can't mutate that, so a test
|
|
// reconfiguring 'file' would keep using storage/framework/cache/data and pass either way.
|
|
// The same applies to the other tests below that configure separate cache stores rather than using 'file'.
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
'lock_path' => $path,
|
|
],
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
|
|
Cache::store('foo_file')->put('foo', 'central');
|
|
|
|
// Just initialize and revert tenancy to trigger FilesystemTenancyBootstrapper
|
|
tenancy()->initialize($tenant);
|
|
tenancy()->end();
|
|
|
|
// Nothing deleted the 'foo' entry, so its value should stay 'central' even after reverting tenancy.
|
|
// FilesystemTenancyBootstrapper::revert() makes the store use its original configured path.
|
|
expect(Cache::store('foo_file')->get('foo'))->toBe('central');
|
|
|
|
File::deleteDirectory($path);
|
|
});
|
|
|
|
test('file cache stores with different configured paths do not share a directory', function () {
|
|
$fooPath = storage_path('framework/cache/foo');
|
|
$barPath = storage_path('framework/cache/bar');
|
|
|
|
File::deleteDirectory($fooPath);
|
|
File::deleteDirectory($barPath);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file', 'bar_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $fooPath,
|
|
'lock_path' => $fooPath,
|
|
],
|
|
'cache.stores.bar_file' => [
|
|
'driver' => 'file',
|
|
'path' => $barPath,
|
|
'lock_path' => $barPath,
|
|
],
|
|
]);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
Cache::store('foo_file')->put('key', 'foo');
|
|
Cache::store('bar_file')->put('key', 'bar');
|
|
|
|
// Each store uses its own directory in the tenant's context, so they don't read or overwrite
|
|
// each other's entries, and flushing one doesn't empty the other.
|
|
expect(Cache::store('foo_file')->get('key'))->toBe('foo');
|
|
expect(Cache::store('bar_file')->get('key'))->toBe('bar');
|
|
|
|
Cache::store('bar_file')->flush();
|
|
|
|
expect(Cache::store('foo_file')->get('key'))->toBe('foo');
|
|
|
|
tenancy()->end();
|
|
|
|
File::deleteDirectory($fooPath);
|
|
File::deleteDirectory($barPath);
|
|
});
|
|
|
|
test('a configured lock_path is scoped separately from path', function () {
|
|
// A store can configure path and lock_path as two different directories.
|
|
// Locks should use lock_path IF CONFIGURED, not fall back into the same directory as path.
|
|
$centralStoragePath = storage_path();
|
|
$path = "{$centralStoragePath}/framework/cache/foo";
|
|
$lockPath = "{$centralStoragePath}/framework/cache/foo_locks";
|
|
|
|
// Delete the directories before running assertions, not just after.
|
|
// Cache::lock() below defaults to a lock that never expires,
|
|
// and this test never releases it, so a stale lock left over from
|
|
// a previous run would make lock() fail forever.
|
|
File::deleteDirectory($path);
|
|
File::deleteDirectory($lockPath);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
'lock_path' => $lockPath,
|
|
],
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
tenancy()->initialize($tenant);
|
|
|
|
expect(Cache::store('foo_file')->lock('foo')->get())->toBeTrue();
|
|
|
|
$tenantPath = "{$centralStoragePath}/tenant{$tenant->id}/framework/cache/foo";
|
|
$tenantLockPath = "{$centralStoragePath}/tenant{$tenant->id}/framework/cache/foo_locks";
|
|
|
|
// Taking the lock creates the tenant's scoped lock_path directory.
|
|
// Nothing wrote a cache entry, so the tenant's scoped path directory doesn't exist (path and lock_path are scoped separately).
|
|
expect(File::isDirectory($tenantLockPath))->toBeTrue();
|
|
expect(File::isDirectory($tenantPath))->toBeFalse();
|
|
|
|
tenancy()->end();
|
|
|
|
expect(Cache::store('foo_file')->lock('foo')->get())->toBeTrue();
|
|
|
|
// After reverting, locks go back to the configured central lock_path.
|
|
expect(File::isDirectory($lockPath))->toBeTrue();
|
|
|
|
File::deleteDirectory($path);
|
|
File::deleteDirectory($lockPath);
|
|
});
|
|
|
|
test('a cache store without a configured lock_path is scoped without error', function () {
|
|
// lock_path is optional -- Laravel falls back to using path for locks when it's not set.
|
|
// This test covers that path through scopeCache() specifically (which none of the other tests exercise).
|
|
$path = storage_path('framework/cache/foo');
|
|
File::deleteDirectory($path);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
// No 'lock_path' key at all.
|
|
],
|
|
]);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
expect(Cache::store('foo_file')->put('key', 'tenant'))->toBeTrue();
|
|
expect(Cache::store('foo_file')->lock('foo')->get())->toBeTrue();
|
|
|
|
tenancy()->end();
|
|
|
|
expect(Cache::store('foo_file')->put('key', 'central'))->toBeTrue();
|
|
expect(Cache::store('foo_file')->lock('foo')->get())->toBeTrue();
|
|
|
|
File::deleteDirectory($path);
|
|
});
|
|
|
|
test('a cache store using a path not based on storage_path() is suffixed in place, not moved under the tenant storage path', function () {
|
|
// $path below doesn't start with the central storage path, so tenantCachePath() has no storage
|
|
// path prefix to swap for the tenant's -- it appends the tenant suffix directly to $path instead.
|
|
// Check that tenant isolation still works for a store configured like this, and that its cache
|
|
// ends up at $path itself, not under the tenant's storage path.
|
|
$path = '/tmp/tenancy-cache-test';
|
|
File::deleteDirectory($path);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
'lock_path' => $path,
|
|
],
|
|
]);
|
|
|
|
$tenant1 = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
|
|
tenancy()->initialize($tenant1);
|
|
Cache::store('foo_file')->put('key', 'tenant1');
|
|
|
|
tenancy()->initialize($tenant2);
|
|
expect(Cache::store('foo_file')->get('key'))->toBeNull();
|
|
|
|
tenancy()->initialize($tenant1);
|
|
expect(Cache::store('foo_file')->get('key'))->toBe('tenant1');
|
|
|
|
expect(File::isDirectory("{$path}/tenant{$tenant1->id}"))->toBeTrue();
|
|
|
|
// storage_path() is already scoped to tenant1 here (storagePath() runs before scopeCache() in
|
|
// bootstrap()), so this is the tenant's default cache directory. Nothing should be there, since
|
|
// foo_file is configured with its own $path that isn't storage_path()-based.
|
|
expect(File::isDirectory(storage_path('framework/cache/data')))->toBeFalse();
|
|
|
|
tenancy()->end();
|
|
|
|
File::deleteDirectory($path);
|
|
});
|
|
|
|
test('cache stores are not scoped at all when scope_cache is disabled', function () {
|
|
$path = storage_path('framework/cache/foo');
|
|
File::deleteDirectory($path);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.filesystem.scope_cache' => false,
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
'lock_path' => $path,
|
|
],
|
|
]);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
// The store keeps its configured path, so tenants share a single cache directory
|
|
expect(config('cache.stores.foo_file.path'))->toBe($path);
|
|
Cache::store('foo_file')->put('key', 'tenant');
|
|
expect(File::isDirectory($path))->toBeTrue();
|
|
|
|
tenancy()->end();
|
|
|
|
expect(config('cache.stores.foo_file.path'))->toBe($path);
|
|
expect(Cache::store('foo_file')->get('key'))->toBe('tenant');
|
|
|
|
File::deleteDirectory($path);
|
|
});
|
|
|
|
test('a store added to tenancy.cache.stores while tenancy is initialized is skipped', function () {
|
|
$fooPath = storage_path('framework/cache/foo');
|
|
$barPath = storage_path('framework/cache/bar');
|
|
|
|
File::deleteDirectory($fooPath);
|
|
File::deleteDirectory($barPath);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $fooPath,
|
|
'lock_path' => $fooPath,
|
|
],
|
|
'cache.stores.bar_file' => [
|
|
'driver' => 'file',
|
|
'path' => $barPath,
|
|
'lock_path' => $barPath,
|
|
],
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
tenancy()->initialize($tenant);
|
|
|
|
expect(config('cache.stores.foo_file.path'))->toBe(storage_path('framework/cache/foo'));
|
|
|
|
// bar_file wasn't in tenancy.cache.stores during bootstrap, so no original path was stored for it
|
|
config(['tenancy.cache.stores' => ['foo_file', 'bar_file']]);
|
|
|
|
tenancy()->end();
|
|
|
|
// foo_file gets reverted, but bar_file is left with the path it's configured with
|
|
expect(config('cache.stores.foo_file.path'))->toBe($fooPath);
|
|
expect(config('cache.stores.bar_file.path'))->toBe($barPath);
|
|
|
|
File::deleteDirectory($fooPath);
|
|
File::deleteDirectory($barPath);
|
|
});
|
|
|
|
test('a store listed in tenancy.cache.stores that has no cache config is skipped', function () {
|
|
$path = storage_path('framework/cache/foo');
|
|
File::deleteDirectory($path);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file', 'nonexistent_store'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
'lock_path' => $path,
|
|
],
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
|
|
// The store doesn't exist in cache.stores, so there's nothing to scope (resolving it would throw)
|
|
tenancy()->initialize($tenant);
|
|
|
|
expect(config('cache.stores.foo_file.path'))->toBe(storage_path('framework/cache/foo'));
|
|
expect(config('cache.stores.nonexistent_store'))->toBeNull();
|
|
|
|
tenancy()->end();
|
|
|
|
expect(config('cache.stores.foo_file.path'))->toBe($path);
|
|
|
|
File::deleteDirectory($path);
|
|
});
|
|
|
|
test('the original cache paths are only stored on the first bootstrap', function () {
|
|
// If bootstrap() throws after scopeCache(), the bootstrapper isn't reverted, so the config still
|
|
// holds the previous tenant's scoped paths during the next bootstrap. Storing the paths again there
|
|
// would lose the central ones forever.
|
|
$centralStoragePath = storage_path();
|
|
$path = "{$centralStoragePath}/framework/cache/foo";
|
|
File::deleteDirectory($path);
|
|
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
FilesystemTenancyBootstrapper::class,
|
|
],
|
|
'tenancy.cache.stores' => ['foo_file'],
|
|
'cache.stores.foo_file' => [
|
|
'driver' => 'file',
|
|
'path' => $path,
|
|
'lock_path' => $path,
|
|
],
|
|
]);
|
|
|
|
$tenant1 = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
|
|
// Make scopeSessions() fail for tenant1 by putting a file where its sessions directory should be.
|
|
// scopeSessions() runs after scopeCache(), so the cache paths are already scoped when bootstrap() throws.
|
|
$sessionsPath = "{$centralStoragePath}/tenant{$tenant1->id}/framework/sessions";
|
|
File::deleteDirectory($sessionsPath);
|
|
File::ensureDirectoryExists(dirname($sessionsPath));
|
|
File::put($sessionsPath, '');
|
|
|
|
expect(fn () => tenancy()->initialize($tenant1))->toThrow(Exception::class, "Unable to create tenant session directory [{$sessionsPath}].");
|
|
|
|
// bootstrap() threw, so the bootstrapper isn't in initializedBootstrappers and revert() gets skipped for it.
|
|
expect(config('cache.stores.foo_file.path'))->toBe("{$centralStoragePath}/tenant{$tenant1->id}/framework/cache/foo");
|
|
|
|
tenancy()->initialize($tenant2);
|
|
|
|
expect(config('cache.stores.foo_file.path'))->toBe("{$centralStoragePath}/tenant{$tenant2->id}/framework/cache/foo");
|
|
|
|
tenancy()->end();
|
|
|
|
expect(config('cache.stores.foo_file.path'))->toBe($path);
|
|
|
|
File::deleteDirectory("{$centralStoragePath}/tenant{$tenant1->id}");
|
|
File::deleteDirectory($path);
|
|
});
|