mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 14:34:04 +00:00
Single-domain tenants (#16)
* Add SingleDomainTenant * Add logic for single domain tenants * Test that the single domain approach works (wip) * Fix code style (php-cs-fixer) * Simplify SubdomainTest tests * Add single domain tenant conditions to DomainTenantResolver * Test single domain tenants in resolver test * Fix test name typo * Improve runUsingBothDomainApproaches() * Delete extra tenancy()->end() * Test early identification with both domain approaches * Test that things work with both domain approaches in the rest of the tests * Fix falsely passing test * Fix PHPStan errors * Change SingleDomainTenant to a contract, add SingleDomainTenant test model * Fix TenantList domainsCLI() * Improve setCurrentDomain() check * Fix code style (php-cs-fixer) * Add annotation * Revert getCustomColumns() change * Add comments * Use the domain returned by the closure in runUsingBoth..() * Delete `migrate` from test * Improve test names * Use variable instead of repeating the same string multiple times * Update comment * Add comment * Clean up PreventAccess test * Don't assign domain to a single-use variable * Update comments * Uncomment datasets * Add todo * Fix user impersonation test * Don't specify tenant key when creating tenant in runUsingBoth..() * Improve universal route test * Improve `runUsingBothDomainApproaches()` * Add tests specific to single domain tenants * Get rid of the runUsingBothDomainApproaches method * Add test file specific for the single domain tenant feature * Rename test * Make getCustomColumns() function static * Positiopn datasets differently * Fix early id test * Add prevent MW to route MW in test * Fix single domain tenant tests * Delete SingleDomainTenantTest (CI testing) * Add the test file back * TUrn APP_DEBUG on temporarily * Turn debug off * Try creating tenant with non-unique domain (CI testing) * dd duplicate tenant records * Revert testing change * Make SingleDomainTenant not extend base tenant (VirtualColumn issues) * Fix early id test * add todo * Use dev-master stancl/virtualcolumn * Make SingleDomainTenant extend the tenant base model * remove todo * Clean up EarlyIdentificationTest changes * Finish test file cleanup * Fix test * improve test --------- Co-authored-by: PHP CS Fixer <phpcsfixer@example.com> Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
parent
c34952f328
commit
e25e7b7961
10 changed files with 243 additions and 14 deletions
|
|
@ -9,7 +9,7 @@ beforeEach(function () {
|
|||
config(['tenancy.models.tenant' => DatabaseAndDomainTenant::class]);
|
||||
});
|
||||
|
||||
test('job delete domains successfully', function () {
|
||||
test('job deletes domains successfully', function () {
|
||||
$tenant = DatabaseAndDomainTenant::create();
|
||||
|
||||
$tenant->domains()->create([
|
||||
|
|
|
|||
|
|
@ -181,7 +181,6 @@ test('early identification works with request data identification', function (st
|
|||
]);
|
||||
|
||||
test('early identification works with domain identification', function (string $middleware, string $domain, bool $useKernelIdentification, RouteMode $defaultRouteMode) {
|
||||
config(['tenancy.tenant_model' => Tenant::class]);
|
||||
config(['tenancy.default_route_mode' => $defaultRouteMode]);
|
||||
|
||||
if ($useKernelIdentification) {
|
||||
|
|
@ -209,6 +208,10 @@ test('early identification works with domain identification', function (string $
|
|||
$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]);
|
||||
}
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
|
@ -234,7 +237,7 @@ test('early identification works with domain identification', function (string $
|
|||
}
|
||||
|
||||
// Expect tenancy is initialized (or not) for the right tenant at the tenant route
|
||||
expect($response->getContent())->toBe('token:' . (tenant()?->getTenantKey() ?? 'central'));
|
||||
expect($response->getContent())->toBe('token:' . tenant()->getTenantKey());
|
||||
})->with([
|
||||
'domain identification' => ['middleware' => InitializeTenancyByDomain::class, 'domain' => 'foo.test'],
|
||||
'subdomain identification' => ['middleware' => InitializeTenancyBySubdomain::class, 'domain' => 'foo'],
|
||||
|
|
|
|||
29
tests/Etc/2023_08_08_000001_add_domain_column.php
Normal file
29
tests/Etc/2023_08_08_000001_add_domain_column.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddDomainColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('domain')->unique()->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropColumn('domain');
|
||||
});
|
||||
}
|
||||
}
|
||||
29
tests/Etc/SingleDomainTenant.php
Normal file
29
tests/Etc/SingleDomainTenant.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc;
|
||||
|
||||
use Stancl\Tenancy\Contracts;
|
||||
use Stancl\Tenancy\Events;
|
||||
use Stancl\Tenancy\Database\Concerns;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Stancl\VirtualColumn\VirtualColumn;
|
||||
use Stancl\Tenancy\Database\TenantCollection;
|
||||
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
|
||||
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
|
||||
use Stancl\Tenancy\Exceptions\TenancyNotInitializedException;
|
||||
use Stancl\Tenancy\Contracts\SingleDomainTenant as SingleDomainTenantContract;
|
||||
|
||||
class SingleDomainTenant extends BaseTenant implements SingleDomainTenantContract, TenantWithDatabase
|
||||
{
|
||||
use Concerns\ConvertsDomainsToLowercase, Concerns\HasDatabase;
|
||||
|
||||
public function getCustomColumns(): array
|
||||
{
|
||||
return [
|
||||
'id',
|
||||
'domain',
|
||||
];
|
||||
}
|
||||
}
|
||||
133
tests/SingleDomainTenantTest.php
Normal file
133
tests/SingleDomainTenantTest.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Tests\Etc\SingleDomainTenant;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Illuminate\Database\UniqueConstraintViolationException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
|
||||
beforeEach(function () {
|
||||
config(['tenancy.models.tenant' => SingleDomainTenant::class]);
|
||||
|
||||
pest()->artisan('migrate', [
|
||||
'--path' => __DIR__ . '/Etc/2023_08_08_000001_add_domain_column.php',
|
||||
'--realpath' => true,
|
||||
])->assertExitCode(0);
|
||||
});
|
||||
|
||||
test('tenant can be resolved by its domain using the cached resolver', function () {
|
||||
$tenant = SingleDomainTenant::create(['domain' => 'acme']);
|
||||
$tenant2 = SingleDomainTenant::create(['domain' => 'bar.domain.test']);
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve($tenant->domain)))->toBeTrue();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve($tenant2->domain)))->toBeFalse();
|
||||
|
||||
expect($tenant2->is(app(DomainTenantResolver::class)->resolve($tenant2->domain)))->toBeTrue();
|
||||
expect($tenant2->is(app(DomainTenantResolver::class)->resolve($tenant->domain)))->toBeFalse();
|
||||
});
|
||||
|
||||
test('cache is invalidated when single domain tenant is updated', function () {
|
||||
DB::enableQueryLog();
|
||||
|
||||
config([
|
||||
'tenancy.models.tenant' => SingleDomainTenant::class,
|
||||
'tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true
|
||||
]);
|
||||
|
||||
$tenant = SingleDomainTenant::create(['domain' => $subdomain = 'acme']);
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve($subdomain)))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve($subdomain)))->toBeTrue();
|
||||
expect(DB::getQueryLog())->toBeEmpty(); // empty
|
||||
|
||||
$tenant->update(['foo' => 'bar']);
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve($subdomain)))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
|
||||
});
|
||||
|
||||
test('cache is invalidated when a single domain tenants domain is updated', function () {
|
||||
DB::enableQueryLog();
|
||||
|
||||
config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]);
|
||||
|
||||
$tenant = SingleDomainTenant::create(['domain' => 'acme']);
|
||||
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
|
||||
pest()->assertEmpty(DB::getQueryLog()); // Empty – tenant retrieved from cache
|
||||
|
||||
$tenant->update(['domain' => 'bar']);
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect(fn () => $tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toThrow(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // resolving old subdomain (not in cache anymore)
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // resolving using new subdomain for the first time
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toBeTrue();
|
||||
pest()->assertEmpty(DB::getQueryLog()); // resolving using new subdomain for the second time
|
||||
|
||||
$tenant->update(['domain' => 'baz']);
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect(fn () => $tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toThrow(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // resolving using first old subdomain - no cache + failed
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect(fn () => $tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toThrow(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // resolving using second old subdomain - no cache + failed
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('baz')))->toBeTrue();
|
||||
pest()->assertNotEmpty(DB::getQueryLog()); // resolving using current subdomain for the first time
|
||||
|
||||
DB::flushQueryLog();
|
||||
expect($tenant->is(app(DomainTenantResolver::class)->resolve('baz')))->toBeTrue();
|
||||
pest()->assertEmpty(DB::getQueryLog()); // resolving using current subdomain for the second time
|
||||
});
|
||||
|
||||
test('tenant has to have a unique domain', function() {
|
||||
SingleDomainTenant::create(['domain' => 'bar']);
|
||||
|
||||
expect(fn () => SingleDomainTenant::create(['domain' => 'bar']))->toThrow(UniqueConstraintViolationException::class);
|
||||
});
|
||||
|
||||
test('single domain tenant can be identified by domain or subdomain', function (string $domain, array $identificationMiddleware) {
|
||||
$tenant = SingleDomainTenant::create(['domain' => $domain]);
|
||||
|
||||
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
||||
return "$a + $b";
|
||||
})->middleware($identificationMiddleware);
|
||||
|
||||
if ($domain === 'acme') {
|
||||
$domain .= '.localhost';
|
||||
}
|
||||
|
||||
pest()
|
||||
->get("http://{$domain}/foo/abc/xyz")
|
||||
->assertSee('abc + xyz');
|
||||
|
||||
expect(tenant('id'))->toBe($tenant->id);
|
||||
})->with([
|
||||
[
|
||||
'domain' => 'acme.localhost',
|
||||
'identification middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
|
||||
],
|
||||
[
|
||||
'subdomain' => 'acme',
|
||||
'identification middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
|
||||
],
|
||||
]);
|
||||
Loading…
Add table
Add a link
Reference in a new issue