mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 09:34:04 +00:00
Give universal flag highest priority (#27)
* Give universal flag the highest priority (wip) * Stop forgetting tenant parameter when route-level path ID is used * Fix PHPStan errors * Simplify annotation * Fix comment * Correct annotations * Improve requestHasTenant comment * Make cloning logic only clone universal routes, delete the universal flag from the new (tenant) route * Delete APP_DEBUG * make if condition easier to read * Update DealsWithRouteContexts.php * Fix test * Fix code style (php-cs-fixer) * Move tests * Delete incorrectly committed file * Cloning routes update wip * Route cloning rework WIP * Add todo to clone routes * Fix code style (php-cs-fixer) * Route cloning fix WIP * Set CloneRoutesAsTenant::$tenantMiddleware to ID MW * Revert CloneRoutesAsTenant::$tenantMiddleware-related changes * Simplify requestHasTenant * Add and test 'ckone' flag * Delete setting $skippedRoutes from CloneRoutesAsTenant * Fix code style (php-cs-fixer) * make config key used for testing distinct from normal tenancy config keys * Update src/Actions/CloneRoutesAsTenant.php * Move 'path identification types' dataset to CloneActionTest --------- Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> Co-authored-by: Samuel Štancl <samuel@archte.ch> Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
This commit is contained in:
parent
070828a81e
commit
c312156c18
10 changed files with 315 additions and 296 deletions
|
|
@ -118,10 +118,10 @@ class CloneRoutesAsTenant
|
||||||
$pathIdentificationUsed = (! $routeHasNonPathIdentificationMiddleware) &&
|
$pathIdentificationUsed = (! $routeHasNonPathIdentificationMiddleware) &&
|
||||||
($routeHasPathIdentificationMiddleware || $pathIdentificationMiddlewareInGlobalStack);
|
($routeHasPathIdentificationMiddleware || $pathIdentificationMiddlewareInGlobalStack);
|
||||||
|
|
||||||
$routeMode = tenancy()->getRouteMode($route);
|
if (
|
||||||
$routeIsUniversalOrTenant = $routeMode === RouteMode::TENANT || $routeMode === RouteMode::UNIVERSAL;
|
$pathIdentificationUsed &&
|
||||||
|
(tenancy()->getRouteMode($route) === RouteMode::UNIVERSAL || tenancy()->routeHasMiddleware($route, 'clone'))
|
||||||
if ($pathIdentificationUsed && $routeIsUniversalOrTenant) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +167,11 @@ class CloneRoutesAsTenant
|
||||||
// Add original route middleware to ensure there's no duplicate middleware
|
// Add original route middleware to ensure there's no duplicate middleware
|
||||||
unset($newRoute->action['middleware']);
|
unset($newRoute->action['middleware']);
|
||||||
|
|
||||||
$newRoute->middleware(tenancy()->getRouteMiddleware($route));
|
// Exclude `universal` and `clone` middleware from the new route -- it should specifically be a tenant route
|
||||||
|
$newRoute->middleware(array_filter(
|
||||||
|
tenancy()->getRouteMiddleware($route),
|
||||||
|
fn (string $middleware) => ! in_array($middleware, ['universal', 'clone'])
|
||||||
|
));
|
||||||
|
|
||||||
if ($routeName && ! $route->named($tenantRouteNamePrefix . '*')) {
|
if ($routeName && ! $route->named($tenantRouteNamePrefix . '*')) {
|
||||||
// Clear the route name so that `name()` sets the route name instead of suffixing it
|
// Clear the route name so that `name()` sets the route name instead of suffixing it
|
||||||
|
|
|
||||||
|
|
@ -20,46 +20,43 @@ trait DealsWithRouteContexts
|
||||||
* Get route's middleware context (tenant, central or universal).
|
* Get route's middleware context (tenant, central or universal).
|
||||||
* The context is determined by the route's middleware.
|
* The context is determined by the route's middleware.
|
||||||
*
|
*
|
||||||
|
* If the route has the 'universal' middleware, the context is universal,
|
||||||
|
* and the route is accessible from both contexts.
|
||||||
|
* The universal flag has the highest priority.
|
||||||
|
*
|
||||||
* If the route has the 'central' middleware, the context is central.
|
* If the route has the 'central' middleware, the context is central.
|
||||||
* If the route has the 'tenant' middleware, or any tenancy identification middleware (and the route isn't flagged as universal), the context is tenant.
|
* If the route has the 'tenant' middleware, or any tenancy identification middleware, the context is tenant.
|
||||||
*
|
*
|
||||||
* If the route doesn't have any of the mentioned middleware,
|
* If the route doesn't have any of the mentioned middleware,
|
||||||
* the context is determined by the `tenancy.default_route_mode` config.
|
* the context is determined by the `tenancy.default_route_mode` config.
|
||||||
*/
|
*/
|
||||||
public static function getRouteMode(Route $route): RouteMode
|
public static function getRouteMode(Route $route): RouteMode
|
||||||
{
|
{
|
||||||
|
// If the route is universal, you have to determine its actual context using
|
||||||
|
// the identification middleware's determineUniversalRouteContextFromRequest
|
||||||
|
if (static::routeIsUniversal($route)) {
|
||||||
|
return RouteMode::UNIVERSAL;
|
||||||
|
}
|
||||||
|
|
||||||
if (static::routeHasMiddleware($route, 'central')) {
|
if (static::routeHasMiddleware($route, 'central')) {
|
||||||
return RouteMode::CENTRAL;
|
return RouteMode::CENTRAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
$routeIsUniversal = static::routeIsUniversal($route);
|
// If the route is flagged as tenant or it has identification middleware, consider it tenant
|
||||||
|
if (static::routeHasMiddleware($route, 'tenant') || static::routeHasIdentificationMiddleware($route)) {
|
||||||
// If the route is flagged as tenant, consider it tenant
|
|
||||||
// If the route has an identification middleware and the route is not universal, consider it tenant
|
|
||||||
if (
|
|
||||||
static::routeHasMiddleware($route, 'tenant') ||
|
|
||||||
(static::routeHasIdentificationMiddleware($route) && ! $routeIsUniversal)
|
|
||||||
) {
|
|
||||||
return RouteMode::TENANT;
|
return RouteMode::TENANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the route is universal, you have to determine its actual context using
|
|
||||||
// The identification middleware's determineUniversalRouteContextFromRequest
|
|
||||||
if ($routeIsUniversal) {
|
|
||||||
return RouteMode::UNIVERSAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config('tenancy.default_route_mode');
|
return config('tenancy.default_route_mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function routeIsUniversal(Route $route): bool
|
public static function routeIsUniversal(Route $route): bool
|
||||||
{
|
{
|
||||||
$routeFlaggedAsTenantOrCentral = static::routeHasMiddleware($route, 'tenant') || static::routeHasMiddleware($route, 'central');
|
|
||||||
$routeFlaggedAsUniversal = static::routeHasMiddleware($route, 'universal');
|
$routeFlaggedAsUniversal = static::routeHasMiddleware($route, 'universal');
|
||||||
$universalFlagUsedInGlobalStack = app(Kernel::class)->hasMiddleware('universal');
|
$universalFlagUsedInGlobalStack = app(Kernel::class)->hasMiddleware('universal');
|
||||||
$defaultRouteModeIsUniversal = config('tenancy.default_route_mode') === RouteMode::UNIVERSAL;
|
$defaultRouteModeIsUniversal = config('tenancy.default_route_mode') === RouteMode::UNIVERSAL;
|
||||||
|
|
||||||
return ! $routeFlaggedAsTenantOrCentral && ($routeFlaggedAsUniversal || $universalFlagUsedInGlobalStack || $defaultRouteModeIsUniversal);
|
return $routeFlaggedAsUniversal || $universalFlagUsedInGlobalStack || $defaultRouteModeIsUniversal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ class DatabaseConfig
|
||||||
$databaseManagers = config('tenancy.database.managers');
|
$databaseManagers = config('tenancy.database.managers');
|
||||||
|
|
||||||
if (! array_key_exists($driver, $databaseManagers)) {
|
if (! array_key_exists($driver, $databaseManagers)) {
|
||||||
throw new Exceptions\DatabaseManagerNotRegisteredException($driver);
|
throw new DatabaseManagerNotRegisteredException($driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
return app($databaseManagers[$driver]);
|
return app($databaseManagers[$driver]);
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,10 @@ class ForgetTenantParameter
|
||||||
public function handle(RouteMatched $event): void
|
public function handle(RouteMatched $event): void
|
||||||
{
|
{
|
||||||
$kernelPathIdentificationUsed = PathIdentificationManager::pathIdentificationInGlobalStack() && ! tenancy()->routeHasIdentificationMiddleware($event->route);
|
$kernelPathIdentificationUsed = PathIdentificationManager::pathIdentificationInGlobalStack() && ! tenancy()->routeHasIdentificationMiddleware($event->route);
|
||||||
$routeModeIsTenant = tenancy()->getRouteMode($event->route) === RouteMode::TENANT;
|
$routeMode = tenancy()->getRouteMode($event->route);
|
||||||
|
$routeModeIsTenantOrUniversal = $routeMode === RouteMode::TENANT || ($routeMode === RouteMode::UNIVERSAL && $event->route->hasParameter(PathIdentificationManager::getTenantParameterName()));
|
||||||
|
|
||||||
if ($kernelPathIdentificationUsed && $routeModeIsTenant) {
|
if ($kernelPathIdentificationUsed && $routeModeIsTenantOrUniversal) {
|
||||||
$event->route->forgetParameter(PathIdentificationManager::getTenantParameterName());
|
$event->route->forgetParameter(PathIdentificationManager::getTenantParameterName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
|
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
|
||||||
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
|
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
|
||||||
use Stancl\Tenancy\Enums\RouteMode;
|
|
||||||
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
||||||
|
use Stancl\Tenancy\PathIdentificationManager;
|
||||||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
use Stancl\Tenancy\Tenancy;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
|
|
@ -52,17 +52,10 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path identification request has a tenant if the middleware context is tenant.
|
* Request has tenant if the request's route has the tenant parameter.
|
||||||
*
|
|
||||||
* With path identification, we can just check the MW context because we're cloning the universal routes,
|
|
||||||
* and the routes are flagged with the 'tenant' MW group (= their MW context is tenant).
|
|
||||||
*
|
|
||||||
* With other identification middleware, we have to determine the context differently because we only have one
|
|
||||||
* truly universal route available ('truly universal' because with path identification, applying 'universal' to a route just means that
|
|
||||||
* it should get cloned, whereas with other ID MW, it means that the route you apply the 'universal' flag to will be accessible in both contexts).
|
|
||||||
*/
|
*/
|
||||||
public function requestHasTenant(Request $request): bool
|
public function requestHasTenant(Request $request): bool
|
||||||
{
|
{
|
||||||
return tenancy()->getRouteMode(tenancy()->getRoute($request)) === RouteMode::TENANT;
|
return tenancy()->getRoute($request)->hasParameter(PathIdentificationManager::getTenantParameterName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
return $instance;
|
return $instance;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::middlewareGroup('clone', []);
|
||||||
Route::middlewareGroup('universal', []);
|
Route::middlewareGroup('universal', []);
|
||||||
Route::middlewareGroup('tenant', []);
|
Route::middlewareGroup('tenant', []);
|
||||||
Route::middlewareGroup('central', []);
|
Route::middlewareGroup('central', []);
|
||||||
|
|
|
||||||
269
tests/CloneActionTest.php
Normal file
269
tests/CloneActionTest.php
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Routing\Route;
|
||||||
|
use Stancl\Tenancy\Enums\RouteMode;
|
||||||
|
use Illuminate\Contracts\Http\Kernel;
|
||||||
|
use Stancl\Tenancy\Actions\CloneRoutesAsTenant;
|
||||||
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
|
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||||
|
use Stancl\Tenancy\Tests\Etc\HasMiddlewareController;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||||
|
|
||||||
|
test('a route can be universal using path identification', function (array $routeMiddleware, array $globalMiddleware) {
|
||||||
|
foreach ($globalMiddleware as $middleware) {
|
||||||
|
if ($middleware === 'universal') {
|
||||||
|
config(['tenancy.default_route_mode' => RouteMode::UNIVERSAL]);
|
||||||
|
} else {
|
||||||
|
app(Kernel::class)->pushMiddleware($middleware);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteFacade::get('/foo', function () {
|
||||||
|
return tenancy()->initialized
|
||||||
|
? 'Tenancy is initialized.'
|
||||||
|
: 'Tenancy is not initialized.';
|
||||||
|
})->middleware($routeMiddleware);
|
||||||
|
|
||||||
|
config(['tenancy._tests.static_identification_middleware' => $routeMiddleware]);
|
||||||
|
|
||||||
|
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
||||||
|
|
||||||
|
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
||||||
|
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
||||||
|
|
||||||
|
$cloneRoutesAction->handle();
|
||||||
|
|
||||||
|
$tenantKey = Tenant::create()->getTenantKey();
|
||||||
|
|
||||||
|
pest()->get("http://localhost/foo")
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSee('Tenancy is not initialized.');
|
||||||
|
|
||||||
|
pest()->get("http://localhost/{$tenantKey}/foo")
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSee('Tenancy is initialized.');
|
||||||
|
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
pest()->get("http://localhost/bar")
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSee('Tenancy is not initialized.');
|
||||||
|
|
||||||
|
pest()->get("http://localhost/{$tenantKey}/bar")
|
||||||
|
->assertSuccessful()
|
||||||
|
->assertSee('Tenancy is initialized.');
|
||||||
|
})->with('path identification types');
|
||||||
|
|
||||||
|
test('CloneRoutesAsTenant registers prefixed duplicates of universal routes correctly', function (bool $kernelIdentification, bool $useController, string $tenantMiddleware) {
|
||||||
|
$routeMiddleware = ['universal'];
|
||||||
|
config(['tenancy.identification.path_identification_middleware' => [$tenantMiddleware]]);
|
||||||
|
|
||||||
|
if ($kernelIdentification) {
|
||||||
|
app(Kernel::class)->pushMiddleware($tenantMiddleware);
|
||||||
|
} else {
|
||||||
|
$routeMiddleware[] = $tenantMiddleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_parameter_name' => $tenantParameterName = 'team']);
|
||||||
|
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_route_name_prefix' => $tenantRouteNamePrefix = 'team-route.']);
|
||||||
|
|
||||||
|
// Test that routes with controllers as well as routes with closure actions get cloned correctly
|
||||||
|
$universalRoute = RouteFacade::get('/home', $useController ? Controller::class : fn () => tenant() ? 'Tenancy is initialized.' : 'Tenancy is not initialized.')->middleware($routeMiddleware)->name('home');
|
||||||
|
$centralRoute = RouteFacade::get('/central', fn () => true)->name('central');
|
||||||
|
|
||||||
|
config(['tenancy._tests.static_identification_middleware' => $routeMiddleware]);
|
||||||
|
|
||||||
|
$universalRoute2 = RouteFacade::get('/bar', [HasMiddlewareController::class, 'index'])->name('second-home');
|
||||||
|
|
||||||
|
expect($routes = RouteFacade::getRoutes()->get())->toContain($universalRoute)
|
||||||
|
->toContain($universalRoute2)
|
||||||
|
->toContain($centralRoute);
|
||||||
|
|
||||||
|
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
||||||
|
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
||||||
|
|
||||||
|
$cloneRoutesAction->handle();
|
||||||
|
|
||||||
|
expect($routesAfterRegisteringDuplicates = RouteFacade::getRoutes()->get())
|
||||||
|
->toContain($universalRoute)
|
||||||
|
->toContain($centralRoute);
|
||||||
|
|
||||||
|
$newRoutes = collect($routesAfterRegisteringDuplicates)->filter(fn ($route) => ! in_array($route, $routes));
|
||||||
|
|
||||||
|
expect($newRoutes->first()->uri())->toBe('{' . $tenantParameterName . '}' . '/' . $universalRoute->uri());
|
||||||
|
expect($newRoutes->last()->uri())->toBe('{' . $tenantParameterName . '}' . '/' . $universalRoute2->uri());
|
||||||
|
|
||||||
|
// Universal flag is excluded from the route middleware
|
||||||
|
expect(tenancy()->getRouteMiddleware($newRoutes->first()))
|
||||||
|
->toEqualCanonicalizing(
|
||||||
|
array_filter(array_merge(tenancy()->getRouteMiddleware($universalRoute), ['tenant']),
|
||||||
|
fn($middleware) => $middleware !== 'universal')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Universal flag is provided statically in the route's controller, so we cannot exclude it
|
||||||
|
expect(tenancy()->getRouteMiddleware($newRoutes->last()))
|
||||||
|
->toEqualCanonicalizing(
|
||||||
|
array_merge(tenancy()->getRouteMiddleware($universalRoute2), ['tenant'])
|
||||||
|
);
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
pest()->get(route($centralRouteName = $universalRoute->getName()))->assertSee('Tenancy is not initialized.');
|
||||||
|
pest()->get(route($centralRouteName2 = $universalRoute2->getName()))->assertSee('Tenancy is not initialized.');
|
||||||
|
pest()->get(route($tenantRouteName = $newRoutes->first()->getName(), [$tenantParameterName => $tenant->getTenantKey()]))->assertSee('Tenancy is initialized.');
|
||||||
|
tenancy()->end();
|
||||||
|
pest()->get(route($tenantRouteName2 = $newRoutes->last()->getName(), [$tenantParameterName => $tenant->getTenantKey()]))->assertSee('Tenancy is initialized.');
|
||||||
|
|
||||||
|
expect($tenantRouteName)->toBe($tenantRouteNamePrefix . $universalRoute->getName());
|
||||||
|
expect($tenantRouteName2)->toBe($tenantRouteNamePrefix . $universalRoute2->getName());
|
||||||
|
expect($centralRouteName)->toBe($universalRoute->getName());
|
||||||
|
expect($centralRouteName2)->toBe($universalRoute2->getName());
|
||||||
|
})->with([
|
||||||
|
'kernel identification' => true,
|
||||||
|
'route-level identification' => false,
|
||||||
|
// Creates a matrix (multiple with())
|
||||||
|
])->with([
|
||||||
|
'use controller' => true,
|
||||||
|
'use closure' => false
|
||||||
|
])->with([
|
||||||
|
'path identification middleware' => InitializeTenancyByPath::class,
|
||||||
|
'custom path identification middleware' => CustomInitializeTenancyByPath::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
test('CloneRoutesAsTenant only clones routes with path identification by default', function () {
|
||||||
|
app(Kernel::class)->pushMiddleware(InitializeTenancyByPath::class);
|
||||||
|
|
||||||
|
$currentRouteCount = fn () => count(RouteFacade::getRoutes()->get());
|
||||||
|
|
||||||
|
$initialRouteCount = $currentRouteCount();
|
||||||
|
|
||||||
|
// Path identification is used globally, and this route doesn't use a specific identification middleware, meaning path identification is used and the route should get cloned
|
||||||
|
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware('universal')->name('home');
|
||||||
|
// The route uses a specific identification middleware other than InitializeTenancyByPath – the route shouldn't get cloned
|
||||||
|
RouteFacade::get('/home-domain-id', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware(['universal', InitializeTenancyByDomain::class])->name('home-domain-id');
|
||||||
|
|
||||||
|
expect($currentRouteCount())->toBe($newRouteCount = $initialRouteCount + 2);
|
||||||
|
|
||||||
|
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
||||||
|
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
||||||
|
|
||||||
|
$cloneRoutesAction->handle();
|
||||||
|
|
||||||
|
// Only one of the two routes gets cloned
|
||||||
|
expect($currentRouteCount())->toBe($newRouteCount + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom callbacks can be used for cloning universal routes', function () {
|
||||||
|
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware(['universal', InitializeTenancyByPath::class])->name($routeName = 'home');
|
||||||
|
|
||||||
|
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
||||||
|
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
||||||
|
$currentRouteCount = fn () => count(RouteFacade::getRoutes()->get());
|
||||||
|
$initialRouteCount = $currentRouteCount();
|
||||||
|
|
||||||
|
$cloneRoutesAction;
|
||||||
|
|
||||||
|
// Skip cloning the 'home' route
|
||||||
|
$cloneRoutesAction->cloneUsing($routeName, function (Route $route) {
|
||||||
|
return;
|
||||||
|
})->handle();
|
||||||
|
|
||||||
|
// Expect route count to stay the same because the 'home' route cloning gets skipped
|
||||||
|
expect($initialRouteCount)->toEqual($currentRouteCount());
|
||||||
|
|
||||||
|
// Modify the 'home' route cloning so that a different route is cloned
|
||||||
|
$cloneRoutesAction->cloneUsing($routeName, function (Route $route) {
|
||||||
|
RouteFacade::get('/cloned-route', fn () => true)->name('new.home');
|
||||||
|
})->handle();
|
||||||
|
|
||||||
|
expect($currentRouteCount())->toEqual($initialRouteCount + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cloning of specific routes can get skipped', function () {
|
||||||
|
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware('universal')->name($routeName = 'home');
|
||||||
|
|
||||||
|
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
||||||
|
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
||||||
|
$currentRouteCount = fn () => count(RouteFacade::getRoutes()->get());
|
||||||
|
$initialRouteCount = $currentRouteCount();
|
||||||
|
|
||||||
|
// Skip cloning the 'home' route
|
||||||
|
$cloneRoutesAction->skipRoute($routeName);
|
||||||
|
|
||||||
|
$cloneRoutesAction->handle();
|
||||||
|
|
||||||
|
// Expect route count to stay the same because the 'home' route cloning gets skipped
|
||||||
|
expect($initialRouteCount)->toEqual($currentRouteCount());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('routes except nonuniversal routes with path id mw are given the tenant flag after cloning', function (array $routeMiddleware, array $globalMiddleware) {
|
||||||
|
foreach ($globalMiddleware as $middleware) {
|
||||||
|
if ($middleware === 'universal') {
|
||||||
|
config(['tenancy.default_route_mode' => RouteMode::UNIVERSAL]);
|
||||||
|
} else {
|
||||||
|
app(Kernel::class)->pushMiddleware($middleware);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$route = RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')
|
||||||
|
->middleware($routeMiddleware)
|
||||||
|
->name($routeName = 'home');
|
||||||
|
|
||||||
|
app(CloneRoutesAsTenant::class)->handle();
|
||||||
|
|
||||||
|
$clonedRoute = RouteFacade::getRoutes()->getByName('tenant.' . $routeName);
|
||||||
|
|
||||||
|
// Non-universal routes with identification middleware are already considered tenant, so they don't get the tenant flag
|
||||||
|
if (! tenancy()->routeIsUniversal($route) && tenancy()->routeHasIdentificationMiddleware($clonedRoute)) {
|
||||||
|
expect($clonedRoute->middleware())->not()->toContain('tenant');
|
||||||
|
} else {
|
||||||
|
expect($clonedRoute->middleware())->toContain('tenant');
|
||||||
|
}
|
||||||
|
})->with('path identification types');
|
||||||
|
|
||||||
|
test('routes with the clone flag get cloned without making the routes universal', function ($identificationMiddleware) {
|
||||||
|
config(['tenancy.identification.path_identification_middleware' => [$identificationMiddleware]]);
|
||||||
|
|
||||||
|
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')
|
||||||
|
->middleware(['clone', $identificationMiddleware])
|
||||||
|
->name($routeName = 'home');
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
app(CloneRoutesAsTenant::class)->handle();
|
||||||
|
|
||||||
|
$clonedRoute = RouteFacade::getRoutes()->getByName('tenant.' . $routeName);
|
||||||
|
|
||||||
|
expect($clonedRoute->middleware())->toEqualCanonicalizing(['tenant', $identificationMiddleware]);
|
||||||
|
|
||||||
|
// The original route is not accessible
|
||||||
|
pest()->get(route($routeName))->assertServerError();
|
||||||
|
pest()->get(route($routeName, ['tenant' => $tenant]))->assertServerError();
|
||||||
|
// The cloned route is a tenant route
|
||||||
|
pest()->get(route('tenant.' . $routeName, ['tenant' => $tenant]))->assertSee('Tenancy initialized.');
|
||||||
|
})->with([InitializeTenancyByPath::class, CustomInitializeTenancyByPath::class]);
|
||||||
|
|
||||||
|
class CustomInitializeTenancyByPath extends InitializeTenancyByPath
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dataset('path identification types', [
|
||||||
|
'kernel identification' => [
|
||||||
|
'route_middleware' => ['universal'],
|
||||||
|
'global_middleware' => [InitializeTenancyByPath::class],
|
||||||
|
],
|
||||||
|
'route-level identification' => [
|
||||||
|
'route_middleware' => ['universal', InitializeTenancyByPath::class],
|
||||||
|
'global_middleware' => [],
|
||||||
|
],
|
||||||
|
'kernel identification + defaulting to universal routes' => [
|
||||||
|
'route_middleware' => [],
|
||||||
|
'global_middleware' => ['universal', InitializeTenancyByPath::class],
|
||||||
|
],
|
||||||
|
'route-level identification + defaulting to universal routes' => [
|
||||||
|
'route_middleware' => [InitializeTenancyByPath::class],
|
||||||
|
'global_middleware' => ['universal'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
@ -430,18 +430,6 @@ test('route level identification is prioritized over kernel identification', fun
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
'default to central routes' => RouteMode::CENTRAL,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('routes with path identification middleware can get prefixed using the clone action', function() {
|
|
||||||
$tenantKey = Tenant::create()->getTenantKey();
|
|
||||||
|
|
||||||
RouteFacade::get('/home', fn () => tenant()?->getTenantKey())->name('home')->middleware(InitializeTenancyByPath::class);
|
|
||||||
|
|
||||||
pest()->get("http://localhost/$tenantKey/home")->assertNotFound();
|
|
||||||
|
|
||||||
app(CloneRoutesAsTenant::class)->handle();
|
|
||||||
|
|
||||||
pest()->get("http://localhost/$tenantKey/home")->assertOk();
|
|
||||||
});
|
|
||||||
|
|
||||||
function assertTenancyInitializedInEarlyIdentificationRequest(bool $expect = true): void
|
function assertTenancyInitializedInEarlyIdentificationRequest(bool $expect = true): void
|
||||||
{
|
{
|
||||||
expect(app()->make('additionalMiddlewareRunsInTenantContext'))->toBe($expect); // Assert that middleware added in the controller constructor runs in tenant context
|
expect(app()->make('additionalMiddlewareRunsInTenantContext'))->toBe($expect); // Assert that middleware added in the controller constructor runs in tenant context
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class HasMiddlewareController implements HasMiddleware
|
||||||
{
|
{
|
||||||
public static function middleware()
|
public static function middleware()
|
||||||
{
|
{
|
||||||
return array_map(fn (string $middleware) => new Middleware($middleware), config('tenancy.static_identification_middleware'));
|
return array_map(fn (string $middleware) => new Middleware($middleware), config('tenancy._tests.static_identification_middleware'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
|
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
use Stancl\Tenancy\Exceptions\MiddlewareNotUsableWithUniversalRoutesException;
|
use Stancl\Tenancy\Exceptions\MiddlewareNotUsableWithUniversalRoutesException;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
||||||
|
|
@ -43,7 +42,7 @@ test('a route can be universal using domain identification', function (array $ro
|
||||||
: 'Tenancy is not initialized.';
|
: 'Tenancy is not initialized.';
|
||||||
})->middleware($routeMiddleware);
|
})->middleware($routeMiddleware);
|
||||||
|
|
||||||
config(['tenancy.static_identification_middleware' => $routeMiddleware]);
|
config(['tenancy._tests.static_identification_middleware' => $routeMiddleware]);
|
||||||
|
|
||||||
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
||||||
|
|
||||||
|
|
@ -87,7 +86,7 @@ test('a route can be universal using subdomain identification', function (array
|
||||||
: 'Tenancy is not initialized.';
|
: 'Tenancy is not initialized.';
|
||||||
})->middleware($routeMiddleware);
|
})->middleware($routeMiddleware);
|
||||||
|
|
||||||
config(['tenancy.static_identification_middleware' => $routeMiddleware]);
|
config(['tenancy._tests.static_identification_middleware' => $routeMiddleware]);
|
||||||
|
|
||||||
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
||||||
|
|
||||||
|
|
@ -132,7 +131,7 @@ test('a route can be universal using domainOrSubdomain identification', function
|
||||||
: 'Tenancy is not initialized.';
|
: 'Tenancy is not initialized.';
|
||||||
})->middleware($routeMiddleware);
|
})->middleware($routeMiddleware);
|
||||||
|
|
||||||
config(['tenancy.static_identification_middleware' => $routeMiddleware]);
|
config(['tenancy._tests.static_identification_middleware' => $routeMiddleware]);
|
||||||
|
|
||||||
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
||||||
|
|
||||||
|
|
@ -194,7 +193,7 @@ test('a route can be universal using request data identification', function (arr
|
||||||
: 'Tenancy is not initialized.';
|
: 'Tenancy is not initialized.';
|
||||||
})->middleware($routeMiddleware);
|
})->middleware($routeMiddleware);
|
||||||
|
|
||||||
config(['tenancy.static_identification_middleware' => $routeMiddleware]);
|
config(['tenancy._tests.static_identification_middleware' => $routeMiddleware]);
|
||||||
|
|
||||||
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
||||||
|
|
||||||
|
|
@ -219,51 +218,6 @@ test('a route can be universal using request data identification', function (arr
|
||||||
->assertSee('Tenancy is initialized.');
|
->assertSee('Tenancy is initialized.');
|
||||||
})->with('request data identification types');
|
})->with('request data identification types');
|
||||||
|
|
||||||
test('a route can be universal using path identification', function (array $routeMiddleware, array $globalMiddleware) {
|
|
||||||
foreach ($globalMiddleware as $middleware) {
|
|
||||||
if ($middleware === 'universal') {
|
|
||||||
config(['tenancy.default_route_mode' => RouteMode::UNIVERSAL]);
|
|
||||||
} else {
|
|
||||||
app(Kernel::class)->pushMiddleware($middleware);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RouteFacade::get('/foo', function () {
|
|
||||||
return tenancy()->initialized
|
|
||||||
? 'Tenancy is initialized.'
|
|
||||||
: 'Tenancy is not initialized.';
|
|
||||||
})->middleware($routeMiddleware);
|
|
||||||
|
|
||||||
config(['tenancy.static_identification_middleware' => $routeMiddleware]);
|
|
||||||
|
|
||||||
RouteFacade::get('/bar', [HasMiddlewareController::class, 'index']);
|
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
|
||||||
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
|
||||||
|
|
||||||
$cloneRoutesAction->handle();
|
|
||||||
|
|
||||||
$tenantKey = Tenant::create()->getTenantKey();
|
|
||||||
|
|
||||||
pest()->get("http://localhost/foo")
|
|
||||||
->assertSuccessful()
|
|
||||||
->assertSee('Tenancy is not initialized.');
|
|
||||||
|
|
||||||
pest()->get("http://localhost/{$tenantKey}/foo")
|
|
||||||
->assertSuccessful()
|
|
||||||
->assertSee('Tenancy is initialized.');
|
|
||||||
|
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
pest()->get("http://localhost/bar")
|
|
||||||
->assertSuccessful()
|
|
||||||
->assertSee('Tenancy is not initialized.');
|
|
||||||
|
|
||||||
pest()->get("http://localhost/{$tenantKey}/bar")
|
|
||||||
->assertSuccessful()
|
|
||||||
->assertSee('Tenancy is initialized.');
|
|
||||||
})->with('path identification types');
|
|
||||||
|
|
||||||
test('correct exception is thrown when route is universal and tenant could not be identified using domain identification', function (array $routeMiddleware, array $globalMiddleware) {
|
test('correct exception is thrown when route is universal and tenant could not be identified using domain identification', function (array $routeMiddleware, array $globalMiddleware) {
|
||||||
foreach ($globalMiddleware as $middleware) {
|
foreach ($globalMiddleware as $middleware) {
|
||||||
if ($middleware === 'universal') {
|
if ($middleware === 'universal') {
|
||||||
|
|
@ -302,26 +256,6 @@ test('correct exception is thrown when route is universal and tenant could not b
|
||||||
$this->withoutExceptionHandling()->get('http://nonexistent_subdomain.localhost/foo');
|
$this->withoutExceptionHandling()->get('http://nonexistent_subdomain.localhost/foo');
|
||||||
})->with('subdomain identification types');
|
})->with('subdomain identification types');
|
||||||
|
|
||||||
test('correct exception is thrown when route is universal and tenant could not be identified using path identification', function (array $routeMiddleware, array $globalMiddleware) {
|
|
||||||
foreach ($globalMiddleware as $middleware) {
|
|
||||||
if ($middleware === 'universal') {
|
|
||||||
config(['tenancy.default_route_mode' => RouteMode::UNIVERSAL]);
|
|
||||||
} else {
|
|
||||||
app(Kernel::class)->pushMiddleware($middleware);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RouteFacade::get('/foo', fn () => tenant() ? 'Tenancy is initialized.' : 'Tenancy is not initialized.')->middleware($routeMiddleware)->name('foo');
|
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
|
||||||
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
|
||||||
|
|
||||||
$cloneRoutesAction->handle();
|
|
||||||
|
|
||||||
pest()->expectException(TenantCouldNotBeIdentifiedByPathException::class);
|
|
||||||
$this->withoutExceptionHandling()->get('http://localhost/non_existent/foo');
|
|
||||||
})->with('path identification types');
|
|
||||||
|
|
||||||
test('correct exception is thrown when route is universal and tenant could not be identified using request data identification', function (array $routeMiddleware, array $globalMiddleware) {
|
test('correct exception is thrown when route is universal and tenant could not be identified using request data identification', function (array $routeMiddleware, array $globalMiddleware) {
|
||||||
foreach ($globalMiddleware as $middleware) {
|
foreach ($globalMiddleware as $middleware) {
|
||||||
if ($middleware === 'universal') {
|
if ($middleware === 'universal') {
|
||||||
|
|
@ -341,29 +275,15 @@ test('correct exception is thrown when route is universal and tenant could not b
|
||||||
$this->withoutExceptionHandling()->get('http://localhost/foo?tenant=nonexistent_tenant');
|
$this->withoutExceptionHandling()->get('http://localhost/foo?tenant=nonexistent_tenant');
|
||||||
})->with('request data identification types');
|
})->with('request data identification types');
|
||||||
|
|
||||||
test('tenant and central flags override the universal flag', function () {
|
test('route is made universal by adding the universal flag using request data identification', function () {
|
||||||
app(Kernel::class)->pushMiddleware(InitializeTenancyByRequestData::class);
|
app(Kernel::class)->pushMiddleware(InitializeTenancyByRequestData::class);
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
$route = RouteFacade::get('/route', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware('universal');
|
RouteFacade::get('/route', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware('universal');
|
||||||
|
|
||||||
// Route is universal
|
// Route is universal
|
||||||
pest()->get('/route')->assertOk()->assertSee('Tenancy not initialized.');
|
pest()->get('/route')->assertOk()->assertSee('Tenancy not initialized.');
|
||||||
pest()->get('/route?tenant=' . $tenant->getTenantKey())->assertOk()->assertSee('Tenancy initialized.');
|
pest()->get('/route?tenant=' . $tenant->getTenantKey())->assertOk()->assertSee('Tenancy initialized.');
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
// Route is in tenant context
|
|
||||||
$route->action['middleware'] = ['universal', 'tenant'];
|
|
||||||
|
|
||||||
pest()->get('/route')->assertServerError(); // "Tenant could not be identified by request data with payload..."
|
|
||||||
pest()->get('/route?tenant=' . $tenant->getTenantKey())->assertOk()->assertSee('Tenancy initialized.');
|
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
// Route is in central context
|
|
||||||
$route->action['middleware'] = ['universal', 'central'];
|
|
||||||
|
|
||||||
pest()->get('/route')->assertOk()->assertSee('Tenancy not initialized.');
|
|
||||||
pest()->get('/route?tenant=' . $tenant->getTenantKey())->assertOk()->assertSee('Tenancy not initialized.'); // Route is accessible, but the context is central
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a route can be flagged as universal in both route modes', function (RouteMode $defaultRouteMode) {
|
test('a route can be flagged as universal in both route modes', function (RouteMode $defaultRouteMode) {
|
||||||
|
|
@ -388,67 +308,6 @@ test('a route can be flagged as universal in both route modes', function (RouteM
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
'default to central routes' => RouteMode::CENTRAL,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('CloneRoutesAsTenant registers prefixed duplicates of universal routes correctly', function (bool $kernelIdentification, bool $useController) {
|
|
||||||
$routeMiddleware = ['universal'];
|
|
||||||
|
|
||||||
if ($kernelIdentification) {
|
|
||||||
app(Kernel::class)->pushMiddleware(InitializeTenancyByPath::class);
|
|
||||||
} else {
|
|
||||||
$routeMiddleware[] = InitializeTenancyByPath::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_parameter_name' => $tenantParameterName = 'team']);
|
|
||||||
config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.tenant_route_name_prefix' => $tenantRouteNamePrefix = 'team-route.']);
|
|
||||||
|
|
||||||
// Test that routes with controllers as well as routes with closure actions get cloned correctly
|
|
||||||
$universalRoute = RouteFacade::get('/home', $useController ? Controller::class : fn () => tenant() ? 'Tenancy is initialized.' : 'Tenancy is not initialized.')->middleware($routeMiddleware)->name('home');
|
|
||||||
$centralRoute = RouteFacade::get('/central', fn () => true)->name('central');
|
|
||||||
|
|
||||||
config(['tenancy.static_identification_middleware' => $routeMiddleware]);
|
|
||||||
|
|
||||||
$universalRoute2 = RouteFacade::get('/bar', [HasMiddlewareController::class, 'index'])->name('second-home');
|
|
||||||
|
|
||||||
expect($routes = RouteFacade::getRoutes()->get())->toContain($universalRoute)
|
|
||||||
->toContain($universalRoute2)
|
|
||||||
->toContain($centralRoute);
|
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
|
||||||
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
|
||||||
|
|
||||||
$cloneRoutesAction->handle();
|
|
||||||
|
|
||||||
expect($routesAfterRegisteringDuplicates = RouteFacade::getRoutes()->get())
|
|
||||||
->toContain($universalRoute)
|
|
||||||
->toContain($centralRoute);
|
|
||||||
|
|
||||||
$newRoutes = collect($routesAfterRegisteringDuplicates)->filter(fn ($route) => ! in_array($route, $routes));
|
|
||||||
|
|
||||||
expect($newRoutes->first()->uri())->toBe('{' . $tenantParameterName . '}' . '/' . $universalRoute->uri());
|
|
||||||
expect($newRoutes->last()->uri())->toBe('{' . $tenantParameterName . '}' . '/' . $universalRoute2->uri());
|
|
||||||
|
|
||||||
expect(tenancy()->getRouteMiddleware($newRoutes->first()))->toBe(array_merge(tenancy()->getRouteMiddleware($universalRoute), ['tenant']));
|
|
||||||
expect(tenancy()->getRouteMiddleware($newRoutes->last()))->toBe(array_merge(tenancy()->getRouteMiddleware($universalRoute2), ['tenant']));
|
|
||||||
|
|
||||||
$tenant = Tenant::create();
|
|
||||||
|
|
||||||
pest()->get(route($centralRouteName = $universalRoute->getName()))->assertSee('Tenancy is not initialized.');
|
|
||||||
pest()->get(route($centralRouteName2 = $universalRoute2->getName()))->assertSee('Tenancy is not initialized.');
|
|
||||||
pest()->get(route($tenantRouteName = $newRoutes->first()->getName(), [$tenantParameterName => $tenant->getTenantKey()]))->assertSee('Tenancy is initialized.');
|
|
||||||
tenancy()->end();
|
|
||||||
pest()->get(route($tenantRouteName2 = $newRoutes->last()->getName(), [$tenantParameterName => $tenant->getTenantKey()]))->assertSee('Tenancy is initialized.');
|
|
||||||
|
|
||||||
expect($tenantRouteName)->toBe($tenantRouteNamePrefix . $universalRoute->getName());
|
|
||||||
expect($tenantRouteName2)->toBe($tenantRouteNamePrefix . $universalRoute2->getName());
|
|
||||||
expect($centralRouteName)->toBe($universalRoute->getName());
|
|
||||||
expect($centralRouteName2)->toBe($universalRoute2->getName());
|
|
||||||
})->with([
|
|
||||||
'kernel identification' => true,
|
|
||||||
'route-level identification' => false,
|
|
||||||
// Creates a matrix (multiple with())
|
|
||||||
])->with([
|
|
||||||
'use controller' => true,
|
|
||||||
'use closure' => false
|
|
||||||
]);
|
|
||||||
|
|
||||||
test('tenant resolver methods return the correct names for configured values', function (string $configurableParameter, string $value) {
|
test('tenant resolver methods return the correct names for configured values', function (string $configurableParameter, string $value) {
|
||||||
$configurableParameterConfigKey = 'tenancy.identification.resolvers.' . PathTenantResolver::class . '.' . $configurableParameter;
|
$configurableParameterConfigKey = 'tenancy.identification.resolvers.' . PathTenantResolver::class . '.' . $configurableParameter;
|
||||||
|
|
@ -464,73 +323,6 @@ test('tenant resolver methods return the correct names for configured values', f
|
||||||
['tenant_route_name_prefix', 'prefix']
|
['tenant_route_name_prefix', 'prefix']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('CloneRoutesAsTenant only clones routes with path identification by default', function () {
|
|
||||||
app(Kernel::class)->pushMiddleware(InitializeTenancyByPath::class);
|
|
||||||
|
|
||||||
$currentRouteCount = fn () => count(RouteFacade::getRoutes()->get());
|
|
||||||
|
|
||||||
$initialRouteCount = $currentRouteCount();
|
|
||||||
|
|
||||||
// Path identification is used globally, and this route doesn't use a specific identification middleware, meaning path identification is used and the route should get cloned
|
|
||||||
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware('universal')->name('home');
|
|
||||||
// The route uses a specific identification middleware other than InitializeTenancyByPath – the route shouldn't get cloned
|
|
||||||
RouteFacade::get('/home-domain-id', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware(['universal', InitializeTenancyByDomain::class])->name('home-domain-id');
|
|
||||||
|
|
||||||
expect($currentRouteCount())->toBe($newRouteCount = $initialRouteCount + 2);
|
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
|
||||||
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
|
||||||
|
|
||||||
$cloneRoutesAction->handle();
|
|
||||||
|
|
||||||
// Only one of the two routes gets cloned
|
|
||||||
expect($currentRouteCount())->toBe($newRouteCount + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('custom callbacks can be used for cloning universal routes', function () {
|
|
||||||
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware(['universal', InitializeTenancyByPath::class])->name($routeName = 'home');
|
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
|
||||||
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
|
||||||
$currentRouteCount = fn () => count(RouteFacade::getRoutes()->get());
|
|
||||||
$initialRouteCount = $currentRouteCount();
|
|
||||||
|
|
||||||
$cloneRoutesAction;
|
|
||||||
|
|
||||||
// Skip cloning the 'home' route
|
|
||||||
$cloneRoutesAction->cloneUsing($routeName, function (Route $route) {
|
|
||||||
return;
|
|
||||||
})->handle();
|
|
||||||
|
|
||||||
// Expect route count to stay the same because the 'home' route cloning gets skipped
|
|
||||||
expect($initialRouteCount)->toEqual($currentRouteCount());
|
|
||||||
|
|
||||||
// Modify the 'home' route cloning so that a different route is cloned
|
|
||||||
$cloneRoutesAction->cloneUsing($routeName, function (Route $route) {
|
|
||||||
RouteFacade::get('/cloned-route', fn () => true)->name('new.home');
|
|
||||||
})->handle();
|
|
||||||
|
|
||||||
expect($currentRouteCount())->toEqual($initialRouteCount + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cloning of specific routes can get skipped', function () {
|
|
||||||
RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')->middleware('universal')->name($routeName = 'home');
|
|
||||||
|
|
||||||
/** @var CloneRoutesAsTenant $cloneRoutesAction */
|
|
||||||
$cloneRoutesAction = app(CloneRoutesAsTenant::class);
|
|
||||||
$currentRouteCount = fn () => count(RouteFacade::getRoutes()->get());
|
|
||||||
$initialRouteCount = $currentRouteCount();
|
|
||||||
|
|
||||||
// Skip cloning the 'home' route
|
|
||||||
$cloneRoutesAction->skipRoute($routeName);
|
|
||||||
|
|
||||||
$cloneRoutesAction->handle();
|
|
||||||
|
|
||||||
// Expect route count to stay the same because the 'home' route cloning gets skipped
|
|
||||||
expect($initialRouteCount)->toEqual($currentRouteCount());
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('identification middleware works with universal routes only when it implements MiddlewareUsableWithUniversalRoutes', function () {
|
test('identification middleware works with universal routes only when it implements MiddlewareUsableWithUniversalRoutes', function () {
|
||||||
$tenantKey = Tenant::create()->getTenantKey();
|
$tenantKey = Tenant::create()->getTenantKey();
|
||||||
$routeAction = fn () => tenancy()->initialized ? $tenantKey : 'Tenancy is not initialized.';
|
$routeAction = fn () => tenancy()->initialized ? $tenantKey : 'Tenancy is not initialized.';
|
||||||
|
|
@ -554,36 +346,10 @@ test('identification middleware works with universal routes only when it impleme
|
||||||
$this->withoutExceptionHandling()->get('http://localhost/custom-mw-universal-route');
|
$this->withoutExceptionHandling()->get('http://localhost/custom-mw-universal-route');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('routes except nonuniversal routes with path id mw are given the tenant flag after cloning', function (array $routeMiddleware, array $globalMiddleware) {
|
|
||||||
foreach ($globalMiddleware as $middleware) {
|
|
||||||
if ($middleware === 'universal') {
|
|
||||||
config(['tenancy.default_route_mode' => RouteMode::UNIVERSAL]);
|
|
||||||
} else {
|
|
||||||
app(Kernel::class)->pushMiddleware($middleware);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$route = RouteFacade::get('/home', fn () => tenant() ? 'Tenancy initialized.' : 'Tenancy not initialized.')
|
|
||||||
->middleware($routeMiddleware)
|
|
||||||
->name($routeName = 'home');
|
|
||||||
|
|
||||||
app(CloneRoutesAsTenant::class)->handle();
|
|
||||||
|
|
||||||
$clonedRoute = RouteFacade::getRoutes()->getByName('tenant.' . $routeName);
|
|
||||||
|
|
||||||
// Non-universal routes with identification middleware are already considered tenant, so they don't get the tenant flag
|
|
||||||
if (! tenancy()->routeIsUniversal($route) && tenancy()->routeHasIdentificationMiddleware($clonedRoute)) {
|
|
||||||
expect($clonedRoute->middleware())->not()->toContain('tenant');
|
|
||||||
} else {
|
|
||||||
expect($clonedRoute->middleware())->toContain('tenant');
|
|
||||||
}
|
|
||||||
})->with('path identification types');
|
|
||||||
|
|
||||||
foreach ([
|
foreach ([
|
||||||
'domain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
|
'domain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
|
||||||
'subdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
|
'subdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
|
||||||
'domainOrSubdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomainOrSubdomain::class],
|
'domainOrSubdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomainOrSubdomain::class],
|
||||||
'path identification types' => [InitializeTenancyByPath::class],
|
|
||||||
'request data identification types' => [InitializeTenancyByRequestData::class],
|
'request data identification types' => [InitializeTenancyByRequestData::class],
|
||||||
] as $datasetName => $middleware) {
|
] as $datasetName => $middleware) {
|
||||||
dataset($datasetName, [
|
dataset($datasetName, [
|
||||||
|
|
@ -606,14 +372,6 @@ foreach ([
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Controller extends BaseController
|
|
||||||
{
|
|
||||||
public function __invoke()
|
|
||||||
{
|
|
||||||
return tenant() ? 'Tenancy is initialized.' : 'Tenancy is not initialized.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomMiddleware extends IdentificationMiddleware
|
class CustomMiddleware extends IdentificationMiddleware
|
||||||
{
|
{
|
||||||
use UsableWithEarlyIdentification;
|
use UsableWithEarlyIdentification;
|
||||||
|
|
@ -652,3 +410,11 @@ class CustomMiddleware extends IdentificationMiddleware
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
public function __invoke()
|
||||||
|
{
|
||||||
|
return tenant() ? 'Tenancy is initialized.' : 'Tenancy is not initialized.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue