1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-05-06 17:44:04 +00:00
tenancy/tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php
Samuel Stancl 5bb76e1421
refactor
1. Remove the CreateTenantStorage job altogether since as the docblock
   says the class should be redundant now that
   FilesystemTenancyBootstrapper creates this path automatically when
   storage_path suffixing is enabled
2. Remove docblock on the DeleteTenantStorage job - a class's docblock
   should describe what it does, not how it handles edge cases. Here
   the former isn't even necessary and the latter is well explained by
   comments in the implementation.
3. Remove the CreateTenantStorage test following the class's removal,
   the test would pass on its own even without the job with just
   tenancy initialization as mentioned above.
4. Slightly improve the structure of the DeleteTenantStorage job and
   deprecated listener
5. Improve deprecation notices so they include full steps for upgrading
   to the new approach.
2026-04-20 18:25:04 +02:00

296 lines
10 KiB
PHP

<?php
use Stancl\JobPipeline\JobPipeline;
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 can get deleted after the tenant when DeletingTenant listens to 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($tenantStoragePath))->toBeTrue();
tenant()->delete();
expect(File::isDirectory($tenantStoragePath))->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($tenantStoragePath))->toBeTrue();
tenant()->delete();
expect(File::isDirectory($tenantStoragePath))->toBeTrue();
config([
'tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class],
'tenancy.filesystem.suffix_storage_path' => true,
]);
tenancy()->initialize(Tenant::create());
$tenantStoragePath = storage_path();
expect($centralStoragePath)->not()->toBe($tenantStoragePath);
expect(File::isDirectory($tenantStoragePath))->toBeTrue();
tenant()->delete();
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
});
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');
});