[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('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']); // Custom driver, not included in TenancyBroadcastManager::$tenantBroadcasters by default TenancyBroadcastManager::$tenantBroadcasters = ['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); // When updating tenant properties without reinitializing, the tenant property update doesn't update the config, // so the config has to be modified manually. Only methods that use TenancyBroadcastManager::get() // will use the updated credentials without needing to reinitialize tenancy (e.g. the bound // BroadcasterContract instance will still use the original credentials, even after config gets updated directly). config(["broadcasting.connections.{$driver}.key" => 'new_tenant1_key']); expect(app(BroadcastManager::class)->driver()->config['key'])->toBe('new_tenant1_key'); expect(Broadcast::driver()->config['key'])->toBe('new_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', // Except for this custom driver, assume that the drivers are included in TenancyBroadcastManager::$tenantBroadcasters by default ]); 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))->toBe([...$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))->toBe($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))->toBe($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']); // Custom driver, not included in TenancyBroadcastManager::$tenantBroadcasters by default TenancyBroadcastManager::$tenantBroadcasters = ['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', // Except for this custom driver, assume that the drivers are included in TenancyBroadcastManager::$tenantBroadcasters by default ]);