diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b6bb7f7..8ecb863b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,11 @@ jobs: strategy: matrix: - laravel: ['dev-cache-methods as 9.47'] + include: + - laravel: 9 + php: "8.0" + - laravel: 10 + php: "8.1" steps: - name: Checkout @@ -23,7 +27,7 @@ jobs: - name: Install Composer dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update + composer require "laravel/framework:^${{ matrix.laravel }}.0" --no-interaction --no-update composer update --prefer-dist --no-interaction - name: Run tests run: ./vendor/bin/pest diff --git a/Dockerfile b/Dockerfile index 5dfe442c..421e43d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN apt-get update \ && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update \ - && ACCEPT_EULA=Y apt-get install -y unixodbc-dev msodbcsql17 + && ACCEPT_EULA=Y apt-get install -y unixodbc-dev=2.3.7 unixodbc=2.3.7 odbcinst1debian2=2.3.7 odbcinst=2.3.7 msodbcsql17 # set PHP version RUN update-alternatives --set php /usr/bin/php$PHP_VERSION \ diff --git a/assets/TenancyServiceProvider.stub.php b/assets/TenancyServiceProvider.stub.php index 6735b37f..a2679061 100644 --- a/assets/TenancyServiceProvider.stub.php +++ b/assets/TenancyServiceProvider.stub.php @@ -4,14 +4,14 @@ declare(strict_types=1); namespace App\Providers; +use Stancl\Tenancy\Jobs; +use Stancl\Tenancy\Events; +use Stancl\Tenancy\Listeners; +use Stancl\Tenancy\Middleware; +use Stancl\JobPipeline\JobPipeline; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; -use Stancl\JobPipeline\JobPipeline; -use Stancl\Tenancy\Events; -use Stancl\Tenancy\Jobs; -use Stancl\Tenancy\Listeners; -use Stancl\Tenancy\Middleware; class TenancyServiceProvider extends ServiceProvider { @@ -118,6 +118,21 @@ class TenancyServiceProvider extends ServiceProvider ]; } + protected function overrideUrlInTenantContext(): void + { + /** + * Example of CLI tenant URL root override: + * + * UrlTenancyBootstrapper::$rootUrlOverride = function (Tenant $tenant) { + * $baseUrl = url('/'); + * $scheme = str($baseUrl)->before('://'); + * $hostname = str($baseUrl)->after($scheme . '://'); + * + * return $scheme . '://' . $tenant->getTenantKey() . '.' . $hostname; + *}; + */ + } + public function register() { // @@ -129,6 +144,7 @@ class TenancyServiceProvider extends ServiceProvider $this->mapRoutes(); $this->makeTenancyMiddlewareHighestPriority(); + $this->overrideUrlInTenantContext(); } protected function bootEvents() diff --git a/assets/config.php b/assets/config.php index 94445e64..0a4a6233 100644 --- a/assets/config.php +++ b/assets/config.php @@ -103,6 +103,7 @@ return [ Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class, Stancl\Tenancy\Bootstrappers\BatchTenancyBootstrapper::class, // Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper::class, + // Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper::class, // Stancl\Tenancy\Bootstrappers\SessionTenancyBootstrapper::class, // Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper::class, // Queueing mail requires using QueueTenancyBootstrapper with $forceRefresh set to true // Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed @@ -259,7 +260,7 @@ return [ ], /** - * Redis tenancy config. Used by RedisTenancyBoostrapper. + * Redis tenancy config. Used by RedisTenancyBootstrapper. * * Note: You need phpredis to use Redis tenancy. * @@ -287,6 +288,7 @@ return [ // Stancl\Tenancy\Features\TelescopeTags::class, // Stancl\Tenancy\Features\TenantConfig::class, // https://tenancyforlaravel.com/docs/v3/features/tenant-config // Stancl\Tenancy\Features\CrossDomainRedirect::class, // https://tenancyforlaravel.com/docs/v3/features/cross-domain-redirect + // Stancl\Tenancy\Features\ViteBundler::class, ], /** diff --git a/composer.json b/composer.json index 2b1cb662..1431cec5 100644 --- a/composer.json +++ b/composer.json @@ -17,17 +17,19 @@ "require": { "php": "^8.2", "ext-json": "*", - "illuminate/support": "^9.38", + "illuminate/support": "^9.38|^10.0", + "facade/ignition-contracts": "^1.0.2", "spatie/ignition": "^1.4", - "ramsey/uuid": "^4.0", - "stancl/jobpipeline": "^1.0", - "stancl/virtualcolumn": "^1.3" + "ramsey/uuid": "^4.7.3", + "stancl/jobpipeline": "^1.6.2", + "stancl/virtualcolumn": "^1.3.1", + "spatie/invade": "^1.1" }, "require-dev": { - "laravel/framework": "dev-cache-methods as 9.47", - "orchestra/testbench": "^7.0", - "league/flysystem-aws-s3-v3": "^3.0", - "doctrine/dbal": "^2.10", + "laravel/framework": "^9.38|^10.0", + "orchestra/testbench": "^7.0|^8.0", + "league/flysystem-aws-s3-v3": "^3.12.2", + "doctrine/dbal": "^3.6.0", "spatie/valuestore": "^1.2.5", "pestphp/pest": "^1.21", "nunomaduro/larastan": "^2.4", diff --git a/phpstan.neon b/phpstan.neon index 91e9f3af..19cda805 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -40,10 +40,6 @@ parameters: message: '#Illuminate\\Routing\\UrlGenerator#' paths: - src/Bootstrappers/FilesystemTenancyBootstrapper.php - - - message: '#select\(\) expects string, Illuminate\\Database\\Query\\Expression given#' - paths: - - src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php - message: '#Trying to invoke Closure\|null but it might not be a callable#' paths: diff --git a/src/Bootstrappers/BroadcastTenancyBootstrapper.php b/src/Bootstrappers/BroadcastTenancyBootstrapper.php new file mode 100644 index 00000000..2f625437 --- /dev/null +++ b/src/Bootstrappers/BroadcastTenancyBootstrapper.php @@ -0,0 +1,95 @@ + 'tenant_property', + * ] + */ + public static array $credentialsMap = []; + + public static string|null $broadcaster = null; + + protected array $originalConfig = []; + protected BroadcastManager|null $originalBroadcastManager = null; + protected Broadcaster|null $originalBroadcaster = null; + + public static array $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', + ], + 'ably' => [ + 'broadcasting.connections.ably.key' => 'ably_key', + 'broadcasting.connections.ably.public' => 'ably_public', + ], + ]; + + public function __construct( + protected Repository $config, + protected Application $app + ) { + static::$broadcaster ??= $config->get('broadcasting.default'); + static::$credentialsMap = array_merge(static::$credentialsMap, static::$mapPresets[static::$broadcaster] ?? []); + } + + public function bootstrap(Tenant $tenant): void + { + $this->originalBroadcastManager = $this->app->make(BroadcastManager::class); + $this->originalBroadcaster = $this->app->make(Broadcaster::class); + + $this->setConfig($tenant); + + // Make BroadcastManager resolve to a custom BroadcastManager which makes the broadcasters use the tenant credentials + $this->app->extend(BroadcastManager::class, function (BroadcastManager $broadcastManager) { + return new TenancyBroadcastManager($this->app); + }); + } + + public function revert(): void + { + // Change the BroadcastManager and Broadcaster singletons back to what they were before initializing tenancy + $this->app->singleton(BroadcastManager::class, fn (Application $app) => $this->originalBroadcastManager); + $this->app->singleton(Broadcaster::class, fn (Application $app) => $this->originalBroadcaster); + + $this->unsetConfig(); + } + + protected function setConfig(Tenant $tenant): void + { + foreach (static::$credentialsMap as $configKey => $storageKey) { + $override = $tenant->$storageKey; + + if (array_key_exists($storageKey, $tenant->getAttributes())) { + $this->originalConfig[$configKey] ??= $this->config->get($configKey); + + $this->config->set($configKey, $override); + } + } + } + + protected function unsetConfig(): void + { + foreach ($this->originalConfig as $key => $value) { + $this->config->set($key, $value); + } + } +} diff --git a/src/Bootstrappers/UrlTenancyBootstrapper.php b/src/Bootstrappers/UrlTenancyBootstrapper.php new file mode 100644 index 00000000..db27c8c5 --- /dev/null +++ b/src/Bootstrappers/UrlTenancyBootstrapper.php @@ -0,0 +1,41 @@ +originalRootUrl = $this->urlGenerator->to('/'); + + if (static::$rootUrlOverride) { + $newRootUrl = (static::$rootUrlOverride)($tenant); + + $this->urlGenerator->forceRootUrl($newRootUrl); + $this->config->set('app.url', $newRootUrl); + } + } + + public function revert(): void + { + $this->urlGenerator->forceRootUrl($this->originalRootUrl); + $this->config->set('app.url', $this->originalRootUrl); + } +} diff --git a/src/Database/Models/ImpersonationToken.php b/src/Database/Models/ImpersonationToken.php index 05d17ad4..3d7b595b 100644 --- a/src/Database/Models/ImpersonationToken.php +++ b/src/Database/Models/ImpersonationToken.php @@ -33,9 +33,8 @@ class ImpersonationToken extends Model public $incrementing = false; protected $table = 'tenant_user_impersonation_tokens'; - - protected $dates = [ - 'created_at', + protected $casts = [ + 'created_at' => 'datetime', ]; public static function booted(): void diff --git a/src/Database/Models/Tenant.php b/src/Database/Models/Tenant.php index 37c2af2d..c3574942 100644 --- a/src/Database/Models/Tenant.php +++ b/src/Database/Models/Tenant.php @@ -32,6 +32,8 @@ class Tenant extends Model implements Contracts\Tenant Concerns\InitializationHelpers, Concerns\InvalidatesResolverCache; + protected static $modelsShouldPreventAccessingMissingAttributes = false; + protected $table = 'tenants'; protected $primaryKey = 'id'; protected $guarded = []; diff --git a/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php b/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php index f7e7440e..308d8786 100644 --- a/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php +++ b/src/Database/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php @@ -41,7 +41,8 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl protected function isVersion8(): bool { - $version = $this->database()->select($this->database()->raw('select version()'))[0]->{'version()'}; + $versionSelect = (string) $this->database()->raw('select version()')->getValue($this->database()->getQueryGrammar()); + $version = $this->database()->select($versionSelect)[0]->{'version()'}; return version_compare($version, '8.0.0') >= 0; } diff --git a/src/Features/ViteBundler.php b/src/Features/ViteBundler.php new file mode 100644 index 00000000..e3fee2fa --- /dev/null +++ b/src/Features/ViteBundler.php @@ -0,0 +1,26 @@ +app = $app; + } + + public function bootstrap(Tenancy $tenancy): void + { + $this->app->singleton(\Illuminate\Foundation\Vite::class, Vite::class); + } +} diff --git a/src/TenancyBroadcastManager.php b/src/TenancyBroadcastManager.php new file mode 100644 index 00000000..59e30b57 --- /dev/null +++ b/src/TenancyBroadcastManager.php @@ -0,0 +1,65 @@ +resolve() (even when they're + * cached and available in the $broadcasters property). + * + * The reason for recreating the broadcasters is + * to make your app use the correct broadcaster credentials when tenancy is initialized. + */ + public static array $tenantBroadcasters = ['pusher', 'ably']; + + /** + * Override the get method so that the broadcasters in $tenantBroadcasters + * always get freshly resolved even when they're cached and available in the $broadcasters property, + * and that the resolved broadcaster will override the BroadcasterContract::class singleton. + * + * If there's a cached broadcaster with the same name as $name, + * give its channels to the newly resolved bootstrapper. + */ + protected function get($name) + { + if (in_array($name, static::$tenantBroadcasters)) { + /** @var Broadcaster|null $originalBroadcaster */ + $originalBroadcaster = $this->app->make(BroadcasterContract::class); + $newBroadcaster = $this->resolve($name); + + // If there is a current broadcaster, give its channels to the newly resolved one + // Broadcasters only have to implement the Illuminate\Contracts\Broadcasting\Broadcaster contract + // Which doesn't require the channels property + // So passing the channels is only needed for Illuminate\Broadcasting\Broadcasters\Broadcaster instances + if ($originalBroadcaster instanceof Broadcaster && $newBroadcaster instanceof Broadcaster) { + $this->passChannelsFromOriginalBroadcaster($originalBroadcaster, $newBroadcaster); + } + + $this->app->singleton(BroadcasterContract::class, fn (Application $app) => $newBroadcaster); + + return $newBroadcaster; + } + + return parent::get($name); + } + + // Because, unlike the original broadcaster, the newly resolved broadcaster won't have the channels registered using routes/channels.php + // Using it for broadcasting won't work, unless we make it have the original broadcaster's channels + protected function passChannelsFromOriginalBroadcaster(Broadcaster $originalBroadcaster, Broadcaster $newBroadcaster): void + { + // invade() because channels can't be retrieved through any of the broadcaster's public methods + $originalBroadcaster = invade($originalBroadcaster); + + foreach ($originalBroadcaster->channels as $channel => $callback) { + $newBroadcaster->channel($channel, $callback, $originalBroadcaster->retrieveChannelOptions($channel)); + } + } +} diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index c0276392..c3a68755 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -63,6 +63,7 @@ class TenancyServiceProvider extends ServiceProvider $this->app->singleton(Commands\Rollback::class, function ($app) { return new Commands\Rollback($app['migrator']); }); + $this->app->singleton(Commands\Seed::class, function ($app) { return new Commands\Seed($app['db']); }); diff --git a/src/Vite.php b/src/Vite.php new file mode 100644 index 00000000..ca47fcc3 --- /dev/null +++ b/src/Vite.php @@ -0,0 +1,22 @@ +toBeFalse(); }); +test('BroadcastTenancyBootstrapper binds TenancyBroadcastManager to BroadcastManager and reverts the binding when tenancy is ended', function() { + expect(app(BroadcastManager::class))->toBeInstanceOf(BroadcastManager::class); + + tenancy()->initialize(Tenant::create()); + + expect(app(BroadcastManager::class))->toBeInstanceOf(TenancyBroadcastManager::class); + + tenancy()->end(); + + expect(app(BroadcastManager::class))->toBeInstanceOf(BroadcastManager::class); +}); + +test('BroadcastTenancyBootstrapper maps tenant broadcaster credentials to config as specified in the $credentialsMap property and reverts the config after ending tenancy', function() { + config([ + 'broadcasting.connections.testing.driver' => 'testing', + 'broadcasting.connections.testing.message' => $defaultMessage = 'default', + ]); + + BroadcastTenancyBootstrapper::$credentialsMap = [ + 'broadcasting.connections.testing.message' => 'testing_broadcaster_message', + ]; + + $tenant = Tenant::create(['testing_broadcaster_message' => $tenantMessage = 'first testing']); + $tenant2 = Tenant::create(['testing_broadcaster_message' => $secondTenantMessage = 'second testing']); + + tenancy()->initialize($tenant); + + expect(array_key_exists('testing_broadcaster_message', tenant()->getAttributes()))->toBeTrue(); + expect(config('broadcasting.connections.testing.message'))->toBe($tenantMessage); + + tenancy()->initialize($tenant2); + + expect(config('broadcasting.connections.testing.message'))->toBe($secondTenantMessage); + + tenancy()->end(); + + expect(config('broadcasting.connections.testing.message'))->toBe($defaultMessage); +}); + +test('BroadcastTenancyBootstrapper makes the app use broadcasters with the correct credentials', function() { + config([ + 'broadcasting.default' => 'testing', + 'broadcasting.connections.testing.driver' => 'testing', + 'broadcasting.connections.testing.message' => $defaultMessage = 'default', + ]); + + TenancyBroadcastManager::$tenantBroadcasters[] = 'testing'; + BroadcastTenancyBootstrapper::$credentialsMap = [ + 'broadcasting.connections.testing.message' => 'testing_broadcaster_message', + ]; + + $registerTestingBroadcaster = fn() => app(BroadcastManager::class)->extend('testing', fn($app, $config) => new TestingBroadcaster($config['message'])); + + $registerTestingBroadcaster(); + + expect(invade(app(BroadcastManager::class)->driver())->message)->toBe($defaultMessage); + + $tenant = Tenant::create(['testing_broadcaster_message' => $tenantMessage = 'first testing']); + $tenant2 = Tenant::create(['testing_broadcaster_message' => $secondTenantMessage = 'second testing']); + + tenancy()->initialize($tenant); + $registerTestingBroadcaster(); + + expect(invade(app(BroadcastManager::class)->driver())->message)->toBe($tenantMessage); + + tenancy()->initialize($tenant2); + $registerTestingBroadcaster(); + + expect(invade(app(BroadcastManager::class)->driver())->message)->toBe($secondTenantMessage); + + tenancy()->end(); + $registerTestingBroadcaster(); + + expect(invade(app(BroadcastManager::class)->driver())->message)->toBe($defaultMessage); +}); + test('MailTenancyBootstrapper maps tenant mail credentials to config as specified in the $credentialsMap property and makes the mailer use tenant credentials', function() { MailTenancyBootstrapper::$credentialsMap = [ 'mail.mailers.smtp.username' => 'smtp_username', @@ -385,3 +468,48 @@ function getDiskPrefix(string $disk): string return $prefix; } + +test('url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one after tenancy ends', function() { + config(['tenancy.bootstrappers.url' => UrlTenancyBootstrapper::class]); + + Route::group([ + 'middleware' => InitializeTenancyBySubdomain::class, + ], function () { + Route::get('/', function () { + return true; + })->name('home'); + }); + + $baseUrl = url(route('home')); + config(['app.url' => $baseUrl]); + + $rootUrlOverride = function (Tenant $tenant) use ($baseUrl) { + $scheme = str($baseUrl)->before('://'); + $hostname = str($baseUrl)->after($scheme . '://'); + + return $scheme . '://' . $tenant->getTenantKey() . '.' . $hostname; + }; + + UrlTenancyBootstrapper::$rootUrlOverride = $rootUrlOverride; + + $tenant = Tenant::create(); + $tenantUrl = $rootUrlOverride($tenant); + + expect($tenantUrl)->not()->toBe($baseUrl); + + expect(url(route('home')))->toBe($baseUrl); + expect(URL::to('/'))->toBe($baseUrl); + expect(config('app.url'))->toBe($baseUrl); + + tenancy()->initialize($tenant); + + expect(url(route('home')))->toBe($tenantUrl); + expect(URL::to('/'))->toBe($tenantUrl); + expect(config('app.url'))->toBe($tenantUrl); + + tenancy()->end(); + + expect(url(route('home')))->toBe($baseUrl); + expect(URL::to('/'))->toBe($baseUrl); + expect(config('app.url'))->toBe($baseUrl); +}); diff --git a/tests/BroadcastingTest.php b/tests/BroadcastingTest.php new file mode 100644 index 00000000..aeb70de2 --- /dev/null +++ b/tests/BroadcastingTest.php @@ -0,0 +1,65 @@ + 'null']); + TenancyBroadcastManager::$tenantBroadcasters[] = 'null'; + + $originalBroadcaster = app(BroadcasterContract::class); + + tenancy()->initialize(Tenant::create()); + + // TenancyBroadcastManager binds new broadcaster + $tenantBroadcaster = app(BroadcastManager::class)->driver(); + + expect($tenantBroadcaster)->not()->toBe($originalBroadcaster); + + tenancy()->end(); + + expect($originalBroadcaster)->toBe(app(BroadcasterContract::class)); +}); + +test('new broadcasters get the channels from the previously bound broadcaster', function() { + config([ + 'broadcasting.default' => $driver = 'testing', + 'broadcasting.connections.testing.driver' => $driver, + ]); + + TenancyBroadcastManager::$tenantBroadcasters[] = $driver; + + $registerTestingBroadcaster = fn() => app(BroadcastManager::class)->extend('testing', fn($app, $config) => new TestingBroadcaster('testing')); + $getCurrentChannels = fn() => array_keys(invade(app(BroadcastManager::class)->driver())->channels); + + $registerTestingBroadcaster(); + Broadcast::channel($channel = 'testing-channel', fn() => true); + + expect($channel)->toBeIn($getCurrentChannels()); + + tenancy()->initialize(Tenant::create()); + $registerTestingBroadcaster(); + + expect($channel)->toBeIn($getCurrentChannels()); + + tenancy()->end(); + $registerTestingBroadcaster(); + + expect($channel)->toBeIn($getCurrentChannels()); +}); diff --git a/tests/Etc/TestingBroadcaster.php b/tests/Etc/TestingBroadcaster.php new file mode 100644 index 00000000..23efb74c --- /dev/null +++ b/tests/Etc/TestingBroadcaster.php @@ -0,0 +1,25 @@ +assertFalse($tenant->database()->manager()->databaseExists( $tenant->database()->getName() @@ -171,12 +171,13 @@ test('database is not migrated if creation is disabled', function () { })->toListener() ); - Tenant::create([ + $tenant = Tenant::create([ 'tenancy_create_database' => false, 'tenancy_db_name' => 'already_created', ]); - expect(pest()->hasFailed())->toBeFalse(); + // assert test didn't fail + $this->assertTrue($tenant->exists()); }); class FooListener extends QueueableListener diff --git a/tests/Features/ViteBundlerTest.php b/tests/Features/ViteBundlerTest.php new file mode 100644 index 00000000..0d4c9069 --- /dev/null +++ b/tests/Features/ViteBundlerTest.php @@ -0,0 +1,29 @@ +toBeInstanceOf(Vite::class); + expect($vite)->not()->toBeInstanceOf(StanclVite::class); + + config([ + 'tenancy.features' => [ViteBundler::class], + ]); + + $tenant = Tenant::create(); + + tenancy()->initialize($tenant); + + app()->forgetInstance(Vite::class); + + $vite = app(Vite::class); + + expect($vite)->toBeInstanceOf(StanclVite::class); +}); diff --git a/tests/TenantDatabaseManagerTest.php b/tests/TenantDatabaseManagerTest.php index 5d9a15d6..c776d7a1 100644 --- a/tests/TenantDatabaseManagerTest.php +++ b/tests/TenantDatabaseManagerTest.php @@ -302,7 +302,7 @@ test('database credentials can be provided to PermissionControlledMySQLDatabaseM $mysql2DB->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}';"); $mysql2DB->statement("GRANT ALL PRIVILEGES ON *.* TO `{$username}`@`%` identified by '{$password}' WITH GRANT OPTION;"); $mysql2DB->statement("FLUSH PRIVILEGES;"); - + DB::purge('mysql2'); // forget the mysql2 connection so that it uses the new credentials the next time config(['database.connections.mysql2.username' => $username]); @@ -347,7 +347,7 @@ test('tenant database can be created by using the username and password from ten $mysqlDB->statement("CREATE USER `{$username}`@`%` IDENTIFIED BY '{$password}';"); $mysqlDB->statement("GRANT ALL PRIVILEGES ON *.* TO `{$username}`@`%` identified by '{$password}' WITH GRANT OPTION;"); $mysqlDB->statement("FLUSH PRIVILEGES;"); - + DB::purge('mysql2'); // forget the mysql2 connection so that it uses the new credentials the next time // Remove `mysql` credentials to make sure we will be using the credentials from the tenant config diff --git a/tests/TestCase.php b/tests/TestCase.php index 60329d78..36d5fc9f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,16 +4,18 @@ declare(strict_types=1); namespace Stancl\Tenancy\Tests; -use Dotenv\Dotenv; -use Illuminate\Foundation\Application; -use Illuminate\Support\Facades\Redis; use PDO; -use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper; -use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper; -use Stancl\Tenancy\Facades\GlobalCache; +use Dotenv\Dotenv; use Stancl\Tenancy\Facades\Tenancy; -use Stancl\Tenancy\TenancyServiceProvider; use Stancl\Tenancy\Tests\Etc\Tenant; +use Illuminate\Support\Facades\Redis; +use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper; +use Illuminate\Foundation\Application; +use Stancl\Tenancy\Facades\GlobalCache; +use Stancl\Tenancy\TenancyServiceProvider; +use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper; +use Stancl\Tenancy\Bootstrappers\BroadcastTenancyBootstrapper; +use Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper; use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper; abstract class TestCase extends \Orchestra\Testbench\TestCase @@ -105,7 +107,9 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase '--force' => true, ], 'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class, // todo1 change this to []? two tests in TenantDatabaseManagerTest are failing with that + 'tenancy.bootstrappers.broadcast' => BroadcastTenancyBootstrapper::class, // todo1 change this to []? two tests in TenantDatabaseManagerTest are failing with that 'tenancy.bootstrappers.mail' => MailTenancyBootstrapper::class, + 'tenancy.bootstrappers.url' => UrlTenancyBootstrapper::class, 'queue.connections.central' => [ 'driver' => 'sync', 'central' => true, @@ -116,7 +120,9 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase $app->singleton(RedisTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration $app->singleton(PrefixCacheTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration + $app->singleton(BroadcastTenancyBootstrapper::class); $app->singleton(MailTenancyBootstrapper::class); + $app->singleton(UrlTenancyBootstrapper::class); } protected function getPackageProviders($app)