mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 09:34:05 +00:00
Merge branch 'may25' into syncable-scoping
This commit is contained in:
commit
820faf1e25
101 changed files with 1586 additions and 580 deletions
|
|
@ -11,6 +11,7 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|||
use Stancl\Tenancy\Actions\CreateStorageSymlinksAction;
|
||||
use Stancl\Tenancy\Actions\RemoveStorageSymlinksAction;
|
||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
|
|
@ -35,11 +36,15 @@ test('create storage symlinks action works', function() {
|
|||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
$this->assertDirectoryDoesNotExist($publicPath = public_path("public-$tenantKey"));
|
||||
// The symlink doesn't exist
|
||||
expect(is_link($publicPath = public_path("public-$tenantKey")))->toBeFalse();
|
||||
expect(file_exists($publicPath))->toBeFalse();
|
||||
|
||||
(new CreateStorageSymlinksAction)($tenant);
|
||||
|
||||
$this->assertDirectoryExists($publicPath);
|
||||
// The symlink exists and is valid
|
||||
expect(is_link($publicPath = public_path("public-$tenantKey")))->toBeTrue();
|
||||
expect(file_exists($publicPath))->toBeTrue();
|
||||
$this->assertEquals(storage_path("app/public/"), readlink($publicPath));
|
||||
});
|
||||
|
||||
|
|
@ -61,9 +66,48 @@ test('remove storage symlinks action works', function() {
|
|||
|
||||
(new CreateStorageSymlinksAction)($tenant);
|
||||
|
||||
$this->assertDirectoryExists($publicPath = public_path("public-$tenantKey"));
|
||||
// The symlink exists and is valid
|
||||
expect(is_link($publicPath = public_path("public-$tenantKey")))->toBeTrue();
|
||||
expect(file_exists($publicPath))->toBeTrue();
|
||||
|
||||
(new RemoveStorageSymlinksAction)($tenant);
|
||||
|
||||
$this->assertDirectoryDoesNotExist($publicPath);
|
||||
// The symlink doesn't exist
|
||||
expect(is_link($publicPath))->toBeFalse();
|
||||
expect(file_exists($publicPath))->toBeFalse();
|
||||
});
|
||||
|
||||
test('removing tenant symlinks works even if the symlinks are invalid', function() {
|
||||
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();
|
||||
$tenantKey = $tenant->getTenantKey();
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
(new CreateStorageSymlinksAction)($tenant);
|
||||
|
||||
// The symlink exists and is valid
|
||||
expect(is_link($publicPath = public_path("public-$tenantKey")))->toBeTrue();
|
||||
expect(file_exists($publicPath))->toBeTrue();
|
||||
|
||||
// Make the symlink invalid by deleting the tenant storage directory
|
||||
$storagePath = storage_path();
|
||||
File::deleteDirectory($storagePath);
|
||||
|
||||
// The symlink still exists, but isn't valid
|
||||
expect(is_link($publicPath))->toBeTrue();
|
||||
expect(file_exists($publicPath))->toBeFalse();
|
||||
|
||||
(new RemoveStorageSymlinksAction)($tenant);
|
||||
|
||||
expect(is_link($publicPath))->toBeFalse();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use Stancl\Tenancy\Events\TenancyInitialized;
|
|||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use function Stancl\Tenancy\Tests\withTenantDatabases;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
|||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->mockConsoleOutput = false;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
|||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|||
use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
|
||||
use Stancl\Tenancy\Events\TenancyEnded;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['tenancy.bootstrappers' => [CacheTagsBootstrapper::class]]);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use Stancl\Tenancy\Jobs\CreateDatabase;
|
|||
use Stancl\Tenancy\Listeners;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
/**
|
||||
* This collection of regression tests verifies that SessionTenancyBootstrapper
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ use Stancl\Tenancy\Events\TenancyEnded;
|
|||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|||
use Stancl\Tenancy\Listeners\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);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteBootstrapper;
|
|||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||
FortifyRouteBootstrapper::$passTenantParameter = true;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
FortifyRouteBootstrapper::$passTenantParameter = true;
|
||||
FortifyRouteBootstrapper::$fortifyRedirectMap = [];
|
||||
FortifyRouteBootstrapper::$fortifyHome = 'tenant.dashboard';
|
||||
FortifyRouteBootstrapper::$passQueryParameter = false;
|
||||
});
|
||||
|
||||
test('fortify route tenancy bootstrapper updates fortify config correctly', function() {
|
||||
|
|
@ -25,53 +33,31 @@ test('fortify route tenancy bootstrapper updates fortify config correctly', func
|
|||
return true;
|
||||
})->name($homeRouteName = 'home');
|
||||
|
||||
Route::get('/{tenant}/home', function () {
|
||||
return true;
|
||||
})->name($pathIdHomeRouteName = 'tenant.home');
|
||||
|
||||
Route::get('/welcome', function () {
|
||||
return true;
|
||||
})->name($welcomeRouteName = 'welcome');
|
||||
|
||||
Route::get('/{tenant}/welcome', function () {
|
||||
return true;
|
||||
})->name($pathIdWelcomeRouteName = 'path.welcome');
|
||||
|
||||
FortifyRouteBootstrapper::$fortifyHome = $homeRouteName;
|
||||
FortifyRouteBootstrapper::$fortifyRedirectMap['login'] = $welcomeRouteName;
|
||||
|
||||
// Make login redirect to the central welcome route
|
||||
FortifyRouteBootstrapper::$fortifyRedirectMap['login'] = [
|
||||
'route_name' => $welcomeRouteName,
|
||||
'context' => Context::CENTRAL,
|
||||
];
|
||||
expect(config('fortify.home'))->toBe($originalFortifyHome);
|
||||
expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
|
||||
|
||||
FortifyRouteBootstrapper::$passTenantParameter = true;
|
||||
tenancy()->initialize($tenant = Tenant::create());
|
||||
// The bootstraper makes fortify.home always receive the tenant parameter
|
||||
expect(config('fortify.home'))->toBe('http://localhost/home?tenant=' . $tenant->getTenantKey());
|
||||
|
||||
// The login redirect route has the central context specified, so it doesn't receive the tenant parameter
|
||||
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome']);
|
||||
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome?tenant=' . $tenant->getTenantKey()]);
|
||||
|
||||
tenancy()->end();
|
||||
expect(config('fortify.home'))->toBe($originalFortifyHome);
|
||||
expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
|
||||
|
||||
// Making a route's context will pass the tenant parameter to the route
|
||||
FortifyRouteBootstrapper::$fortifyRedirectMap['login']['context'] = Context::TENANT;
|
||||
|
||||
FortifyRouteBootstrapper::$passTenantParameter = false;
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome?tenant=' . $tenant->getTenantKey()]);
|
||||
|
||||
// Make the home and login route accept the tenant as a route parameter
|
||||
// To confirm that tenant route parameter gets filled automatically too (path identification works as well as query string)
|
||||
FortifyRouteBootstrapper::$fortifyHome = $pathIdHomeRouteName;
|
||||
FortifyRouteBootstrapper::$fortifyRedirectMap['login']['route_name'] = $pathIdWelcomeRouteName;
|
||||
expect(config('fortify.home'))->toBe('http://localhost/home');
|
||||
expect(config('fortify.redirects'))->toEqual(['login' => 'http://localhost/welcome']);
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
expect(config('fortify.home'))->toBe("http://localhost/{$tenant->getTenantKey()}/home");
|
||||
expect(config('fortify.redirects'))->toEqual(['login' => "http://localhost/{$tenant->getTenantKey()}/welcome"]);
|
||||
expect(config('fortify.home'))->toBe($originalFortifyHome);
|
||||
expect(config('fortify.redirects'))->toBe($originalFortifyRedirects);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,18 +10,23 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||
RootUrlBootstrapper::$rootUrlOverride = null;
|
||||
RootUrlBootstrapper::$rootUrlOverrideInTests = true;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
RootUrlBootstrapper::$rootUrlOverride = null;
|
||||
RootUrlBootstrapper::$rootUrlOverrideInTests = false;
|
||||
});
|
||||
|
||||
test('root url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one after tenancy ends', function() {
|
||||
test('root url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one when ending tenancy', function() {
|
||||
config(['tenancy.bootstrappers' => [RootUrlBootstrapper::class]]);
|
||||
|
||||
Route::group([
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Routing\UrlGenerator;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
|
@ -12,19 +13,26 @@ use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
|||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Illuminate\Routing\Exceptions\UrlGenerationException;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
|
||||
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = false;
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = false;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = false;
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = false;
|
||||
});
|
||||
|
||||
test('url generator bootstrapper swaps the url generator instance correctly', function() {
|
||||
|
|
@ -41,42 +49,246 @@ test('url generator bootstrapper swaps the url generator instance correctly', fu
|
|||
->not()->toBeInstanceOf(TenancyUrlGenerator::class);
|
||||
});
|
||||
|
||||
test('url generator bootstrapper can prefix route names passed to the route helper', function() {
|
||||
Route::get('/central/home', fn () => route('home'))->name('home');
|
||||
// Tenant route name prefix is 'tenant.' by default
|
||||
Route::get('/{tenant}/home', fn () => route('tenant.home'))->name('tenant.home')->middleware(['tenant', InitializeTenancyByPath::class]);
|
||||
test('tenancy url generator can prefix route names passed to the route helper', function() {
|
||||
config([
|
||||
'tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_route_name_prefix' => 'custom_prefix.',
|
||||
]);
|
||||
|
||||
Route::get('/central/home', fn () => '')->name('home');
|
||||
Route::get('/tenant/home', fn () => '')->name('custom_prefix.home');
|
||||
|
||||
$tenant = Tenant::create();
|
||||
$tenantKey = $tenant->getTenantKey();
|
||||
$centralRouteUrl = route('home');
|
||||
$tenantRouteUrl = route('tenant.home', ['tenant' => $tenantKey]);
|
||||
TenancyUrlGenerator::$bypassParameter = 'bypassParameter';
|
||||
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// Route names don't get prefixed when TenancyUrlGenerator::$prefixRouteNames is false
|
||||
expect(route('home'))->not()->toBe($centralRouteUrl);
|
||||
// When TenancyUrlGenerator::$passTenantParameterToRoutes is true (default)
|
||||
// The route helper receives the tenant parameter
|
||||
// So in order to generate central URL, we have to pass the bypass parameter
|
||||
expect(route('home', ['bypassParameter' => true]))->toBe($centralRouteUrl);
|
||||
|
||||
// Route names don't get prefixed when TenancyUrlGenerator::$prefixRouteNames is false (default)
|
||||
expect(route('home'))->toBe('http://localhost/central/home');
|
||||
|
||||
// When $prefixRouteNames is true, the route name passed to the route() helper ('home') gets prefixed automatically.
|
||||
TenancyUrlGenerator::$prefixRouteNames = true;
|
||||
// The $prefixRouteNames property is true
|
||||
// The route name passed to the route() helper ('home') gets prefixed prefixed with 'tenant.' automatically
|
||||
expect(route('home'))->toBe($tenantRouteUrl);
|
||||
|
||||
// The 'tenant.home' route name doesn't get prefixed because it is already prefixed with 'tenant.'
|
||||
// Also, the route receives the tenant parameter automatically
|
||||
expect(route('tenant.home'))->toBe($tenantRouteUrl);
|
||||
expect(route('home'))->toBe('http://localhost/tenant/home');
|
||||
|
||||
// The 'custom_prefix.home' route name doesn't get prefixed -- it is already prefixed with 'custom_prefix.'
|
||||
expect(route('custom_prefix.home'))->toBe('http://localhost/tenant/home');
|
||||
|
||||
// Ending tenancy reverts route() behavior changes
|
||||
tenancy()->end();
|
||||
|
||||
expect(route('home'))->toBe($centralRouteUrl);
|
||||
expect(route('home'))->toBe('http://localhost/central/home');
|
||||
});
|
||||
|
||||
test('path identification route helper behavior', function (bool $addTenantParameterToDefaults, bool $passTenantParameterToRoutes) {
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = $addTenantParameterToDefaults;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = $passTenantParameterToRoutes;
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
||||
Route::get('/{tenant}/home', fn () => tenant('id'))
|
||||
->name('tenant.home')
|
||||
->middleware([InitializeTenancyByPath::class]);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
if (! $addTenantParameterToDefaults && ! $passTenantParameterToRoutes) {
|
||||
expect(fn () => route('tenant.home'))->toThrow(UrlGenerationException::class, 'Missing parameter: tenant');
|
||||
} else {
|
||||
// If at least *one* of the approaches was used, the parameter will make its way to the route
|
||||
expect(route('tenant.home'))->toBe("http://localhost/{$tenant->id}/home");
|
||||
pest()->get(route('tenant.home'))->assertSee($tenant->id);
|
||||
}
|
||||
})->with([true, false]) // UrlGeneratorBootstrapper::$addTenantParameterToDefaults
|
||||
->with([true, false]); // TenancyUrlGenerator::$passTenantParameterToRoutes
|
||||
|
||||
test('request data identification route helper behavior', function (bool $addTenantParameterToDefaults, bool $passTenantParameterToRoutes) {
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = $addTenantParameterToDefaults;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = $passTenantParameterToRoutes;
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
||||
Route::get('/tenant/home', fn () => tenant('id'))
|
||||
->name('tenant.home')
|
||||
->middleware([InitializeTenancyByRequestData::class]);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
if ($passTenantParameterToRoutes) {
|
||||
// Only $passTenantParameterToRoutes has an effect, defaults do not affect request data URL generation
|
||||
expect(route('tenant.home'))->toBe("http://localhost/tenant/home?tenant={$tenant->id}");
|
||||
pest()->get(route('tenant.home'))->assertSee($tenant->id);
|
||||
} else {
|
||||
expect(route('tenant.home'))->toBe("http://localhost/tenant/home");
|
||||
expect(fn () => $this->withoutExceptionHandling()->get(route('tenant.home')))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
|
||||
}
|
||||
})->with([true, false]) // UrlGeneratorBootstrapper::$addTenantParameterToDefaults
|
||||
->with([true, false]); // TenancyUrlGenerator::$passTenantParameterToRoutes
|
||||
|
||||
test('changing request data query parameter and model column is respected by the url generator', function () {
|
||||
config([
|
||||
'tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class],
|
||||
'tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => 'team',
|
||||
'tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => 'slug',
|
||||
]);
|
||||
|
||||
Tenant::$extraCustomColumns = ['slug'];
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('slug')->unique();
|
||||
});
|
||||
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||
|
||||
$tenant = Tenant::create(['slug' => 'acme']);
|
||||
|
||||
Route::get('/tenant/home', fn () => tenant('id'))
|
||||
->name('tenant.home')
|
||||
->middleware([InitializeTenancyByRequestData::class]);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
expect(route('tenant.home'))->toBe("http://localhost/tenant/home?team=acme");
|
||||
pest()->get(route('tenant.home'))->assertSee($tenant->id);
|
||||
});
|
||||
|
||||
test('setting extra model columns sets additional URL defaults', function () {
|
||||
Tenant::$extraCustomColumns = ['slug'];
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = false;
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = true;
|
||||
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.allowed_extra_model_columns' => ['slug']]);
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('slug')->unique();
|
||||
});
|
||||
|
||||
Route::get('/{tenant}/foo/{user}', function (string $user) {
|
||||
return tenant()->getTenantKey() . " $user";
|
||||
})->middleware([InitializeTenancyByPath::class, 'web'])->name('foo');
|
||||
|
||||
Route::get('/{tenant:slug}/fooslug/{user}', function (string $user) {
|
||||
return tenant()->getTenantKey() . " $user";
|
||||
})->middleware([InitializeTenancyByPath::class, 'web'])->name('fooslug');
|
||||
|
||||
$tenant = Tenant::create(['slug' => 'acme']);
|
||||
|
||||
// In central context, no URL defaults are applied
|
||||
expect(route('foo', [$tenant->getTenantKey(), 'bar']))->toBe("http://localhost/{$tenant->getTenantKey()}/foo/bar");
|
||||
pest()->get(route('foo', [$tenant->getTenantKey(), 'bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
tenancy()->end();
|
||||
|
||||
expect(route('fooslug', ['acme', 'bar']))->toBe('http://localhost/acme/fooslug/bar');
|
||||
pest()->get(route('fooslug', ['acme', 'bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
tenancy()->end();
|
||||
|
||||
// In tenant context, URL defaults are applied
|
||||
tenancy()->initialize($tenant);
|
||||
expect(route('foo', ['bar']))->toBe("http://localhost/{$tenant->getTenantKey()}/foo/bar");
|
||||
pest()->get(route('foo', ['bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
|
||||
expect(route('fooslug', ['bar']))->toBe('http://localhost/acme/fooslug/bar');
|
||||
pest()->get(route('fooslug', ['bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
});
|
||||
|
||||
test('changing the tenant model column changes the default value for the tenant parameter', function () {
|
||||
Tenant::$extraCustomColumns = ['slug'];
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = false;
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = true;
|
||||
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_model_column' => 'slug']);
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('slug')->unique();
|
||||
});
|
||||
|
||||
Route::get('/{tenant}/foo/{user}', function (string $user) {
|
||||
return tenant()->getTenantKey() . " $user";
|
||||
})->middleware([InitializeTenancyByPath::class, 'web'])->name('foo');
|
||||
|
||||
$tenant = Tenant::create(['slug' => 'acme']);
|
||||
|
||||
// In central context, no URL defaults are applied
|
||||
expect(route('foo', ['acme', 'bar']))->toBe("http://localhost/acme/foo/bar");
|
||||
pest()->get(route('foo', ['acme', 'bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
tenancy()->end();
|
||||
|
||||
// In tenant context, URL defaults are applied
|
||||
tenancy()->initialize($tenant);
|
||||
expect(route('foo', ['bar']))->toBe("http://localhost/acme/foo/bar");
|
||||
pest()->get(route('foo', ['bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
});
|
||||
|
||||
test('changing the tenant parameter name is respected by the url generator', function () {
|
||||
Tenant::$extraCustomColumns = ['slug', 'slug2'];
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = false;
|
||||
UrlGeneratorBootstrapper::$addTenantParameterToDefaults = true;
|
||||
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_parameter_name' => 'team']);
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_model_column' => 'slug']);
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.allowed_extra_model_columns' => ['slug2']]);
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('slug')->unique();
|
||||
$table->string('slug2')->unique();
|
||||
});
|
||||
|
||||
Route::get('/{team}/foo/{user}', function (string $user) {
|
||||
return tenant()->getTenantKey() . " $user";
|
||||
})->middleware([InitializeTenancyByPath::class, 'web'])->name('foo');
|
||||
|
||||
Route::get('/{team:slug2}/fooslug2/{user}', function (string $user) {
|
||||
return tenant()->getTenantKey() . " $user";
|
||||
})->middleware([InitializeTenancyByPath::class, 'web'])->name('fooslug2');
|
||||
|
||||
$tenant = Tenant::create(['slug' => 'acme', 'slug2' => 'acme2']);
|
||||
|
||||
// In central context, no URL defaults are applied
|
||||
expect(route('foo', ['acme', 'bar']))->toBe("http://localhost/acme/foo/bar");
|
||||
pest()->get(route('foo', ['acme', 'bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
tenancy()->end();
|
||||
|
||||
expect(route('fooslug2', ['acme2', 'bar']))->toBe("http://localhost/acme2/fooslug2/bar");
|
||||
pest()->get(route('fooslug2', ['acme2', 'bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
tenancy()->end();
|
||||
|
||||
// In tenant context, URL defaults are applied
|
||||
tenancy()->initialize($tenant);
|
||||
expect(route('foo', ['bar']))->toBe("http://localhost/acme/foo/bar");
|
||||
pest()->get(route('foo', ['bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
|
||||
expect(route('fooslug2', ['bar']))->toBe("http://localhost/acme2/fooslug2/bar");
|
||||
pest()->get(route('fooslug2', ['bar']))->assertSee(tenant()->getTenantKey() . ' bar');
|
||||
});
|
||||
|
||||
test('url generator can override specific route names', function() {
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
|
||||
Route::get('/foo', fn () => 'foo')->name('foo');
|
||||
Route::get('/bar', fn () => 'bar')->name('bar');
|
||||
Route::get('/baz', fn () => 'baz')->name('baz'); // Not overridden
|
||||
|
||||
TenancyUrlGenerator::$overrides = ['foo' => 'bar'];
|
||||
|
||||
expect(route('foo'))->toBe(url('/foo'));
|
||||
expect(route('bar'))->toBe(url('/bar'));
|
||||
expect(route('baz'))->toBe(url('/baz'));
|
||||
|
||||
tenancy()->initialize(Tenant::create());
|
||||
|
||||
expect(route('foo'))->toBe(url('/bar'));
|
||||
expect(route('bar'))->toBe(url('/bar')); // not overridden
|
||||
expect(route('baz'))->toBe(url('/baz')); // not overridden
|
||||
|
||||
// Bypass the override
|
||||
expect(route('foo', ['central' => true]))->toBe(url('/foo'));
|
||||
});
|
||||
|
||||
test('both the name prefixing and the tenant parameter logic gets skipped when bypass parameter is used', function () {
|
||||
|
|
@ -105,54 +317,8 @@ test('both the name prefixing and the tenant parameter logic gets skipped when b
|
|||
->not()->toContain('bypassParameter');
|
||||
|
||||
// When the bypass parameter is false, the generated route URL points to the prefixed route ('tenant.home')
|
||||
expect(route('home', ['bypassParameter' => false]))->toBe($tenantRouteUrl)
|
||||
// The tenant parameter is not passed automatically since both
|
||||
// UrlGeneratorBootstrapper::$addTenantParameterToDefaults and TenancyUrlGenerator::$passTenantParameterToRoutes are false by default
|
||||
expect(route('home', ['bypassParameter' => false, 'tenant' => $tenant->getTenantKey()]))->toBe($tenantRouteUrl)
|
||||
->not()->toContain('bypassParameter');
|
||||
});
|
||||
|
||||
test('url generator bootstrapper can make route helper generate links with the tenant parameter', function() {
|
||||
Route::get('/query_string', fn () => route('query_string'))->name('query_string')->middleware(['universal', InitializeTenancyByRequestData::class]);
|
||||
Route::get('/path', fn () => route('path'))->name('path');
|
||||
Route::get('/{tenant}/path', fn () => route('tenant.path'))->name('tenant.path')->middleware([InitializeTenancyByPath::class]);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
$tenantKey = $tenant->getTenantKey();
|
||||
$queryStringCentralUrl = route('query_string');
|
||||
$queryStringTenantUrl = route('query_string', ['tenant' => $tenantKey]);
|
||||
$pathCentralUrl = route('path');
|
||||
$pathTenantUrl = route('tenant.path', ['tenant' => $tenantKey]);
|
||||
|
||||
// Makes the route helper receive the tenant parameter whenever available
|
||||
// Unless the bypass parameter is true
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||
|
||||
TenancyUrlGenerator::$bypassParameter = 'bypassParameter';
|
||||
|
||||
config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]);
|
||||
|
||||
expect(route('path'))->toBe($pathCentralUrl);
|
||||
// Tenant parameter required, but not passed since tenancy wasn't initialized
|
||||
expect(fn () => route('tenant.path'))->toThrow(UrlGenerationException::class);
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// Tenant parameter is passed automatically
|
||||
expect(route('path'))->not()->toBe($pathCentralUrl); // Parameter added as query string – bypassParameter needed
|
||||
expect(route('path', ['bypassParameter' => true]))->toBe($pathCentralUrl);
|
||||
expect(route('tenant.path'))->toBe($pathTenantUrl);
|
||||
|
||||
expect(route('query_string'))->toBe($queryStringTenantUrl)->toContain('tenant=');
|
||||
expect(route('query_string', ['bypassParameter' => 'true']))->toBe($queryStringCentralUrl)->not()->toContain('tenant=');
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
expect(route('query_string'))->toBe($queryStringCentralUrl);
|
||||
|
||||
// Tenant parameter required, but shouldn't be passed since tenancy isn't initialized
|
||||
expect(fn () => route('tenant.path'))->toThrow(UrlGenerationException::class);
|
||||
|
||||
// Route-level identification
|
||||
pest()->get("http://localhost/query_string")->assertSee($queryStringCentralUrl);
|
||||
pest()->get("http://localhost/query_string?tenant=$tenantKey")->assertSee($queryStringTenantUrl);
|
||||
pest()->get("http://localhost/path")->assertSee($pathCentralUrl);
|
||||
pest()->get("http://localhost/$tenantKey/path")->assertSee($pathTenantUrl);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
|
|||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
||||
use function Stancl\Tenancy\Tests\withTenantDatabases;
|
||||
|
||||
beforeEach(function () {
|
||||
withTenantDatabases();
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
|||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\PathIdentificationManager;
|
||||
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('tenants can be resolved using cached resolvers', function (string $resolver) {
|
||||
$tenant = Tenant::create(['id' => $tenantKey = 'acme']);
|
||||
|
|
@ -84,6 +86,34 @@ test('cache is invalidated when the tenant is updated', function (string $resolv
|
|||
RequestDataTenantResolver::class,
|
||||
]);
|
||||
|
||||
test('cache is invalidated when the tenant is deleted', function (string $resolver) {
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=0;'); // allow deleting the tenant
|
||||
$tenant = Tenant::create(['id' => $tenantKey = 'acme']);
|
||||
$tenant->createDomain($tenantKey);
|
||||
|
||||
DB::enableQueryLog();
|
||||
|
||||
config(['tenancy.identification.resolvers.' . $resolver . '.cache' => true]);
|
||||
|
||||
expect($tenant->is(app($resolver)->resolve(getResolverArgument($resolver, $tenantKey))))->toBeTrue();
|
||||
expect(DB::getQueryLog())->not()->toBeEmpty();
|
||||
|
||||
DB::flushQueryLog();
|
||||
|
||||
expect($tenant->is(app($resolver)->resolve(getResolverArgument($resolver, $tenantKey))))->toBeTrue();
|
||||
expect(DB::getQueryLog())->toBeEmpty();
|
||||
|
||||
$tenant->delete();
|
||||
DB::flushQueryLog();
|
||||
|
||||
expect(fn () => app($resolver)->resolve(getResolverArgument($resolver, $tenantKey)))->toThrow(TenantCouldNotBeIdentifiedException::class);
|
||||
expect(DB::getQueryLog())->not()->toBeEmpty(); // Cache was invalidated, so the DB was queried
|
||||
})->with([
|
||||
DomainTenantResolver::class,
|
||||
PathTenantResolver::class,
|
||||
RequestDataTenantResolver::class,
|
||||
]);
|
||||
|
||||
test('cache is invalidated when a tenants domain is changed', function () {
|
||||
$tenant = Tenant::create(['id' => $tenantKey = 'acme']);
|
||||
$tenant->createDomain($tenantKey);
|
||||
|
|
@ -110,6 +140,26 @@ test('cache is invalidated when a tenants domain is changed', function () {
|
|||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||
});
|
||||
|
||||
test('cache is invalidated when a tenants domain is deleted', function () {
|
||||
$tenant = Tenant::create(['id' => $tenantKey = 'acme']);
|
||||
$tenant->createDomain($tenantKey);
|
||||
|
||||
DB::enableQueryLog();
|
||||
|
||||
config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
expect(DB::getQueryLog())->toBeEmpty(); // empty
|
||||
|
||||
$tenant->domains->first()->delete();
|
||||
DB::flushQueryLog();
|
||||
|
||||
expect(fn () => $tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toThrow(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
expect(DB::getQueryLog())->not()->toBeEmpty(); // Cache was invalidated, so the DB was queried
|
||||
});
|
||||
|
||||
test('PathTenantResolver forgets the tenant route parameter when the tenant is resolved from cache', function() {
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.cache' => true]);
|
||||
DB::enableQueryLog();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Route as RouteFacade;
|
|||
use Stancl\Tenancy\Tests\Etc\HasMiddlewareController;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('a route can be universal using path identification', function (array $routeMiddleware, array $globalMiddleware) {
|
||||
foreach ($globalMiddleware as $middleware) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Database\Concerns\HasDomains;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||
use Stancl\Tenancy\Database\Models;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
Route::group([
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) {
|
||||
|
|
@ -389,6 +390,21 @@ test('run command works when sub command asks questions and accepts arguments',
|
|||
expect($user->email)->toBe('email@localhost');
|
||||
});
|
||||
|
||||
test('run command accepts arguments and options correctly', function() {
|
||||
$tenant = Tenant::create();
|
||||
$id = $tenant->getTenantKey();
|
||||
|
||||
// Use unquoted single-word arguments and quoted arguments with spaces
|
||||
pest()->artisan("tenants:run \"bar username 'email@localhost' adsfg123 'some Arg' --option='some option'\" --tenants=$id")
|
||||
->expectsOutputToContain("Tenant: $id.")
|
||||
->expectsOutput("Name: username")
|
||||
->expectsOutput("Email: email@localhost")
|
||||
->expectsOutput("Password: adsfg123")
|
||||
->expectsOutput("Argument: some Arg")
|
||||
->expectsOutput("Option: some option")
|
||||
->assertExitCode(0);
|
||||
});
|
||||
|
||||
test('migrate fresh command only deletes tenant databases if drop_tenant_databases_on_migrate_fresh is true', function (bool $dropTenantDBsOnMigrateFresh) {
|
||||
Event::listen(DeletingTenant::class,
|
||||
JobPipeline::make([DeleteDomains::class])->send(function (DeletingTenant $event) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMySQLData
|
|||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLSchemaManager;
|
||||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLDatabaseManager;
|
||||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMicrosoftSQLServerDatabaseManager;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
|
|||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
InitializeTenancyByDomain::$onFail = null;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader;
|
|||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithMiddleware;
|
||||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithRouteMiddleware;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config()->set([
|
||||
|
|
|
|||
31
tests/Etc/Console/AnotherExampleCommand.php
Normal file
31
tests/Etc/Console/AnotherExampleCommand.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class AnotherExampleCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bar {name} {email} {password} {arg} {--option=}';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->line('Name: ' . $this->argument('name'));
|
||||
$this->line('Email: ' . $this->argument('email'));
|
||||
$this->line('Password: ' . $this->argument('password'));
|
||||
$this->line('Argument: ' . $this->argument('arg'));
|
||||
$this->line('Option: ' . $this->option('option'));
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ class ConsoleKernel extends Kernel
|
|||
{
|
||||
protected $commands = [
|
||||
ExampleCommand::class,
|
||||
AnotherExampleCommand::class,
|
||||
ExampleQuestionCommand::class,
|
||||
AddUserCommand::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Stancl\Tenancy\Events\BootstrappingTenancy;
|
|||
use Stancl\Tenancy\Listeners\QueueableListener;
|
||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
FooListener::$shouldQueue = false;
|
||||
|
|
|
|||
|
|
@ -18,14 +18,9 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('sqlite ATTACH statements can be blocked', function (bool $disallow) {
|
||||
try {
|
||||
readlink(base_path('vendor'));
|
||||
} catch (\Throwable) {
|
||||
symlink(base_path('vendor'), '/var/www/html/vendor');
|
||||
}
|
||||
|
||||
if (php_uname('m') == 'aarch64') {
|
||||
// Escape testbench prison. Can't hardcode /var/www/html/extensions/... here
|
||||
// since GHA doesn't mount the filesystem on the container's workdir
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Features\CrossDomainRedirect;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('tenant redirect macro replaces only the hostname', function () {
|
||||
config([
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Stancl\Tenancy\Features\TenantConfig;
|
|||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
afterEach(function () {
|
||||
TenantConfig::$storageToConfigMap = [];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use Stancl\Tenancy\Events\TenancyInitialized;
|
|||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\MailConfigBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use function Stancl\Tenancy\Tests\withTenantDatabases;
|
||||
|
||||
beforeEach(function() {
|
||||
config(['mail.default' => 'smtp']);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
beforeEach(function () {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ use Stancl\Tenancy\Jobs\CreateDatabase;
|
|||
use Stancl\Tenancy\Listeners\CreateTenantConnection;
|
||||
use Stancl\Tenancy\Listeners\UseCentralConnection;
|
||||
use Stancl\Tenancy\Listeners\UseTenantConnection;
|
||||
use \Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('manual tenancy initialization works', function () {
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
InitializeTenancyByOriginHeader::$onFail = null;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
|
|||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
// Make sure the tenant parameter is set to 'tenant'
|
||||
|
|
@ -34,6 +35,11 @@ beforeEach(function () {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
InitializeTenancyByPath::$onFail = null;
|
||||
Tenant::$extraCustomColumns = [];
|
||||
});
|
||||
|
||||
test('tenant can be identified by path', function () {
|
||||
Tenant::create([
|
||||
'id' => 'acme',
|
||||
|
|
@ -149,6 +155,7 @@ test('central route can have a parameter with the same name as the tenant parame
|
|||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_parameter_name' => 'team']);
|
||||
$tenantKey = Tenant::create()->getTenantKey();
|
||||
|
||||
// The route is flagged as central (while using kernel identification) so the {team} parameter should not be used for tenancy initialization
|
||||
Route::get('/central/route/{team}/{a}/{b}', function ($team, $a, $b) {
|
||||
return "$a + $b + $team";
|
||||
})->middleware('central')->name('central-route');
|
||||
|
|
@ -184,8 +191,6 @@ test('the tenant model column can be customized in the config', function () {
|
|||
$this->withoutExceptionHandling();
|
||||
pest()->get('/acme/foo')->assertSee($tenant->getTenantKey());
|
||||
expect(fn () => pest()->get($tenant->id . '/foo'))->toThrow(TenantCouldNotBeIdentifiedByPathException::class);
|
||||
|
||||
Tenant::$extraCustomColumns = []; // static property reset
|
||||
});
|
||||
|
||||
test('the tenant model column can be customized in the route definition', function () {
|
||||
|
|
@ -217,8 +222,6 @@ test('the tenant model column can be customized in the route definition', functi
|
|||
// Binding field defined
|
||||
pest()->get('/acme/bar')->assertSee($tenant->getTenantKey());
|
||||
expect(fn () => pest()->get($tenant->id . '/bar'))->toThrow(TenantCouldNotBeIdentifiedByPathException::class);
|
||||
|
||||
Tenant::$extraCustomColumns = []; // static property reset
|
||||
});
|
||||
|
||||
test('any extra model column needs to be whitelisted', function () {
|
||||
|
|
@ -242,6 +245,4 @@ test('any extra model column needs to be whitelisted', function () {
|
|||
// After whitelisting the column it works
|
||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.allowed_extra_model_columns' => ['slug']]);
|
||||
pest()->get('/acme/foo')->assertSee($tenant->getTenantKey());
|
||||
|
||||
Tenant::$extraCustomColumns = []; // static property reset
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Stancl\Tenancy\Events\PendingTenantCreated;
|
|||
use Stancl\Tenancy\Events\PendingTenantPulled;
|
||||
use Stancl\Tenancy\Events\PullingPendingTenant;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('tenants are correctly identified as pending', function (){
|
||||
Tenant::createPending();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Stancl\Tenancy\Tests\TestCase;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
|
@ -8,14 +10,14 @@ use Stancl\Tenancy\Events\TenantCreated;
|
|||
|
||||
uses(TestCase::class)->in(__DIR__);
|
||||
|
||||
function pest(): TestCase
|
||||
{
|
||||
return Pest\TestSuite::getInstance()->test;
|
||||
}
|
||||
|
||||
function withTenantDatabases()
|
||||
{
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->toListener());
|
||||
}
|
||||
|
||||
function pest(): TestCase
|
||||
{
|
||||
return \Pest\TestSuite::getInstance()->test;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
|||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithMiddleware;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('correct routes are accessible in route-level identification', function (RouteMode $defaultRouteMode) {
|
||||
config()->set([
|
||||
|
|
|
|||
|
|
@ -22,16 +22,14 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\PersistentQueueTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Listeners\QueueableListener;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
use function Stancl\Tenancy\Tests\withTenantDatabases;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->mockConsoleOutput = false;
|
||||
|
||||
config([
|
||||
'tenancy.bootstrappers' => [
|
||||
QueueTenancyBootstrapper::class,
|
||||
DatabaseTenancyBootstrapper::class,
|
||||
],
|
||||
'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class],
|
||||
'queue.default' => 'redis',
|
||||
]);
|
||||
|
||||
|
|
@ -45,7 +43,22 @@ afterEach(function () {
|
|||
pest()->valuestore->flush();
|
||||
});
|
||||
|
||||
test('tenant id is passed to tenant queues', function () {
|
||||
dataset('queue_bootstrappers', [
|
||||
QueueTenancyBootstrapper::class,
|
||||
PersistentQueueTenancyBootstrapper::class,
|
||||
]);
|
||||
|
||||
function withQueueBootstrapper(string $class) {
|
||||
config(['tenancy.bootstrappers' => [
|
||||
DatabaseTenancyBootstrapper::class,
|
||||
$class,
|
||||
]]);
|
||||
|
||||
$class::__constructStatic(app());
|
||||
}
|
||||
|
||||
test('tenant id is passed to tenant queues', function (string $bootstrapper) {
|
||||
withQueueBootstrapper($bootstrapper);
|
||||
withTenantDatabases();
|
||||
|
||||
config(['queue.default' => 'sync']);
|
||||
|
|
@ -61,9 +74,10 @@ test('tenant id is passed to tenant queues', function () {
|
|||
Event::assertDispatched(JobProcessing::class, function ($event) {
|
||||
return $event->job->payload()['tenant_id'] === tenant('id');
|
||||
});
|
||||
});
|
||||
})->with('queue_bootstrappers');
|
||||
|
||||
test('tenant id is not passed to central queues', function () {
|
||||
test('tenant id is not passed to central queues', function (string $bootstrapper) {
|
||||
withQueueBootstrapper($bootstrapper);
|
||||
withTenantDatabases();
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
|
@ -82,9 +96,10 @@ test('tenant id is not passed to central queues', function () {
|
|||
Event::assertDispatched(JobProcessing::class, function ($event) {
|
||||
return ! isset($event->job->payload()['tenant_id']);
|
||||
});
|
||||
});
|
||||
})->with('queue_bootstrappers');
|
||||
|
||||
test('tenancy is initialized inside queues', function (bool $shouldEndTenancy) {
|
||||
test('tenancy is initialized inside queues', function (bool $shouldEndTenancy, string $bootstrapper) {
|
||||
withQueueBootstrapper($bootstrapper);
|
||||
withTenantDatabases();
|
||||
withFailedJobs();
|
||||
|
||||
|
|
@ -117,7 +132,7 @@ test('tenancy is initialized inside queues', function (bool $shouldEndTenancy) {
|
|||
$tenant->run(function () use ($user) {
|
||||
expect($user->fresh()->name)->toBe('Bar');
|
||||
});
|
||||
})->with([true, false]);
|
||||
})->with([true, false])->with('queue_bootstrappers');
|
||||
|
||||
test('changing the shouldQueue static property in parent class affects child classes unless the property is redefined', function () {
|
||||
// Parent – $shouldQueue is true
|
||||
|
|
@ -142,7 +157,8 @@ test('changing the shouldQueue static property in parent class affects child cla
|
|||
expect(app(ShouldNotQueueListener::class)->shouldQueue(new stdClass()))->toBeFalse();
|
||||
});
|
||||
|
||||
test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenancy) {
|
||||
test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenancy, string $bootstrapper) {
|
||||
withQueueBootstrapper($bootstrapper);
|
||||
withFailedJobs();
|
||||
withTenantDatabases();
|
||||
|
||||
|
|
@ -189,9 +205,10 @@ test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenan
|
|||
$tenant->run(function () use ($user) {
|
||||
expect($user->fresh()->name)->toBe('Bar');
|
||||
});
|
||||
})->with([true, false]);
|
||||
})->with([true, false])->with('queue_bootstrappers');
|
||||
|
||||
test('the tenant used by the job doesnt change when the current tenant changes', function () {
|
||||
test('the tenant used by the job doesnt change when the current tenant changes', function (string $bootstrapper) {
|
||||
withQueueBootstrapper($bootstrapper);
|
||||
withTenantDatabases();
|
||||
|
||||
$tenant1 = Tenant::create();
|
||||
|
|
@ -208,26 +225,11 @@ test('the tenant used by the job doesnt change when the current tenant changes',
|
|||
pest()->artisan('queue:work --once');
|
||||
|
||||
expect(pest()->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant1->getTenantKey());
|
||||
});
|
||||
|
||||
test('tenant connections do not persist after tenant jobs get processed', function() {
|
||||
withTenantDatabases();
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
dispatch(new TestJob(pest()->valuestore));
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
pest()->artisan('queue:work --once');
|
||||
|
||||
expect(collect(DB::select('SHOW FULL PROCESSLIST'))->pluck('db'))->not()->toContain($tenant->database()->getName());
|
||||
});
|
||||
})->with('queue_bootstrappers');
|
||||
|
||||
// Regression test for #1277
|
||||
test('dispatching a job from a tenant run arrow function dispatches it immediately', function () {
|
||||
test('dispatching a job from a tenant run arrow function dispatches it immediately', function (string $bootstrapper) {
|
||||
withQueueBootstrapper($bootstrapper);
|
||||
withTenantDatabases();
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
|
@ -241,7 +243,7 @@ test('dispatching a job from a tenant run arrow function dispatches it immediate
|
|||
expect(tenant())->toBe(null);
|
||||
|
||||
expect(pest()->valuestore->get('tenant_id'))->toBe('The current tenant id is: ' . $tenant->getTenantKey());
|
||||
});
|
||||
})->with('queue_bootstrappers');
|
||||
|
||||
function createValueStore(): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use Stancl\Tenancy\Commands\CreateUserWithRLSPolicies;
|
|||
use Stancl\Tenancy\RLS\PolicyManagers\TableRLSManager;
|
||||
use Stancl\Tenancy\RLS\PolicyManagers\TraitRLSManager;
|
||||
use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
TraitRLSManager::$excludedModels = [Article::class];
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Stancl\Tenancy\Commands\CreateUserWithRLSPolicies;
|
|||
use Stancl\Tenancy\RLS\PolicyManagers\TableRLSManager;
|
||||
use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
||||
use Stancl\Tenancy\Database\Exceptions\RecursiveRelationshipException;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
TableRLSManager::$scopeByDefault = true;
|
||||
|
|
@ -502,7 +503,7 @@ test('table rls manager generates relationship trees with tables related to the
|
|||
|
||||
// Add non-nullable comment_id foreign key
|
||||
Schema::table('ratings', function (Blueprint $table) {
|
||||
$table->foreignId('comment_id')->onUpdate('cascade')->onDelete('cascade')->comment('rls')->constrained('comments');
|
||||
$table->foreignId('comment_id')->comment('rls')->constrained('comments')->onUpdate('cascade')->onDelete('cascade');
|
||||
});
|
||||
|
||||
// Non-nullable paths are preferred over nullable paths
|
||||
|
|
@ -639,16 +640,29 @@ test('table rls manager generates queries correctly', function() {
|
|||
test('table manager throws an exception when encountering a recursive relationship', function() {
|
||||
Schema::create('recursive_posts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('highlighted_comment_id')->constrained('comments')->nullable()->comment('rls');
|
||||
$table->foreignId('highlighted_comment_id')->nullable()->comment('rls')->constrained('comments');
|
||||
});
|
||||
|
||||
Schema::table('comments', function (Blueprint $table) {
|
||||
$table->foreignId('recursive_post_id')->constrained('recursive_posts')->comment('rls');
|
||||
$table->foreignId('recursive_post_id')->comment('rls')->constrained('recursive_posts');
|
||||
});
|
||||
|
||||
expect(fn () => app(TableRLSManager::class)->generateTrees())->toThrow(RecursiveRelationshipException::class);
|
||||
});
|
||||
|
||||
test('table manager ignores recursive relationship if the foreign key responsible for the recursion has no-rls comment', function() {
|
||||
Schema::create('recursive_posts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('highlighted_comment_id')->nullable()->comment('no-rls')->constrained('comments');
|
||||
});
|
||||
|
||||
Schema::table('comments', function (Blueprint $table) {
|
||||
$table->foreignId('recursive_post_id')->comment('rls')->constrained('recursive_posts');
|
||||
});
|
||||
|
||||
expect(fn () => app(TableRLSManager::class)->generateTrees())->not()->toThrow(RecursiveRelationshipException::class);
|
||||
});
|
||||
|
||||
class Post extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Stancl\Tenancy\Commands\CreateUserWithRLSPolicies;
|
|||
use Stancl\Tenancy\RLS\PolicyManagers\TraitRLSManager;
|
||||
use Stancl\Tenancy\Bootstrappers\PostgresRLSBootstrapper;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToPrimaryModel;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
TraitRLSManager::$implicitRLS = true;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
|
|
@ -14,45 +17,90 @@ beforeEach(function () {
|
|||
],
|
||||
]);
|
||||
|
||||
InitializeTenancyByRequestData::$header = 'X-Tenant';
|
||||
InitializeTenancyByRequestData::$cookie = 'X-Tenant';
|
||||
InitializeTenancyByRequestData::$queryParameter = 'tenant';
|
||||
|
||||
Route::middleware(['tenant', InitializeTenancyByRequestData::class])->get('/test', function () {
|
||||
Route::middleware([InitializeTenancyByRequestData::class])->get('/test', function () {
|
||||
return 'Tenant id: ' . tenant('id');
|
||||
});
|
||||
});
|
||||
|
||||
test('header identification works', function () {
|
||||
$tenant = Tenant::create();
|
||||
test('header identification works', function (string|null $tenantModelColumn) {
|
||||
if ($tenantModelColumn) {
|
||||
Schema::table('tenants', function (Blueprint $table) use ($tenantModelColumn) {
|
||||
$table->string($tenantModelColumn)->unique();
|
||||
});
|
||||
Tenant::$extraCustomColumns = [$tenantModelColumn];
|
||||
}
|
||||
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->withHeader('X-Tenant', $tenant->id)
|
||||
->get('test')
|
||||
->assertSee($tenant->id);
|
||||
});
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => $tenantModelColumn]);
|
||||
|
||||
test('query parameter identification works', function () {
|
||||
$tenant = Tenant::create();
|
||||
$tenant = Tenant::create($tenantModelColumn ? [$tenantModelColumn => 'acme'] : []);
|
||||
$payload = $tenantModelColumn ? 'acme' : $tenant->id;
|
||||
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->get('test?tenant=' . $tenant->id)
|
||||
->assertSee($tenant->id);
|
||||
});
|
||||
// Default header name
|
||||
$this->withoutExceptionHandling()->withHeader('X-Tenant', $payload)->get('test')->assertSee($tenant->id);
|
||||
|
||||
test('cookie identification works', function () {
|
||||
$tenant = Tenant::create();
|
||||
// Custom header name
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.header' => 'X-Custom-Tenant']);
|
||||
$this->withoutExceptionHandling()->withHeader('X-Custom-Tenant', $payload)->get('test')->assertSee($tenant->id);
|
||||
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->withUnencryptedCookie('X-Tenant', $tenant->id)
|
||||
->get('test')
|
||||
->assertSee($tenant->id);
|
||||
});
|
||||
// Setting the header to null disables header identification
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.header' => null]);
|
||||
expect(fn () => $this->withoutExceptionHandling()->withHeader('X-Tenant', $payload)->get('test'))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
|
||||
})->with([null, 'slug']);
|
||||
|
||||
test('middleware throws exception when tenant data is not provided in the request', function () {
|
||||
test('query parameter identification works', function (string|null $tenantModelColumn) {
|
||||
if ($tenantModelColumn) {
|
||||
Schema::table('tenants', function (Blueprint $table) use ($tenantModelColumn) {
|
||||
$table->string($tenantModelColumn)->unique();
|
||||
});
|
||||
Tenant::$extraCustomColumns = [$tenantModelColumn];
|
||||
}
|
||||
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => $tenantModelColumn]);
|
||||
|
||||
$tenant = Tenant::create($tenantModelColumn ? [$tenantModelColumn => 'acme'] : []);
|
||||
$payload = $tenantModelColumn ? 'acme' : $tenant->id;
|
||||
|
||||
// Default query parameter name
|
||||
$this->withoutExceptionHandling()->get('test?tenant=' . $payload)->assertSee($tenant->id);
|
||||
|
||||
// Custom query parameter name
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => 'custom_tenant']);
|
||||
$this->withoutExceptionHandling()->get('test?custom_tenant=' . $payload)->assertSee($tenant->id);
|
||||
|
||||
// Setting the query parameter to null disables query parameter identification
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => null]);
|
||||
expect(fn () => $this->withoutExceptionHandling()->get('test?tenant=' . $payload))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
|
||||
})->with([null, 'slug']);
|
||||
|
||||
test('cookie identification works', function (string|null $tenantModelColumn) {
|
||||
if ($tenantModelColumn) {
|
||||
Schema::table('tenants', function (Blueprint $table) use ($tenantModelColumn) {
|
||||
$table->string($tenantModelColumn)->unique();
|
||||
});
|
||||
Tenant::$extraCustomColumns = [$tenantModelColumn];
|
||||
}
|
||||
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => $tenantModelColumn]);
|
||||
|
||||
$tenant = Tenant::create($tenantModelColumn ? [$tenantModelColumn => 'acme'] : []);
|
||||
$payload = $tenantModelColumn ? 'acme' : $tenant->id;
|
||||
|
||||
// Default cookie name
|
||||
$this->withoutExceptionHandling()->withUnencryptedCookie('tenant', $payload)->get('test')->assertSee($tenant->id);
|
||||
|
||||
// Custom cookie name
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.cookie' => 'custom_tenant_id']);
|
||||
$this->withoutExceptionHandling()->withUnencryptedCookie('custom_tenant_id', $payload)->get('test')->assertSee($tenant->id);
|
||||
|
||||
// Setting the cookie to null disables cookie identification
|
||||
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.cookie' => null]);
|
||||
expect(fn () => $this->withoutExceptionHandling()->withUnencryptedCookie('tenant', $payload)->get('test'))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
|
||||
})->with([null, 'slug']);
|
||||
|
||||
// todo@tests encrypted cookie
|
||||
|
||||
test('an exception is thrown when no tenant data is provided in the request', function () {
|
||||
pest()->expectException(TenantCouldNotBeIdentifiedByRequestDataException::class);
|
||||
$this->withoutExceptionHandling()->get('test');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ use Stancl\Tenancy\ResourceSyncing\Events\SyncedResourceSavedInForeignDatabase;
|
|||
use Illuminate\Database\Eloquent\Scope;
|
||||
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
|
||||
use Illuminate\Database\QueryException;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['tenancy.bootstrappers' => [
|
||||
|
|
|
|||
73
tests/RunForMultipleTest.php
Normal file
73
tests/RunForMultipleTest.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Stancl\Tenancy\Events\TenantCreated;
|
||||
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||
use Stancl\Tenancy\Jobs\MigrateDatabase;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Stancl\Tenancy\Tests\Etc\User;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([
|
||||
CreateDatabase::class,
|
||||
MigrateDatabase::class,
|
||||
])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->toListener());
|
||||
|
||||
config(['tenancy.bootstrappers' => [
|
||||
DatabaseTenancyBootstrapper::class,
|
||||
]]);
|
||||
});
|
||||
|
||||
test('runForMultiple runs the passed closure for the right tenants', function() {
|
||||
$tenants = [Tenant::create(), Tenant::create(), Tenant::create()];
|
||||
|
||||
$createUser = fn ($username) => function () use ($username) {
|
||||
User::create(['name' => $username, 'email' => Str::random(8) . '@example.com', 'password' => bcrypt('password')]);
|
||||
};
|
||||
|
||||
// tenancy()->runForMultiple([], ...) shouldn't do anything
|
||||
// No users should be created -- the closure should not run at all
|
||||
tenancy()->runForMultiple([], $createUser('none'));
|
||||
// Try the same with an empty collection -- the result should be the same for any traversable
|
||||
tenancy()->runForMultiple(collect(), $createUser('none'));
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
$tenant->run(function() {
|
||||
expect(User::count())->toBe(0);
|
||||
});
|
||||
}
|
||||
|
||||
// tenancy()->runForMultiple(['foo', 'bar'], ...) should run the closure only for the passed tenants
|
||||
tenancy()->runForMultiple([$tenants[0]->getTenantKey(), $tenants[1]->getTenantKey()], $createUser('user'));
|
||||
|
||||
// User should be created for tenants[0] and tenants[1], but not for tenants[2]
|
||||
foreach ($tenants as $tenant) {
|
||||
$tenant->run(function() use ($tenants) {
|
||||
if (tenant()->getTenantKey() !== $tenants[2]->getTenantKey()) {
|
||||
expect(User::first()->name)->toBe('user');
|
||||
} else {
|
||||
expect(User::count())->toBe(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// tenancy()->runForMultiple(null, ...) should run the closure for all tenants
|
||||
tenancy()->runForMultiple(null, $createUser('new_user'));
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
$tenant->run(function() {
|
||||
expect(User::all()->pluck('name'))->toContain('new_user');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -8,6 +8,7 @@ use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
|
|||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Middleware\ScopeSessions;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
Route::group([
|
||||
|
|
@ -54,3 +55,15 @@ test('an exception is thrown when the middleware is executed before tenancy is i
|
|||
pest()->expectException(TenancyNotInitializedException::class);
|
||||
$this->withoutExceptionHandling()->get('http://acme.localhost/bar');
|
||||
});
|
||||
|
||||
test('scope sessions mw can be used on universal routes', function() {
|
||||
Route::get('/universal', function () {
|
||||
return true;
|
||||
})->middleware(['universal', InitializeTenancyBySubdomain::class, ScopeSessions::class]);
|
||||
|
||||
Tenant::create([
|
||||
'id' => 'acme',
|
||||
])->createDomain('acme');
|
||||
|
||||
pest()->withoutExceptionHandling()->get('http://localhost/universal')->assertSuccessful();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
// todo@tests write similar low-level tests for the cache bootstrapper? including the database driver in a single-db setup
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToPrimaryModel;
|
||||
use Stancl\Tenancy\Database\Concerns\HasScopedValidationRules;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
Schema::create('posts', function (Blueprint $table) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use Illuminate\Database\UniqueConstraintViolationException;
|
|||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['tenancy.models.tenant' => SingleDomainTenant::class]);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Stancl\Tenancy\Database\Concerns\HasDomains;
|
|||
use Stancl\Tenancy\Exceptions\NotASubdomainException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Database\Models;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
// Global state cleanup after some tests
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Stancl\Tenancy\Controllers\TenantAssetController;
|
|||
use Stancl\Tenancy\Events\TenancyEnded;
|
||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['tenancy.bootstrappers' => [
|
||||
|
|
@ -65,7 +66,7 @@ test('asset can be accessed using the url returned by the tenant asset helper',
|
|||
|
||||
test('asset helper returns a link to tenant asset controller when asset url is null', function () {
|
||||
config(['app.asset_url' => null]);
|
||||
config(['tenancy.filesystem.asset_helper_tenancy' => true]);
|
||||
config(['tenancy.filesystem.asset_helper_override' => true]);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
tenancy()->initialize($tenant);
|
||||
|
|
@ -78,7 +79,7 @@ test('asset helper returns a link to tenant asset controller when asset url is n
|
|||
|
||||
test('asset helper returns a link to an external url when asset url is not null', function () {
|
||||
config(['app.asset_url' => 'https://an-s3-bucket']);
|
||||
config(['tenancy.filesystem.asset_helper_tenancy' => true]);
|
||||
config(['tenancy.filesystem.asset_helper_override' => true]);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
tenancy()->initialize($tenant);
|
||||
|
|
@ -93,7 +94,7 @@ test('asset helper works correctly with path identification', function (bool $ke
|
|||
TenancyUrlGenerator::$prefixRouteNames = true;
|
||||
TenancyUrlGenerator::$passTenantParameterToRoutes = true;
|
||||
|
||||
config(['tenancy.filesystem.asset_helper_tenancy' => true]);
|
||||
config(['tenancy.filesystem.asset_helper_override' => true]);
|
||||
config(['tenancy.identification.default_middleware' => InitializeTenancyByPath::class]);
|
||||
config(['tenancy.bootstrappers' => array_merge([UrlGeneratorBootstrapper::class], config('tenancy.bootstrappers'))]);
|
||||
|
||||
|
|
@ -165,7 +166,7 @@ test('asset helper tenancy can be disabled', function () {
|
|||
|
||||
config([
|
||||
'app.asset_url' => null,
|
||||
'tenancy.filesystem.asset_helper_tenancy' => false,
|
||||
'tenancy.filesystem.asset_helper_override' => false,
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('commands run globally are tenant aware and return valid exit code', function () {
|
||||
$tenant1 = Tenant::create();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMySQLData
|
|||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLSchemaManager;
|
||||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledPostgreSQLDatabaseManager;
|
||||
use Stancl\Tenancy\Database\TenantDatabaseManagers\PermissionControlledMicrosoftSQLServerDatabaseManager;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
SQLiteDatabaseManager::$path = null;
|
||||
|
|
@ -405,6 +406,42 @@ test('tenant database can be created by using the username and password from ten
|
|||
expect($manager->databaseExists($name))->toBeTrue();
|
||||
});
|
||||
|
||||
test('decrypted password can be used to connect to a tenant db while the password is saved as encrypted', function (string|null $tenantDbPassword) {
|
||||
config([
|
||||
'tenancy.database.managers.mysql' => PermissionControlledMySQLDatabaseManager::class,
|
||||
'tenancy.database.template_tenant_connection' => 'mysql',
|
||||
]);
|
||||
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->toListener());
|
||||
|
||||
// Create a tenant, either with a specific password, or with a password generated by the DB manager
|
||||
$tenant = TenantWithEncryptedPassword::create([
|
||||
'tenancy_db_name' => $name = 'foo' . Str::random(8),
|
||||
'tenancy_db_username' => 'user' . Str::random(4),
|
||||
'tenancy_db_password' => $tenantDbPassword,
|
||||
]);
|
||||
|
||||
$decryptedPassword = $tenant->tenancy_db_password;
|
||||
$encryptedPassword = $tenant->getAttributes()['tenancy_db_password']; // Password encrypted using the TenantWithEncryptedPassword model's encrypted cast
|
||||
expect($decryptedPassword)->not()->toBe($encryptedPassword);
|
||||
|
||||
$passwordSavedInDatabase = json_decode(DB::select('SELECT data FROM tenants LIMIT 1')[0]->data)->tenancy_db_password;
|
||||
expect($encryptedPassword)->toBe($passwordSavedInDatabase);
|
||||
|
||||
app(DatabaseManager::class)->connectToTenant($tenant);
|
||||
|
||||
// Check if we got connected to the tenant DB
|
||||
expect(config('database.default'))->toBe('tenant');
|
||||
expect(config('database.connections.tenant.database'))->toBe($name);
|
||||
// Check if the decrypted password is used to connect to the tenant DB
|
||||
expect(config('database.connections.tenant.password'))->toBe($decryptedPassword);
|
||||
})->with([
|
||||
'decrypted' . Str::random(8), // Use this password as the tenant DB password
|
||||
null, // Let the DB manager generate the tenant DB password
|
||||
]);
|
||||
|
||||
test('path used by sqlite manager can be customized', function () {
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
|
|
@ -529,3 +566,13 @@ function createUsersTable()
|
|||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
class TenantWithEncryptedPassword extends Tenant
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'tenancy_db_password' => 'encrypted',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,16 @@ use Stancl\Tenancy\Tests\Etc\Tenant;
|
|||
use Stancl\Tenancy\UniqueIdentifierGenerators\UUIDGenerator;
|
||||
use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
|
||||
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomHexGenerator;
|
||||
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomIntGenerator;
|
||||
use Stancl\Tenancy\UniqueIdentifierGenerators\RandomStringGenerator;
|
||||
use Stancl\Tenancy\UniqueIdentifierGenerators\ULIDGenerator;
|
||||
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
afterEach(function () {
|
||||
RandomIntGenerator::$min = 0;
|
||||
RandomIntGenerator::$max = PHP_INT_MAX;
|
||||
});
|
||||
|
||||
test('created event is dispatched', function () {
|
||||
Event::fake([TenantCreated::class]);
|
||||
|
|
@ -71,6 +80,20 @@ test('autoincrement ids are supported', function () {
|
|||
expect($tenant2->id)->toBe(2);
|
||||
});
|
||||
|
||||
test('ulid ids are supported', function () {
|
||||
app()->bind(UniqueIdentifierGenerator::class, ULIDGenerator::class);
|
||||
|
||||
$tenant1 = Tenant::create();
|
||||
expect($tenant1->id)->toBeString();
|
||||
expect(strlen($tenant1->id))->toBe(26);
|
||||
|
||||
$tenant2 = Tenant::create();
|
||||
expect($tenant2->id)->toBeString();
|
||||
expect(strlen($tenant2->id))->toBe(26);
|
||||
|
||||
expect($tenant2->id > $tenant1->id)->toBeTrue();
|
||||
});
|
||||
|
||||
test('hex ids are supported', function () {
|
||||
app()->bind(UniqueIdentifierGenerator::class, RandomHexGenerator::class);
|
||||
|
||||
|
|
@ -87,6 +110,16 @@ test('hex ids are supported', function () {
|
|||
RandomHexGenerator::$bytes = 6; // reset
|
||||
});
|
||||
|
||||
test('random ints are supported', function () {
|
||||
app()->bind(UniqueIdentifierGenerator::class, RandomIntGenerator::class);
|
||||
RandomIntGenerator::$min = 200;
|
||||
RandomIntGenerator::$max = 1000;
|
||||
|
||||
$tenant1 = Tenant::create();
|
||||
expect($tenant1->id >= 200)->toBeTrue();
|
||||
expect($tenant1->id <= 1000)->toBeTrue();
|
||||
});
|
||||
|
||||
test('random string ids are supported', function () {
|
||||
app()->bind(UniqueIdentifierGenerator::class, RandomStringGenerator::class);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
|||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Exceptions\StatefulGuardRequiredException;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
beforeEach(function () {
|
||||
pest()->artisan('migrate', [
|
||||
|
|
@ -88,7 +89,7 @@ test('tenant user can be impersonated on a tenant domain', function () {
|
|||
expect(session('tenancy_impersonating'))->toBeTrue();
|
||||
|
||||
// Leave impersonation
|
||||
UserImpersonation::leave();
|
||||
UserImpersonation::stopImpersonating();
|
||||
|
||||
expect(UserImpersonation::isImpersonating())->toBeFalse();
|
||||
expect(session('tenancy_impersonating'))->toBeNull();
|
||||
|
|
@ -134,7 +135,7 @@ test('tenant user can be impersonated on a tenant path', function () {
|
|||
expect(session('tenancy_impersonating'))->toBeTrue();
|
||||
|
||||
// Leave impersonation
|
||||
UserImpersonation::leave();
|
||||
UserImpersonation::stopImpersonating();
|
||||
|
||||
expect(UserImpersonation::isImpersonating())->toBeFalse();
|
||||
expect(session('tenancy_impersonating'))->toBeNull();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
|||
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||
{
|
||||
|
|
@ -85,11 +87,8 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'--realpath' => true,
|
||||
]);
|
||||
|
||||
// Laravel 6.x support todo@refactor clean up
|
||||
$testResponse = class_exists('Illuminate\Testing\TestResponse') ? 'Illuminate\Testing\TestResponse' : 'Illuminate\Foundation\Testing\TestResponse';
|
||||
$testResponse::macro('assertContent', function ($content) {
|
||||
$assertClass = class_exists('Illuminate\Testing\Assert') ? 'Illuminate\Testing\Assert' : 'Illuminate\Foundation\Testing\Assert';
|
||||
$assertClass::assertSame($content, $this->baseResponse->getContent());
|
||||
\Illuminate\Testing\TestResponse::macro('assertContent', function ($content) {
|
||||
\Illuminate\Testing\Assert::assertSame($content, $this->baseResponse->getContent());
|
||||
|
||||
return $this;
|
||||
});
|
||||
|
|
@ -175,18 +174,25 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'tenancy.models.tenant' => Tenant::class, // Use test tenant w/ DBs & domains
|
||||
]);
|
||||
|
||||
$app->singleton(RedisTenancyBootstrapper::class); // todo@samuel use proper approach eg config for singleton registration
|
||||
$app->singleton(CacheTenancyBootstrapper::class); // todo@samuel use proper approach eg config for singleton registration
|
||||
// Since we run the TSP with no bootstrappers enabled, we need
|
||||
// to manually register bootstrappers as singletons here.
|
||||
$app->singleton(RedisTenancyBootstrapper::class);
|
||||
$app->singleton(CacheTenancyBootstrapper::class);
|
||||
$app->singleton(BroadcastingConfigBootstrapper::class);
|
||||
$app->singleton(BroadcastChannelPrefixBootstrapper::class);
|
||||
$app->singleton(PostgresRLSBootstrapper::class);
|
||||
$app->singleton(MailConfigBootstrapper::class);
|
||||
$app->singleton(RootUrlBootstrapper::class);
|
||||
$app->singleton(UrlGeneratorBootstrapper::class);
|
||||
$app->singleton(FilesystemTenancyBootstrapper::class);
|
||||
}
|
||||
|
||||
protected function getPackageProviders($app)
|
||||
{
|
||||
TenancyServiceProvider::$configure = function () {
|
||||
config(['tenancy.bootstrappers' => []]);
|
||||
};
|
||||
|
||||
return [
|
||||
TenancyServiceProvider::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
use Illuminate\Http\Request;
|
||||
use Stancl\Tenancy\Enums\RouteMode;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
|
|
@ -11,16 +9,14 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
|||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Stancl\Tenancy\Tests\Etc\HasMiddlewareController;
|
||||
use Stancl\Tenancy\Middleware\IdentificationMiddleware;
|
||||
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
||||
use function Stancl\Tenancy\Tests\pest;
|
||||
|
||||
test('a route can be universal using domain identification', function (array $routeMiddleware, array $globalMiddleware) {
|
||||
foreach ($globalMiddleware as $middleware) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue