mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 20:14:03 +00:00
Improve early identification tests (#66)
* Separate route-level domain identification test from path/request to improve readability WIP * Get rid of confusing datasets in route-level identifcation tests * Clean up updated tests * Simplify early id tests * Reduce dataset duplication * Improve test readability, fix false positive, polish details * Separate early ID test from defaulting test (WIP) * Finish improving and correcting the early identification/default route mode tests * Make flag/default mode usage more clear by improving the docblock in DealsWithRouteContexts * Fix PHPUnit deprecation warnings * code review * code review --------- Co-authored-by: Samuel Štancl <samuel@archte.ch>
This commit is contained in:
parent
48b916e182
commit
cb0d7e2902
7 changed files with 248 additions and 147 deletions
|
|
@ -22,18 +22,32 @@ use Stancl\Tenancy\Enums\RouteMode;
|
||||||
trait DealsWithRouteContexts
|
trait DealsWithRouteContexts
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get route's middleware context (tenant, central or universal).
|
* Get the middleware context of a route (tenant, central, or universal).
|
||||||
* The context is determined by the route's middleware.
|
|
||||||
*
|
*
|
||||||
* If the route has the 'universal' middleware, the context is universal,
|
* If the route has the 'universal' middleware, the context is universal,
|
||||||
* and the route is accessible from both contexts.
|
* and the route is accessible from both contexts.
|
||||||
|
*
|
||||||
* The universal flag has the highest priority.
|
* The universal flag has the highest priority.
|
||||||
*
|
*
|
||||||
* If the route has the 'central' middleware, the context is central.
|
* If you want a universal route to be accessible from the tenant context,
|
||||||
* If the route has the 'tenant' middleware, or any tenancy identification middleware, the context is tenant.
|
* you still have to provide an identification middleware either using
|
||||||
|
* route-level middleware or in the global middleware stack.
|
||||||
*
|
*
|
||||||
* If the route doesn't have any of the mentioned middleware,
|
* If the 'tenant' group has identification middleware, you can use it in
|
||||||
|
* combination with the 'universal' flag, the route will still be universal.
|
||||||
|
*
|
||||||
|
* If the route has the 'tenant' middleware, or any tenancy identification
|
||||||
|
* middleware, the context is tenant (assuming the route doesn't also have
|
||||||
|
* the 'universal' flag).
|
||||||
|
*
|
||||||
|
* If the route has the 'central' middleware, the context is central.
|
||||||
|
*
|
||||||
|
* If the route doesn't have any of the mentioned flags/middleware,
|
||||||
* the context is determined by the `tenancy.default_route_mode` config.
|
* the context is determined by the `tenancy.default_route_mode` config.
|
||||||
|
*
|
||||||
|
* If the default route mode is tenant, all unflagged routes will be tenant by default,
|
||||||
|
* but they will still have to have an identification midddleware (route-level
|
||||||
|
* or global) to be accessible. Same applies for universal default route mode.
|
||||||
*/
|
*/
|
||||||
public static function getRouteMode(Route $route): RouteMode
|
public static function getRouteMode(Route $route): RouteMode
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,14 @@ test('central helper can be used in tenant requests', function (bool $enabled, b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})->with([
|
})->with([
|
||||||
['enabled' => false, 'shouldThrow' => true],
|
[
|
||||||
['enabled' => true, 'shouldThrow' => false],
|
false, // Disabled
|
||||||
|
true // Should throw
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true, // Enabled
|
||||||
|
false // Should not throw
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('tenant run helper can be used on central requests', function (bool $enabled, bool $shouldThrow) {
|
test('tenant run helper can be used on central requests', function (bool $enabled, bool $shouldThrow) {
|
||||||
|
|
@ -140,6 +146,12 @@ test('tenant run helper can be used on central requests', function (bool $enable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})->with([
|
})->with([
|
||||||
['enabled' => false, 'shouldThrow' => true],
|
[
|
||||||
['enabled' => true, 'shouldThrow' => false],
|
false, // Disabled
|
||||||
|
true // Should throw
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true, // Enabled
|
||||||
|
false // Should not throw
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -331,19 +331,19 @@ class CustomInitializeTenancyByPath extends InitializeTenancyByPath
|
||||||
|
|
||||||
dataset('path identification types', [
|
dataset('path identification types', [
|
||||||
'kernel identification' => [
|
'kernel identification' => [
|
||||||
'route_middleware' => ['universal'],
|
['universal'], // Route middleware
|
||||||
'global_middleware' => [InitializeTenancyByPath::class],
|
[InitializeTenancyByPath::class], // Global Global middleware
|
||||||
],
|
],
|
||||||
'route-level identification' => [
|
'route-level identification' => [
|
||||||
'route_middleware' => ['universal', InitializeTenancyByPath::class],
|
['universal', InitializeTenancyByPath::class], // Route middleware
|
||||||
'global_middleware' => [],
|
[], // Global middleware
|
||||||
],
|
],
|
||||||
'kernel identification + defaulting to universal routes' => [
|
'kernel identification + defaulting to universal routes' => [
|
||||||
'route_middleware' => [],
|
[], // Route middleware
|
||||||
'global_middleware' => ['universal', InitializeTenancyByPath::class],
|
['universal', InitializeTenancyByPath::class], // Global middleware
|
||||||
],
|
],
|
||||||
'route-level identification + defaulting to universal routes' => [
|
'route-level identification + defaulting to universal routes' => [
|
||||||
'route_middleware' => [InitializeTenancyByPath::class],
|
[InitializeTenancyByPath::class], // Route middleware
|
||||||
'global_middleware' => ['universal'],
|
['universal'], // Global middleware
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Exceptions\NotASubdomainException;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\Models\Post;
|
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\Models\Post;
|
||||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||||
|
|
@ -22,6 +23,7 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader;
|
||||||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithMiddleware;
|
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithMiddleware;
|
||||||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithRouteMiddleware;
|
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithRouteMiddleware;
|
||||||
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
config()->set([
|
config()->set([
|
||||||
|
|
@ -35,6 +37,30 @@ beforeEach(function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dataset('identification_middleware', [
|
||||||
|
InitializeTenancyByDomain::class,
|
||||||
|
InitializeTenancyBySubdomain::class,
|
||||||
|
InitializeTenancyByDomainOrSubdomain::class,
|
||||||
|
InitializeTenancyByPath::class,
|
||||||
|
InitializeTenancyByRequestData::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
dataset('domain_identification_middleware', [
|
||||||
|
InitializeTenancyByDomain::class,
|
||||||
|
InitializeTenancyBySubdomain::class,
|
||||||
|
InitializeTenancyByDomainOrSubdomain::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
dataset('default_route_modes', [
|
||||||
|
RouteMode::TENANT,
|
||||||
|
RouteMode::CENTRAL,
|
||||||
|
]);
|
||||||
|
|
||||||
|
dataset('global_and_route_level_identification', [
|
||||||
|
false, // Route-level identification
|
||||||
|
true, // Global identification
|
||||||
|
]);
|
||||||
|
|
||||||
test('early identification works with path identification', function (bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
test('early identification works with path identification', function (bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
||||||
$identificationMiddleware = InitializeTenancyByPath::class;
|
$identificationMiddleware = InitializeTenancyByPath::class;
|
||||||
|
|
||||||
|
|
@ -117,14 +143,8 @@ test('early identification works with path identification', function (bool $useK
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertContent($tenantPost->title . '-' . $tenantComment->comment);
|
->assertContent($tenantPost->title . '-' . $tenantComment->comment);
|
||||||
assertTenancyInitializedInEarlyIdentificationRequest();
|
assertTenancyInitializedInEarlyIdentificationRequest();
|
||||||
})->with([
|
})->with('global_and_route_level_identification')
|
||||||
'route-level identification' => false,
|
->with('default_route_modes');
|
||||||
'kernel identification' => true,
|
|
||||||
// Creates a matrix (multiple with())
|
|
||||||
])->with([
|
|
||||||
'default to tenant routes' => RouteMode::TENANT,
|
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
|
||||||
]);
|
|
||||||
|
|
||||||
test('early identification works with request data identification', function (string $type, bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
test('early identification works with request data identification', function (string $type, bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
||||||
$identificationMiddleware = InitializeTenancyByRequestData::class;
|
$identificationMiddleware = InitializeTenancyByRequestData::class;
|
||||||
|
|
@ -168,14 +188,8 @@ test('early identification works with request data identification', function (st
|
||||||
'using request header parameter' => 'header',
|
'using request header parameter' => 'header',
|
||||||
'using request query parameter' => 'queryParameter',
|
'using request query parameter' => 'queryParameter',
|
||||||
'using request cookie parameter' => 'cookie',
|
'using request cookie parameter' => 'cookie',
|
||||||
// Creates a matrix (multiple with())
|
])->with('global_and_route_level_identification')->with('default_route_modes');
|
||||||
])->with([
|
|
||||||
'route-level identification' => false,
|
|
||||||
'kernel identification' => true,
|
|
||||||
])->with([
|
|
||||||
'default to tenant routes' => RouteMode::TENANT,
|
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
|
||||||
]);
|
|
||||||
test('early identification works with origin identification', function (bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
test('early identification works with origin identification', function (bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
||||||
$identificationMiddleware = InitializeTenancyByOriginHeader::class;
|
$identificationMiddleware = InitializeTenancyByOriginHeader::class;
|
||||||
|
|
||||||
|
|
@ -209,85 +223,111 @@ test('early identification works with origin identification', function (bool $us
|
||||||
$response = pest()->post('/tenant-route', headers: ['Origin' => 'foo.localhost']);
|
$response = pest()->post('/tenant-route', headers: ['Origin' => 'foo.localhost']);
|
||||||
|
|
||||||
$response->assertOk()->assertSee('token:' . $tenantKey);
|
$response->assertOk()->assertSee('token:' . $tenantKey);
|
||||||
})->with([
|
})->with('global_and_route_level_identification')->with('default_route_modes');
|
||||||
'route-level identification' => false,
|
|
||||||
'kernel identification' => true,
|
|
||||||
])->with([
|
|
||||||
'default to tenant routes' => RouteMode::TENANT,
|
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
|
||||||
]);
|
|
||||||
|
|
||||||
test('early identification works with domain identification', function (string $middleware, string $domain, bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
|
||||||
config(['tenancy.default_route_mode' => $defaultRouteMode]);
|
|
||||||
|
|
||||||
if ($useKernelIdentification) {
|
|
||||||
$controller = ControllerWithMiddleware::class;
|
|
||||||
app(Kernel::class)->pushMiddleware($middleware);
|
|
||||||
app(Kernel::class)->pushMiddleware(PreventAccessFromUnwantedDomains::class);
|
|
||||||
} else {
|
|
||||||
$controller = ControllerWithRouteMiddleware::class;
|
|
||||||
RouteFacade::middlewareGroup('tenant', [$middleware, PreventAccessFromUnwantedDomains::class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tenant route
|
|
||||||
$tenantRoute = RouteFacade::get('/tenant-route', [$controller, 'index']);
|
|
||||||
|
|
||||||
// Central route
|
|
||||||
$centralRoute = RouteFacade::get('/central-route', function () {
|
|
||||||
return 'central route';
|
|
||||||
});
|
|
||||||
|
|
||||||
$defaultToTenantRoutes = $defaultRouteMode === RouteMode::TENANT;
|
|
||||||
|
|
||||||
// Test defaulting to route mode (central/tenant context)
|
|
||||||
if ($useKernelIdentification) {
|
|
||||||
$routeThatShouldReceiveMiddleware = $defaultToTenantRoutes ? $centralRoute : $tenantRoute;
|
|
||||||
$routeThatShouldReceiveMiddleware->middleware($defaultToTenantRoutes ? 'central' : 'tenant');
|
|
||||||
} elseif (! $defaultToTenantRoutes) {
|
|
||||||
$tenantRoute->middleware('tenant');
|
|
||||||
} else {
|
|
||||||
// Route-level identification + defaulting to tenant routes
|
|
||||||
// We still have to apply the tenant middleware to the routes, so they aren't really tenant by default
|
|
||||||
$tenantRoute->middleware([$middleware, PreventAccessFromUnwantedDomains::class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
test('early identification works with domain identification', function (string $middleware, bool $useKernelIdentification) {
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
$tenant->domains()->create([
|
// Create domain and a subdomain for the tenant
|
||||||
'domain' => $domain,
|
$tenant->createDomain('foo.test');
|
||||||
]);
|
$tenant->createDomain('foo');
|
||||||
|
|
||||||
if ($domain === 'foo') {
|
if ($useKernelIdentification) {
|
||||||
$domain = 'foo.localhost';
|
app(Kernel::class)->pushMiddleware($middleware);
|
||||||
|
app(Kernel::class)->pushMiddleware(PreventAccessFromUnwantedDomains::class);
|
||||||
|
|
||||||
|
RouteFacade::get('/tenant-route', [ControllerWithMiddleware::class, 'index'])->middleware('tenant');
|
||||||
|
} else {
|
||||||
|
RouteFacade::get('/tenant-route', [ControllerWithRouteMiddleware::class, 'index'])->middleware([$middleware, PreventAccessFromUnwantedDomains::class]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pest()->get('http://localhost/central-route')->assertOk()->assertContent('central route'); // Central route is accessible
|
$domainUrl = 'http://foo.test/tenant-route';
|
||||||
|
$subdomainUrl = str(config('app.url'))->replaceFirst('://', "://foo.")->toString() . '/tenant-route';
|
||||||
|
|
||||||
$response = pest()->get("http://{$domain}/tenant-route");
|
$tenantUrls = Arr::wrap(match ($middleware) {
|
||||||
|
InitializeTenancyByDomain::class => $domainUrl,
|
||||||
|
InitializeTenancyBySubdomain::class => $subdomainUrl,
|
||||||
|
InitializeTenancyByDomainOrSubdomain::class => [$domainUrl, $subdomainUrl], // Domain or subdomain -- try visiting both
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach ($tenantUrls as $url) {
|
||||||
|
$response = pest()->get($url);
|
||||||
|
|
||||||
if ($defaultToTenantRoutes === $useKernelIdentification || $useKernelIdentification) {
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
|
|
||||||
assertTenancyInitializedInEarlyIdentificationRequest();
|
assertTenancyInitializedInEarlyIdentificationRequest();
|
||||||
} elseif (! $defaultToTenantRoutes) {
|
|
||||||
$response->assertNotFound();
|
|
||||||
assertTenancyInitializedInEarlyIdentificationRequest(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expect tenancy is initialized (or not) for the right tenant at the tenant route
|
// Expect tenancy is initialized (or not) for the right tenant at the tenant route
|
||||||
expect($response->getContent())->toBe('token:' . tenant()->getTenantKey());
|
expect($response->getContent())->toBe('token:' . tenant()->getTenantKey());
|
||||||
})->with([
|
}
|
||||||
'domain identification' => ['middleware' => InitializeTenancyByDomain::class, 'domain' => 'foo.test'],
|
})->with('domain_identification_middleware')
|
||||||
'subdomain identification' => ['middleware' => InitializeTenancyBySubdomain::class, 'domain' => 'foo'],
|
->with('global_and_route_level_identification');
|
||||||
'domainOrSubdomain identification using domain' => ['middleware' => InitializeTenancyByDomainOrSubdomain::class, 'domain' => 'foo.test'],
|
|
||||||
'domainOrSubdomain identification using subdomain' => ['middleware' => InitializeTenancyByDomainOrSubdomain::class, 'domain' => 'foo'],
|
test('using different default route modes works with global domain identification', function(string $middleware, RouteMode $defaultRouteMode) {
|
||||||
// Creates a matrix (multiple with())
|
config(['tenancy.default_route_mode' => $defaultRouteMode]);
|
||||||
])->with([
|
|
||||||
'route-level identification' => false,
|
$tenant = Tenant::create();
|
||||||
'kernel identification' => true,
|
|
||||||
])->with([
|
// Create domain and a subdomain for the tenant
|
||||||
'default to tenant routes' => RouteMode::TENANT,
|
$tenant->createDomain('foo.test');
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
$tenant->createDomain('foo');
|
||||||
]);
|
|
||||||
|
// Create central and tenant routes, without any identification middleware or tags
|
||||||
|
$centralRoute = RouteFacade::get('/central-route', fn () => 'central route');
|
||||||
|
RouteFacade::get('/tenant-route', [ControllerWithMiddleware::class, 'index']);
|
||||||
|
|
||||||
|
// Add the domain identification middleware to the kernel MW
|
||||||
|
app(Kernel::class)->pushMiddleware($middleware);
|
||||||
|
app(Kernel::class)->pushMiddleware(PreventAccessFromUnwantedDomains::class);
|
||||||
|
|
||||||
|
$domainUrl = 'http://foo.test/tenant-route';
|
||||||
|
$subdomainUrl = str(config('app.url'))->replaceFirst('://', "://foo.")->toString() . '/tenant-route';
|
||||||
|
|
||||||
|
$tenantUrls = Arr::wrap(match ($middleware) {
|
||||||
|
InitializeTenancyByDomain::class => $domainUrl,
|
||||||
|
InitializeTenancyBySubdomain::class => $subdomainUrl,
|
||||||
|
InitializeTenancyByDomainOrSubdomain::class => [$domainUrl, $subdomainUrl], // Domain or subdomain -- try visiting both
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($defaultRouteMode === RouteMode::TENANT) {
|
||||||
|
// When defaulting to tenant routes and using kernel identification,
|
||||||
|
// the central route should not be accessible if not flagged as central.
|
||||||
|
|
||||||
|
// Since central-route is considered tenant by default, and there's tenant ID MW,
|
||||||
|
// expect that an exception specific to that ID MW to be thrown when trying to access the route.
|
||||||
|
$exception = match ($middleware) {
|
||||||
|
InitializeTenancyByDomain::class => TenantCouldNotBeIdentifiedOnDomainException::class,
|
||||||
|
InitializeTenancyBySubdomain::class => NotASubdomainException::class,
|
||||||
|
InitializeTenancyByDomainOrSubdomain::class => NotASubdomainException::class,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(fn () => $this->withoutExceptionHandling()->get('http://localhost/central-route'))->toThrow($exception);
|
||||||
|
|
||||||
|
// Flagging the central route as central should make it accessible,
|
||||||
|
// even if the default route mode is tenant
|
||||||
|
$centralRoute = $centralRoute->middleware('central');
|
||||||
|
|
||||||
|
pest()->get('http://localhost/central-route')->assertOk()->assertSee('central route');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($tenantUrls as $url) {
|
||||||
|
$response = pest()->get($url);
|
||||||
|
|
||||||
|
// If the default route mode is tenant, only the tenant route should be accessible
|
||||||
|
// and tenancy should be initialized using early identification for the correct tenant
|
||||||
|
if ($defaultRouteMode === RouteMode::TENANT) {
|
||||||
|
$response->assertOk();
|
||||||
|
|
||||||
|
assertTenancyInitializedInEarlyIdentificationRequest();
|
||||||
|
|
||||||
|
// Expect tenancy is initialized for the right tenant at the tenant route
|
||||||
|
expect($response->getContent())->toBe('token:' . tenant()->getTenantKey());
|
||||||
|
} else {
|
||||||
|
$response->assertNotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})->with('domain_identification_middleware')
|
||||||
|
->with('default_route_modes');
|
||||||
|
|
||||||
test('the tenant parameter is only removed from tenant routes when using path identification', function (bool $kernelIdentification, bool $pathIdentification) {
|
test('the tenant parameter is only removed from tenant routes when using path identification', function (bool $kernelIdentification, bool $pathIdentification) {
|
||||||
if ($kernelIdentification) {
|
if ($kernelIdentification) {
|
||||||
|
|
@ -373,62 +413,97 @@ test('the tenant parameter is only removed from tenant routes when using path id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})->with([
|
})->with([
|
||||||
'kernel path identification' => ['kernelIdentification' => true, 'pathIdentification' => true],
|
'kernel path identification' => [
|
||||||
'route-level path identification' => ['kernelIdentification' => false, 'pathIdentification' => true],
|
true, // Kernel identification
|
||||||
'kernel domain identification' => ['kernelIdentification' => true, 'pathIdentification' => false],
|
true // Path identification
|
||||||
'route-level domain identification' => ['kernelIdentification' => false, 'pathIdentification' => false],
|
],
|
||||||
|
'route-level path identification' => [
|
||||||
|
false, // Kernel identification
|
||||||
|
true // Path identification
|
||||||
|
],
|
||||||
|
'kernel domain identification' => [
|
||||||
|
true,
|
||||||
|
false // Path identification
|
||||||
|
],
|
||||||
|
'route-level domain identification' => [
|
||||||
|
false, // Kernel identification
|
||||||
|
false // Path identification
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('route level identification is prioritized over kernel identification', function (
|
test('route level domain identification is prioritized over kernel identification', function (
|
||||||
string|array $kernelIdentificationMiddleware,
|
string $kernelIdentificationMiddleware,
|
||||||
string|array $routeIdentificationMiddleware,
|
string $routeIdentificationMiddleware,
|
||||||
string $routeUri,
|
|
||||||
string $domainToVisit,
|
|
||||||
string|null $domain = null,
|
|
||||||
RouteMode $defaultRouteMode,
|
RouteMode $defaultRouteMode,
|
||||||
) {
|
) {
|
||||||
$tenant = Tenant::create();
|
$tenant = Tenant::create();
|
||||||
$domainToVisit = str_replace('{tenantKey}', $tenant->getTenantKey(), $domainToVisit);
|
|
||||||
|
|
||||||
config(['tenancy.default_route_mode' => $defaultRouteMode]);
|
config(['tenancy.default_route_mode' => $defaultRouteMode]);
|
||||||
|
|
||||||
if ($domain) {
|
// Subdomain
|
||||||
$tenant->domains()->create(['domain' => str_replace('{tenantKey}', $tenant->getTenantKey(), $domain)]);
|
$tenant->createDomain($subdomain = $tenant->getTenantKey());
|
||||||
|
$tenant->createDomain($domain = $subdomain . '.test');
|
||||||
|
|
||||||
|
app(Kernel::class)->pushMiddleware(PreventAccessFromUnwantedDomains::class)->pushMiddleware($kernelIdentificationMiddleware);
|
||||||
|
|
||||||
|
// We're testing *non-early* route-level identification so that we can assert that early kernel identification got skipped
|
||||||
|
// Also, ignore the defaulting when the identification MW is applied directly on the route
|
||||||
|
// Because the route is automatically considered tenant if it has identification middleware (unless it also has the 'universal' middleware)
|
||||||
|
RouteFacade::get('tenant-route', [ControllerWithMiddleware::class, 'index'])
|
||||||
|
->middleware([PreventAccessFromUnwantedDomains::class, $routeIdentificationMiddleware]);
|
||||||
|
|
||||||
|
$domainIdUrl = "http://{$domain}/tenant-route";
|
||||||
|
$subdomainIdUrl = str(config('app.url'))->replaceFirst('://', "://{$subdomain}.")->append("/tenant-route")->toString();
|
||||||
|
|
||||||
|
$urlsToVisit = Arr::wrap(match ($routeIdentificationMiddleware) {
|
||||||
|
InitializeTenancyByDomain::class => $domainIdUrl,
|
||||||
|
InitializeTenancyBySubdomain::class => $subdomainIdUrl,
|
||||||
|
InitializeTenancyByDomainOrSubdomain::class => [$domainIdUrl, $subdomainIdUrl], // Domain or subdomain -- try visiting both
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach ($urlsToVisit as $url) {
|
||||||
|
pest()->get($url)->assertOk();
|
||||||
|
|
||||||
|
// Kernel (early) identification skipped
|
||||||
|
expect(app()->make('controllerRunsInTenantContext'))->toBeFalse();
|
||||||
|
}
|
||||||
|
})->with('identification_middleware')
|
||||||
|
->with('domain_identification_middleware')
|
||||||
|
->with('default_route_modes');
|
||||||
|
|
||||||
|
test('route level path and request data identification is prioritized over kernel identification', function (
|
||||||
|
string $kernelIdentificationMiddleware,
|
||||||
|
string $routeIdentificationMiddleware,
|
||||||
|
RouteMode $defaultRouteMode,
|
||||||
|
) {
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
config(['tenancy.default_route_mode' => $defaultRouteMode]);
|
||||||
|
|
||||||
|
if (in_array($kernelIdentificationMiddleware, config('tenancy.identification.domain_identification_middleware'))) {
|
||||||
|
// If a domain identification middleware is used, the prevent access MW is used too
|
||||||
|
app(Kernel::class)->pushMiddleware(PreventAccessFromUnwantedDomains::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Arr::wrap($kernelIdentificationMiddleware) as $identificationMiddleware) {
|
app(Kernel::class)->pushMiddleware($kernelIdentificationMiddleware);
|
||||||
app(Kernel::class)->pushMiddleware($identificationMiddleware);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're testing *non-early* route-level identification so that we can assert that early kernel identification got skipped
|
// We're testing *non-early* route-level identification so that we can assert that early kernel identification got skipped
|
||||||
// Also, ignore the defaulting when the identification MW is applied directly on the route
|
// Also, ignore the defaulting when the identification MW is applied directly on the route
|
||||||
// The route is automatically considered tenant if it has identification middleware (unless it also has the 'universal' middleware)
|
// The route is automatically considered tenant if it has identification middleware (unless it also has the 'universal' middleware)
|
||||||
RouteFacade::get($routeUri, [ControllerWithMiddleware::class, 'index'])->middleware($routeIdentificationMiddleware);
|
$route = RouteFacade::get('/tenant-route', [ControllerWithMiddleware::class, 'index'])->middleware($routeIdentificationMiddleware)->name('tenant-route');
|
||||||
|
|
||||||
pest()->get($domainToVisit)->assertOk();
|
if ($routeIdentificationMiddleware === InitializeTenancyByPath::class) {
|
||||||
|
$route = $route->prefix('{tenant}');
|
||||||
|
}
|
||||||
|
|
||||||
|
pest()->get(route('tenant-route', ['tenant' => $tenant->getTenantKey()]))->assertOk();
|
||||||
|
|
||||||
// Kernel (early) identification skipped
|
// Kernel (early) identification skipped
|
||||||
expect(app()->make('controllerRunsInTenantContext'))->toBeFalse();
|
expect(app()->make('controllerRunsInTenantContext'))->toBeFalse();
|
||||||
})->with([
|
})->with('identification_middleware')
|
||||||
'kernel request data identification mw' => ['kernelMiddleware' => InitializeTenancyByRequestData::class],
|
->with([
|
||||||
'kernel path identification mw' => ['kernelMiddleware' => InitializeTenancyByPath::class],
|
'route level request data identification' => InitializeTenancyByRequestData::class,
|
||||||
'kernel domain identification mw' => ['kernelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class]],
|
'route level path identification' => InitializeTenancyByPath::class,
|
||||||
'kernel subdomain identification mw' => ['kernelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class]],
|
])->with('default_route_modes');
|
||||||
'kernel domainOrSubdomain identification mw using domain' => ['kernelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomainOrSubdomain::class]],
|
|
||||||
'kernel domainOrSubdomain identification mw using subdomain' => ['kernelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomainOrSubdomain::class]],
|
|
||||||
// Creates a matrix (multiple with())
|
|
||||||
])->with([
|
|
||||||
'route level request data identification mw' => ['routeLevelMiddleware' => InitializeTenancyByRequestData::class, 'routeUri' => '/tenant-route', 'domainToVisit' => 'http://localhost/tenant-route?tenant={tenantKey}', 'domain' => null],
|
|
||||||
'route level path identification mw' => ['routeLevelMiddleware' => InitializeTenancyByPath::class, 'routeUri' => '/{tenant}/tenant-route', 'domainToVisit' => 'http://localhost/{tenantKey}/tenant-route', 'domain' => null],
|
|
||||||
'route level domain identification mw' => ['routeLevelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class], 'routeUri' => '/tenant-route', 'domainToVisit' => 'http://{tenantKey}.test/tenant-route', 'domain' => '{tenantKey}.test'],
|
|
||||||
'route level subdomain identification mw' => ['routeLevelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class], 'routeUri' => '/tenant-route', 'domainToVisit' => 'http://{tenantKey}.localhost/tenant-route', 'domain' => '{tenantKey}'],
|
|
||||||
'route level domainOrSubdomain identification mw using domain' => ['routeLevelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomainOrSubdomain::class], 'routeUri' => '/tenant-route', 'domainToVisit' => 'http://{tenantKey}.test/tenant-route', 'domain' => '{tenantKey}.test'],
|
|
||||||
'route level domainOrSubdomain identification mw using subdomain' => ['routeLevelMiddleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomainOrSubdomain::class], 'routeUri' => '/tenant-route', 'domainToVisit' => 'http://{tenantKey}.localhost/tenant-route', 'domain' => '{tenantKey}'],
|
|
||||||
])
|
|
||||||
->with([
|
|
||||||
'default to tenant routes' => RouteMode::TENANT,
|
|
||||||
'default to central routes' => RouteMode::CENTRAL,
|
|
||||||
]);
|
|
||||||
|
|
||||||
function assertTenancyInitializedInEarlyIdentificationRequest(bool $expect = true): void
|
function assertTenancyInitializedInEarlyIdentificationRequest(bool $expect = true): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,8 @@ test('kernel PreventAccessFromUnwantedDomains does not get skipped when route le
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('placement of domain identification and access prevention middleware can get mixed', function (
|
test('placement of domain identification and access prevention middleware can get mixed', function (
|
||||||
array $globalMiddleware,
|
|
||||||
array $routeMiddleware,
|
array $routeMiddleware,
|
||||||
|
array $globalMiddleware,
|
||||||
array $centralRouteMiddleware
|
array $centralRouteMiddleware
|
||||||
) {
|
) {
|
||||||
config([
|
config([
|
||||||
|
|
@ -214,16 +214,16 @@ test('placement of domain identification and access prevention middleware can ge
|
||||||
expect(tenancy()->initialized)->toBeFalse();
|
expect(tenancy()->initialized)->toBeFalse();
|
||||||
})->with([
|
})->with([
|
||||||
'route-level identification, kernel access prevention' => [
|
'route-level identification, kernel access prevention' => [
|
||||||
'global_middleware' => [PreventAccessFromUnwantedDomains::class],
|
[InitializeTenancyBySubdomain::class], // Route middleware
|
||||||
'route_middleware' => [InitializeTenancyBySubdomain::class],
|
[PreventAccessFromUnwantedDomains::class], // Global middleware
|
||||||
],
|
],
|
||||||
'kernel identification, kernel access prevention' => [
|
'kernel identification, kernel access prevention' => [
|
||||||
'global_middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
|
[], // Route middleware
|
||||||
'route_middleware' => [],
|
[PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class], // Global middleware
|
||||||
],
|
],
|
||||||
'route-level identification, route-level access prevention' => [
|
'route-level identification, route-level access prevention' => [
|
||||||
'global_middleware' => [],
|
[PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class], // Route middleware
|
||||||
'route_middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
|
[], // Global middleware
|
||||||
],
|
],
|
||||||
// Creates a matrix (multiple with())
|
// Creates a matrix (multiple with())
|
||||||
])->with([
|
])->with([
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,11 @@ test('single domain tenant can be identified by domain or subdomain', function (
|
||||||
expect(tenant('id'))->toBe($tenant->id);
|
expect(tenant('id'))->toBe($tenant->id);
|
||||||
})->with([
|
})->with([
|
||||||
[
|
[
|
||||||
'domain' => 'acme.localhost',
|
'acme.localhost', // Domain
|
||||||
'identification middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
|
[PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class], // Identification middleware
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'subdomain' => 'acme',
|
'acme', // Subdomain
|
||||||
'identification middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
|
[PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class], // Identification middleware
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -327,20 +327,20 @@ foreach ([
|
||||||
] as $datasetName => $middleware) {
|
] as $datasetName => $middleware) {
|
||||||
dataset($datasetName, [
|
dataset($datasetName, [
|
||||||
'kernel identification' => [
|
'kernel identification' => [
|
||||||
'route_middleware' => ['universal'],
|
['universal'], // Route middleware
|
||||||
'global_middleware' => $middleware,
|
$middleware, // Global middleware
|
||||||
],
|
],
|
||||||
'route-level identification' => [
|
'route-level identification' => [
|
||||||
'route_middleware' => ['universal', ...$middleware],
|
['universal', ...$middleware], // Route middleware
|
||||||
'global_middleware' => [],
|
[], // Global middleware
|
||||||
],
|
],
|
||||||
'kernel identification + defaulting to universal routes' => [
|
'kernel identification + defaulting to universal routes' => [
|
||||||
'route_middleware' => [],
|
[], // Route middleware
|
||||||
'global_middleware' => ['universal', ...$middleware],
|
['universal', ...$middleware], // Global middleware
|
||||||
],
|
],
|
||||||
'route-level identification + defaulting to universal routes' => [
|
'route-level identification + defaulting to universal routes' => [
|
||||||
'route_middleware' => $middleware,
|
$middleware, // Route middleware
|
||||||
'global_middleware' => ['universal'],
|
['universal'], // Global middleware
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue