mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 12:54:05 +00:00
Merge branch 'master' into fix-url-bootstrappers
This commit is contained in:
commit
58cdae908a
44 changed files with 584 additions and 260 deletions
|
|
@ -48,10 +48,6 @@ test('BroadcastChannelPrefixBootstrapper prefixes the channels events are broadc
|
|||
$table->timestamps();
|
||||
});
|
||||
|
||||
universal_channel('users.{userId}', function ($user, $userId) {
|
||||
return User::find($userId)->is($user);
|
||||
});
|
||||
|
||||
$broadcaster = app(BroadcastManager::class)->driver();
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
|
|
|||
|
|
@ -136,14 +136,18 @@ test('broadcasting channel helpers register channels correctly', function() {
|
|||
|
||||
// Tenant channel registered – its name is correctly prefixed ("{tenant}.user.{userId}")
|
||||
$tenantChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === "{tenant}.$channelName");
|
||||
expect($tenantChannelClosure)
|
||||
->not()->toBeNull() // Channel registered
|
||||
->not()->toBe($centralChannelClosure); // The tenant channel closure is different – after the auth user, it accepts the tenant ID
|
||||
expect($tenantChannelClosure)->toBe($centralChannelClosure);
|
||||
|
||||
// The tenant channels are prefixed with '{tenant}.'
|
||||
// They accept the tenant key, but their closures only run in tenant context when tenancy is initialized
|
||||
// The regular channels don't accept the tenant key, but they also respect the current context
|
||||
// The tenant key is used solely for the name prefixing – the closures can still run in the central context
|
||||
tenant_channel($channelName, $tenantChannelClosure = function ($user, $tenant, $userName) {
|
||||
return User::firstWhere('name', $userName)?->is($user) ?? false;
|
||||
});
|
||||
|
||||
expect($tenantChannelClosure)->not()->toBe($centralChannelClosure);
|
||||
|
||||
expect($tenantChannelClosure($centralUser, $tenant->getTenantKey(), $centralUser->name))->toBeTrue();
|
||||
expect($tenantChannelClosure($centralUser, $tenant->getTenantKey(), $tenantUser->name))->toBeFalse();
|
||||
|
||||
|
|
@ -160,25 +164,6 @@ test('broadcasting channel helpers register channels correctly', function() {
|
|||
|
||||
expect($getChannels())->toBeEmpty();
|
||||
|
||||
// universal_channel helper registers both the unprefixed and the prefixed broadcasting channel correctly
|
||||
// Using the tenant_channel helper + basic channel registration (Broadcast::channel())
|
||||
universal_channel($channelName, $channelClosure);
|
||||
|
||||
// Regular channel registered correctly
|
||||
$centralChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === $channelName);
|
||||
expect($centralChannelClosure)->not()->toBeNull();
|
||||
|
||||
// Tenant channel registered correctly
|
||||
$tenantChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === "{tenant}.$channelName");
|
||||
expect($tenantChannelClosure)
|
||||
->not()->toBeNull() // Channel registered
|
||||
->not()->toBe($centralChannelClosure); // The tenant channel callback is different – after the auth user, it accepts the tenant ID
|
||||
|
||||
$broadcastManager->purge($driver);
|
||||
$broadcastManager->extend($driver, fn () => new NullBroadcaster);
|
||||
|
||||
expect($getChannels())->toBeEmpty();
|
||||
|
||||
// Global channel helper prefixes the channel name with 'global__'
|
||||
global_channel($channelName, $channelClosure);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,62 +6,56 @@ use Illuminate\Support\Facades\Route;
|
|||
use Stancl\Tenancy\Database\Concerns\HasDomains;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||
use Stancl\Tenancy\Database\Models;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
beforeEach(function () {
|
||||
Route::group([
|
||||
'middleware' => InitializeTenancyByDomainOrSubdomain::class,
|
||||
], function () {
|
||||
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
||||
return "$a + $b";
|
||||
Route::get('/test', function () {
|
||||
return tenant('id');
|
||||
});
|
||||
});
|
||||
|
||||
config(['tenancy.models.tenant' => CombinedTenant::class]);
|
||||
});
|
||||
|
||||
test('tenant can be identified by subdomain', function () {
|
||||
config(['tenancy.identification.central_domains' => ['localhost']]);
|
||||
|
||||
$tenant = CombinedTenant::create([
|
||||
'id' => 'acme',
|
||||
]);
|
||||
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'foo',
|
||||
]);
|
||||
$tenant = Tenant::create(['id' => 'acme']);
|
||||
$tenant->domains()->create(['domain' => 'foo']);
|
||||
|
||||
expect(tenancy()->initialized)->toBeFalse();
|
||||
|
||||
pest()
|
||||
->get('http://foo.localhost/foo/abc/xyz')
|
||||
->assertSee('abc + xyz');
|
||||
|
||||
expect(tenancy()->initialized)->toBeTrue();
|
||||
expect(tenant('id'))->toBe('acme');
|
||||
pest()->get('http://foo.localhost/test')->assertSee('acme');
|
||||
});
|
||||
|
||||
test('tenant can be identified by domain', function () {
|
||||
config(['tenancy.identification.central_domains' => []]);
|
||||
|
||||
$tenant = CombinedTenant::create([
|
||||
'id' => 'acme',
|
||||
]);
|
||||
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'foobar.localhost',
|
||||
]);
|
||||
$tenant = Tenant::create(['id' => 'acme']);
|
||||
$tenant->domains()->create(['domain' => 'foobar.localhost']);
|
||||
|
||||
expect(tenancy()->initialized)->toBeFalse();
|
||||
|
||||
pest()
|
||||
->get('http://foobar.localhost/foo/abc/xyz')
|
||||
->assertSee('abc + xyz');
|
||||
|
||||
expect(tenancy()->initialized)->toBeTrue();
|
||||
expect(tenant('id'))->toBe('acme');
|
||||
pest()->get('http://foobar.localhost/test')->assertSee('acme');
|
||||
});
|
||||
|
||||
class CombinedTenant extends Models\Tenant
|
||||
{
|
||||
use HasDomains;
|
||||
}
|
||||
test('domain records can be either in domain syntax or subdomain syntax', function () {
|
||||
config(['tenancy.identification.central_domains' => ['localhost']]);
|
||||
|
||||
$foo = Tenant::create(['id' => 'foo']);
|
||||
$foo->domains()->create(['domain' => 'foo']);
|
||||
|
||||
$bar = Tenant::create(['id' => 'bar']);
|
||||
$bar->domains()->create(['domain' => 'bar.localhost']);
|
||||
|
||||
expect(tenancy()->initialized)->toBeFalse();
|
||||
|
||||
// Subdomain format
|
||||
pest()->get('http://foo.localhost/test')->assertSee('foo');
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
// Domain format
|
||||
pest()->get('http://bar.localhost/test')->assertSee('bar');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ test('origin identification works', function () {
|
|||
});
|
||||
|
||||
test('tenant routes are not accessible on central domains while using origin identification', function () {
|
||||
$tenant = Tenant::create();
|
||||
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'foo',
|
||||
]);
|
||||
|
||||
pest()
|
||||
->withHeader('Origin', 'localhost')
|
||||
->post('home')
|
||||
|
|
@ -54,3 +60,50 @@ test('onfail logic can be customized', function() {
|
|||
->post('home')
|
||||
->assertSee('onFail message');
|
||||
});
|
||||
|
||||
test('origin identification can be used with universal routes', function () {
|
||||
$tenant = Tenant::create();
|
||||
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'foo',
|
||||
]);
|
||||
|
||||
Route::post('/universal', function () {
|
||||
return response(tenant('id') ?? 'central');
|
||||
})->middleware([InitializeTenancyByOriginHeader::class, 'universal'])->name('universal');
|
||||
|
||||
pest()
|
||||
->withHeader('Origin', 'foo.localhost')
|
||||
->post('universal')
|
||||
->assertSee($tenant->id);
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
pest()
|
||||
->withHeader('Origin', 'localhost')
|
||||
->post('universal')
|
||||
->assertSee('central');
|
||||
|
||||
pest()
|
||||
// no header
|
||||
->post('universal')
|
||||
->assertSee('central');
|
||||
});
|
||||
|
||||
test('origin identification can be used with both domains and subdomains', function () {
|
||||
$foo = Tenant::create();
|
||||
$foo->domains()->create(['domain' => 'foo']);
|
||||
|
||||
$bar = Tenant::create();
|
||||
$bar->domains()->create(['domain' => 'bar.localhost']);
|
||||
|
||||
pest()
|
||||
->withHeader('Origin', 'foo.localhost')
|
||||
->post('home')
|
||||
->assertSee($foo->id);
|
||||
|
||||
pest()
|
||||
->withHeader('Origin', 'bar.localhost')
|
||||
->post('home')
|
||||
->assertSee($bar->id);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -145,6 +145,36 @@ test('db name is prefixed with db path when sqlite is used', function () {
|
|||
expect(database_path('foodb'))->toBe(config('database.connections.tenant.database'));
|
||||
});
|
||||
|
||||
test('sqlite databases use the WAL journal mode by default', function (bool|null $wal) {
|
||||
$expected = $wal ? 'wal' : 'delete';
|
||||
if ($wal !== null) {
|
||||
SQLiteDatabaseManager::$WAL = $wal;
|
||||
} else {
|
||||
// default behavior
|
||||
$expected = 'wal';
|
||||
}
|
||||
|
||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->toListener());
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'tenancy_db_connection' => 'sqlite',
|
||||
]);
|
||||
|
||||
$dbPath = database_path($tenant->database()->getName());
|
||||
|
||||
expect(file_exists($dbPath))->toBeTrue();
|
||||
|
||||
$db = new PDO('sqlite:' . $dbPath);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
expect($db->query('pragma journal_mode')->fetch(PDO::FETCH_ASSOC)['journal_mode'])->toBe($expected);
|
||||
|
||||
// cleanup
|
||||
SQLiteDatabaseManager::$WAL = true;
|
||||
})->with([true, false, null]);
|
||||
|
||||
test('schema manager uses schema to separate tenant dbs', function () {
|
||||
config([
|
||||
'tenancy.database.managers.pgsql' => PostgreSQLSchemaManager::class,
|
||||
|
|
@ -332,7 +362,7 @@ test('database credentials can be provided to PermissionControlledMySQLDatabaseM
|
|||
/** @var PermissionControlledMySQLDatabaseManager $manager */
|
||||
$manager = $tenant->database()->manager();
|
||||
|
||||
expect($manager->database()->getConfig('username'))->toBe($username); // user created for the HOST connection
|
||||
expect($manager->connection()->getConfig('username'))->toBe($username); // user created for the HOST connection
|
||||
expect($manager->userExists($usernameForNewDB))->toBeTrue();
|
||||
expect($manager->databaseExists($name))->toBeTrue();
|
||||
});
|
||||
|
|
@ -371,7 +401,7 @@ test('tenant database can be created by using the username and password from ten
|
|||
/** @var MySQLDatabaseManager $manager */
|
||||
$manager = $tenant->database()->manager();
|
||||
|
||||
expect($manager->database()->getConfig('username'))->toBe($username); // user created for the HOST connection
|
||||
expect($manager->connection()->getConfig('username'))->toBe($username); // user created for the HOST connection
|
||||
expect($manager->databaseExists($name))->toBeTrue();
|
||||
});
|
||||
|
||||
|
|
@ -417,7 +447,7 @@ test('the tenant connection template can be specified either by name or as a con
|
|||
/** @var MySQLDatabaseManager $manager */
|
||||
$manager = $tenant->database()->manager();
|
||||
expect($manager->databaseExists($name))->toBeTrue();
|
||||
expect($manager->database()->getConfig('host'))->toBe('mysql');
|
||||
expect($manager->connection()->getConfig('host'))->toBe('mysql');
|
||||
|
||||
config([
|
||||
'tenancy.database.template_tenant_connection' => [
|
||||
|
|
@ -446,7 +476,7 @@ test('the tenant connection template can be specified either by name or as a con
|
|||
/** @var MySQLDatabaseManager $manager */
|
||||
$manager = $tenant->database()->manager();
|
||||
expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works
|
||||
expect($manager->database()->getConfig('host'))->toBe('mysql2');
|
||||
expect($manager->connection()->getConfig('host'))->toBe('mysql2');
|
||||
});
|
||||
|
||||
test('partial tenant connection templates get merged into the central connection template', function () {
|
||||
|
|
@ -471,8 +501,8 @@ test('partial tenant connection templates get merged into the central connection
|
|||
/** @var MySQLDatabaseManager $manager */
|
||||
$manager = $tenant->database()->manager();
|
||||
expect($manager->databaseExists($name))->toBeTrue(); // tenant connection works
|
||||
expect($manager->database()->getConfig('host'))->toBe('mysql2');
|
||||
expect($manager->database()->getConfig('url'))->toBeNull();
|
||||
expect($manager->connection()->getConfig('host'))->toBe('mysql2');
|
||||
expect($manager->connection()->getConfig('url'))->toBeNull();
|
||||
});
|
||||
|
||||
// Datasets
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue