mirror of
https://github.com/archtechx/tenancy.git
synced 2026-08-06 06:34:03 +00:00
TenancyBroadcastManager now overrides resolve() (only to pass the central channels to the newly resolved broadcasters) instead of get(), so broadcasters stay cached for the duration of the tenant's context. Remove TenancyBroadcastManager::$tenantBroadcasters -- all broadcasters now inherit the central channels, so even custom drivers work without any additional configuration/registration. Channels registered via Broadcast::channel() in tenant context are no longer lost on subsequent broadcaster retrievals (e.g. during /broadcasting/auth). Added a dedicated test for this,
322 lines
13 KiB
PHP
322 lines
13 KiB
PHP
<?php
|
|
|
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Stancl\Tenancy\Events\TenancyEnded;
|
|
use Illuminate\Broadcasting\BroadcastManager;
|
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
|
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
|
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
|
use Illuminate\Support\Facades\Broadcast;
|
|
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
|
|
|
$cleanup = function () {
|
|
BroadcastingConfigBootstrapper::$broadcaster = null;
|
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
|
BroadcastingConfigBootstrapper::$mapPresets = [
|
|
'pusher' => [
|
|
'broadcasting.connections.pusher.key' => 'pusher_key',
|
|
'broadcasting.connections.pusher.secret' => 'pusher_secret',
|
|
'broadcasting.connections.pusher.app_id' => 'pusher_app_id',
|
|
'broadcasting.connections.pusher.options.cluster' => 'pusher_cluster',
|
|
],
|
|
'reverb' => [
|
|
'broadcasting.connections.reverb.key' => 'reverb_key',
|
|
'broadcasting.connections.reverb.secret' => 'reverb_secret',
|
|
'broadcasting.connections.reverb.app_id' => 'reverb_app_id',
|
|
'broadcasting.connections.reverb.options.cluster' => 'reverb_cluster',
|
|
],
|
|
'ably' => [
|
|
'broadcasting.connections.ably.key' => 'ably_key',
|
|
'broadcasting.connections.ably.public' => 'ably_public',
|
|
],
|
|
];
|
|
};
|
|
|
|
beforeEach(function () use ($cleanup) {
|
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
|
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
|
|
|
$cleanup();
|
|
});
|
|
|
|
afterEach($cleanup);
|
|
|
|
test('BroadcastingConfigBootstrapper binds TenancyBroadcastManager to BroadcastManager and reverts the binding when tenancy is ended', function() {
|
|
config(['tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class]]);
|
|
|
|
expect(app(BroadcastManager::class))
|
|
->toBeInstanceOf(BroadcastManager::class)
|
|
->not()->toBeInstanceOf(TenancyBroadcastManager::class);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
expect(app(BroadcastManager::class))->toBeInstanceOf(TenancyBroadcastManager::class);
|
|
|
|
tenancy()->end();
|
|
|
|
expect(app(BroadcastManager::class))
|
|
->toBeInstanceOf(BroadcastManager::class)
|
|
->not()->toBeInstanceOf(TenancyBroadcastManager::class);
|
|
});
|
|
|
|
test('ending tenancy reverts the bound broadcaster to the original instance', function() {
|
|
config([
|
|
'tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class],
|
|
'broadcasting.default' => 'testing',
|
|
'broadcasting.connections.testing.driver' => 'testing',
|
|
]);
|
|
|
|
app(BroadcastManager::class)->extend('testing', fn ($app, $config) => new TestingBroadcaster('testing', $config));
|
|
|
|
$originalBroadcaster = app(BroadcasterContract::class);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
// BroadcastingConfigBootstrapper binds a freshly resolved broadcaster
|
|
expect(app(BroadcasterContract::class))->not()->toBe($originalBroadcaster);
|
|
|
|
// The bound broadcaster is the same instance as the tenant BroadcastManager's default driver
|
|
expect(app(BroadcasterContract::class))->toBe(app(BroadcastManager::class)->driver());
|
|
|
|
tenancy()->end();
|
|
|
|
// Ending tenancy reverts the binding back to the original broadcaster instance
|
|
expect($originalBroadcaster)->toBe(app(BroadcasterContract::class));
|
|
});
|
|
|
|
test('BroadcastingConfigBootstrapper maps tenant properties to broadcaster credentials correctly', function(string $driver) {
|
|
config([
|
|
'broadcasting.default' => $driver,
|
|
"broadcasting.connections.{$driver}.key" => 'central_key',
|
|
'tenancy.bootstrappers' => [
|
|
BroadcastingConfigBootstrapper::class,
|
|
],
|
|
]);
|
|
|
|
if ($driver === 'custom') {
|
|
config(['broadcasting.connections.custom.driver' => 'custom']);
|
|
}
|
|
|
|
BroadcastingConfigBootstrapper::$credentialsMap["broadcasting.connections.{$driver}.key"] = 'testing_key';
|
|
|
|
app(BroadcastManager::class)->extend($driver, fn ($app, $config) => new TestingBroadcaster('testing', $config));
|
|
|
|
$tenant1 = Tenant::create(['testing_key' => 'tenant1_key']);
|
|
$tenant2 = Tenant::create(['testing_key' => 'tenant2_key']);
|
|
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('central_key');
|
|
expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('central_key');
|
|
expect(app(BroadcasterContract::class)->config['key'])->toBe('central_key');
|
|
expect(Broadcast::driver()->config['key'])->toBe('central_key');
|
|
|
|
tenancy()->initialize($tenant1);
|
|
|
|
// Tenant's testing_key property is mapped to the current broadcasting connection key
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('tenant1_key');
|
|
expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('tenant1_key');
|
|
// Switching to tenant context makes the currently bound Broadcaster instance use the tenant's config
|
|
expect(app(BroadcasterContract::class)->config['key'])->toBe('tenant1_key');
|
|
// The Broadcast facade (used in BroadcastController::authenticate) uses the broadcaster with tenant config
|
|
// instead of the stale broadcaster instance resolved before tenancy was initialized
|
|
expect(Broadcast::driver()->config['key'])->toBe('tenant1_key');
|
|
|
|
tenancy()->initialize($tenant2);
|
|
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('tenant2_key');
|
|
// Switching to another tenant context makes the current broadcaster use the new tenant's config
|
|
expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('tenant2_key');
|
|
expect(app(BroadcasterContract::class)->config['key'])->toBe('tenant2_key');
|
|
expect(Broadcast::driver()->config['key'])->toBe('tenant2_key');
|
|
|
|
$tenant2->update(['testing_key' => 'new_tenant2_key']);
|
|
|
|
// Reinitialize tenancy to apply the tenant property update to config
|
|
tenancy()->end();
|
|
tenancy()->initialize($tenant2);
|
|
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('new_tenant2_key');
|
|
expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('new_tenant2_key');
|
|
expect(app(BroadcasterContract::class)->config['key'])->toBe('new_tenant2_key');
|
|
expect(Broadcast::driver()->config['key'])->toBe('new_tenant2_key');
|
|
|
|
tenancy()->initialize($tenant1);
|
|
|
|
// Direct config changes aren't picked up by the broadcasters -- they get resolved
|
|
// using the config mapped from tenant properties at initialization and stay cached
|
|
// until tenancy is reinitialized.
|
|
config(["broadcasting.connections.{$driver}.key" => 'new_tenant1_key']);
|
|
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('new_tenant1_key');
|
|
expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('tenant1_key');
|
|
expect(app(BroadcasterContract::class)->config['key'])->toBe('tenant1_key');
|
|
expect(Broadcast::driver()->config['key'])->toBe('tenant1_key');
|
|
|
|
tenancy()->end();
|
|
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('central_key');
|
|
// Ending tenancy reverts the broadcaster changes
|
|
expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('central_key');
|
|
expect(app(BroadcasterContract::class)->config['key'])->toBe('central_key');
|
|
expect(Broadcast::driver()->config['key'])->toBe('central_key');
|
|
})->with([
|
|
'pusher',
|
|
'ably',
|
|
'reverb',
|
|
'custom',
|
|
]);
|
|
|
|
test('tenant broadcast manager receives the custom driver creators of the central broadcast manager', function() {
|
|
config([
|
|
'tenancy.bootstrappers' => [
|
|
BroadcastingConfigBootstrapper::class,
|
|
],
|
|
]);
|
|
|
|
$tenant = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
|
|
app(BroadcastManager::class)->extend('testing', fn($app, $config) => new TestingBroadcaster('testing', $config));
|
|
|
|
$originalDrivers = array_keys(invade(app(BroadcastManager::class))->customCreators);
|
|
|
|
expect($originalDrivers)->toContain('testing');
|
|
|
|
tenancy()->initialize($tenant);
|
|
|
|
app(BroadcastManager::class)->extend(
|
|
'testing-tenant1',
|
|
fn($app, $config) => new TestingBroadcaster('testing-tenant1', $config)
|
|
);
|
|
|
|
// Current BroadcastManager instance has the original custom creators plus the newly registered testing-tenant1 creator
|
|
expect(array_keys(invade(app(BroadcastManager::class))->customCreators))->toEqualCanonicalizing([...$originalDrivers, 'testing-tenant1']);
|
|
|
|
tenancy()->initialize($tenant2);
|
|
|
|
// Current BroadcastManager only has the original custom creators,
|
|
// the creator added in the previous tenant's context doesn't persist.
|
|
expect(array_keys(invade(app(BroadcastManager::class))->customCreators))->toEqualCanonicalizing($originalDrivers);
|
|
|
|
tenancy()->end();
|
|
|
|
// Ending tenancy reverts the BroadcastManager binding back to the original state,
|
|
// the creator registered in the tenant context doesn't persist.
|
|
expect(array_keys(invade(app(BroadcastManager::class))->customCreators))->toEqualCanonicalizing($originalDrivers);
|
|
});
|
|
|
|
test('tenant broadcasters receive the channels from the broadcaster bound in central context', function(string $driver) {
|
|
config([
|
|
'tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class],
|
|
'broadcasting.default' => $driver,
|
|
]);
|
|
|
|
if ($driver === 'custom') {
|
|
config(['broadcasting.connections.custom.driver' => 'custom']);
|
|
}
|
|
|
|
$tenant1 = Tenant::create();
|
|
$tenant2 = Tenant::create();
|
|
|
|
app(BroadcastManager::class)->extend($driver, fn($app, $config) => new TestingBroadcaster('testing'));
|
|
$getCurrentChannelsFromBoundBroadcaster = fn() => array_keys(invade(app(BroadcasterContract::class))->channels);
|
|
$getCurrentChannelsThroughManager = fn() => array_keys(invade(app(BroadcastManager::class)->driver())->channels);
|
|
|
|
Broadcast::channel($channel = 'testing-channel', fn() => true);
|
|
|
|
expect($channel)
|
|
->toBeIn($getCurrentChannelsThroughManager())
|
|
->toBeIn($getCurrentChannelsFromBoundBroadcaster());
|
|
|
|
tenancy()->initialize($tenant1);
|
|
|
|
expect($channel)
|
|
->toBeIn($getCurrentChannelsThroughManager())
|
|
->toBeIn($getCurrentChannelsFromBoundBroadcaster());
|
|
|
|
tenancy()->initialize($tenant2);
|
|
|
|
expect($channel)
|
|
->toBeIn($getCurrentChannelsThroughManager())
|
|
->toBeIn($getCurrentChannelsFromBoundBroadcaster());
|
|
|
|
tenancy()->end();
|
|
|
|
expect($channel)
|
|
->toBeIn($getCurrentChannelsThroughManager())
|
|
->toBeIn($getCurrentChannelsFromBoundBroadcaster());
|
|
})->with([
|
|
'pusher',
|
|
'ably',
|
|
'reverb',
|
|
'custom',
|
|
]);
|
|
|
|
test('channels registered in tenant context persist within that context but do not leak into other contexts', function() {
|
|
config([
|
|
'tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class],
|
|
'broadcasting.default' => 'testing',
|
|
'broadcasting.connections.testing.driver' => 'testing',
|
|
]);
|
|
|
|
app(BroadcastManager::class)->extend('testing', fn($app, $config) => new TestingBroadcaster('testing', $config));
|
|
|
|
Broadcast::channel('central-channel', fn() => true);
|
|
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
Broadcast::channel('tenant-channel', fn() => true);
|
|
|
|
// Retrieving the broadcaster again (e.g. on Broadcast::auth() during a /broadcasting/auth request)
|
|
// returns the cached broadcaster, so the channel registered in tenant context is still available
|
|
expect(array_keys(invade(Broadcast::driver())->channels))
|
|
->toContain('central-channel')
|
|
->toContain('tenant-channel');
|
|
|
|
// The channel registered in the previous tenant's context doesn't leak to another tenant's broadcaster
|
|
tenancy()->initialize(Tenant::create());
|
|
|
|
expect(array_keys(invade(Broadcast::driver())->channels))
|
|
->toContain('central-channel')
|
|
->not()->toContain('tenant-channel');
|
|
|
|
tenancy()->end();
|
|
|
|
// The channel registered in tenant context doesn't leak to the central broadcaster
|
|
expect(array_keys(invade(Broadcast::driver())->channels))
|
|
->toContain('central-channel')
|
|
->not()->toContain('tenant-channel');
|
|
});
|
|
|
|
test('mappings specified in credentialsMap override default mapPresets', function($driver) {
|
|
config([
|
|
'tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class],
|
|
'broadcasting.default' => $driver,
|
|
]);
|
|
|
|
// Preset used for broadcasters included in TenancyBroadcastManager::$tenantBroadcasters by default.
|
|
// This is the default for all tenant broadcasters, but we set it here for clarity.
|
|
BroadcastingConfigBootstrapper::$mapPresets[$driver]["broadcasting.connections.{$driver}.key"] = "{$driver}_key";
|
|
|
|
// Custom mapping specified in credentialsMap should override the preset mapping for the tested broadcaster
|
|
BroadcastingConfigBootstrapper::$credentialsMap["broadcasting.connections.{$driver}.key"] = 'broadcasting_key';
|
|
|
|
app(BroadcastManager::class)->extend($driver, fn($app, $config) => new TestingBroadcaster('testing'));
|
|
|
|
$tenant = Tenant::create([
|
|
"{$driver}_key" => 'preset_value',
|
|
'broadcasting_key' => 'custom_value',
|
|
]);
|
|
|
|
|
|
tenancy()->initialize($tenant);
|
|
|
|
expect(config("broadcasting.connections.{$driver}.key"))->toBe('custom_value');
|
|
})->with([
|
|
'pusher',
|
|
'ably',
|
|
'reverb',
|
|
]);
|