diff --git a/src/Bootstrappers/DatabaseTenancyBootstrapper.php b/src/Bootstrappers/DatabaseTenancyBootstrapper.php index c6dba079..f058dc43 100644 --- a/src/Bootstrappers/DatabaseTenancyBootstrapper.php +++ b/src/Bootstrappers/DatabaseTenancyBootstrapper.php @@ -25,7 +25,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper /** @var TenantWithDatabase $tenant */ // Better debugging, but breaks cached lookup in prod - if (app()->environment('local')) { + if (app()->environment('local') || app()->environment('testing')) { // todo@docs mention this change in v4 upgrade guide https://github.com/archtechx/tenancy/pull/945#issuecomment-1268206149 $database = $tenant->database()->getName(); if (! $tenant->database()->manager()->databaseExists($database)) { throw new TenantDatabaseDoesNotExistException($database); diff --git a/src/Commands/Migrate.php b/src/Commands/Migrate.php index 0d2fceaa..47b95bd2 100644 --- a/src/Commands/Migrate.php +++ b/src/Commands/Migrate.php @@ -7,9 +7,11 @@ namespace Stancl\Tenancy\Commands; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Console\Migrations\MigrateCommand; use Illuminate\Database\Migrations\Migrator; +use Illuminate\Database\QueryException; use Stancl\Tenancy\Concerns\DealsWithMigrations; use Stancl\Tenancy\Concerns\ExtendsLaravelCommand; use Stancl\Tenancy\Concerns\HasTenantOptions; +use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException; use Stancl\Tenancy\Events\DatabaseMigrated; use Stancl\Tenancy\Events\MigratingDatabase; @@ -28,6 +30,8 @@ class Migrate extends MigrateCommand { parent::__construct($migrator, $dispatcher); + $this->addOption('skip-failing'); + $this->specifyParameters(); } @@ -43,16 +47,23 @@ class Migrate extends MigrateCommand return 1; } - tenancy()->runForMultiple($this->getTenants(), function ($tenant) { - $this->components->info("Tenant: {$tenant->getTenantKey()}"); + foreach ($this->getTenants() as $tenant) { + try { + $tenant->run(function ($tenant) { + $this->line("Tenant: {$tenant->getTenantKey()}"); - event(new MigratingDatabase($tenant)); + event(new MigratingDatabase($tenant)); + // Migrate + parent::handle(); - // Migrate - parent::handle(); - - event(new DatabaseMigrated($tenant)); - }); + event(new DatabaseMigrated($tenant)); + }); + } catch (TenantDatabaseDoesNotExistException|QueryException $th) { + if (! $this->option('skip-failing')) { + throw $th; + } + } + } return 0; } diff --git a/tests/AutomaticModeTest.php b/tests/AutomaticModeTest.php index fc740fc1..1a0948ea 100644 --- a/tests/AutomaticModeTest.php +++ b/tests/AutomaticModeTest.php @@ -50,6 +50,8 @@ test('context is switched when tenancy is reinitialized', function () { }); test('central helper runs callbacks in the central state', function () { + withTenantDatabases(); + tenancy()->initialize($tenant = Tenant::create()); tenancy()->central(function () { @@ -60,6 +62,8 @@ test('central helper runs callbacks in the central state', function () { }); test('central helper returns the value from the callback', function () { + withTenantDatabases(); + tenancy()->initialize(Tenant::create()); pest()->assertSame('foo', tenancy()->central(function () { @@ -68,6 +72,8 @@ test('central helper returns the value from the callback', function () { }); test('central helper reverts back to tenant context', function () { + withTenantDatabases(); + tenancy()->initialize($tenant = Tenant::create()); tenancy()->central(function () { diff --git a/tests/BatchTest.php b/tests/BatchTest.php index 629a4e61..24cb7c59 100644 --- a/tests/BatchTest.php +++ b/tests/BatchTest.php @@ -23,6 +23,8 @@ beforeEach(function () { }); test('batch repository is set to tenant connection and reverted', function () { + withTenantDatabases(); + $tenant = Tenant::create(); $tenant2 = Tenant::create(); diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 444830d1..e5da16b7 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -18,11 +18,13 @@ use Stancl\Tenancy\Events\TenantCreated; use Stancl\Tenancy\Events\TenantDeleted; use Stancl\Tenancy\Tests\Etc\TestSeeder; use Stancl\Tenancy\Events\DeletingTenant; +use Stancl\Tenancy\Events\DatabaseMigrated; use Stancl\Tenancy\Tests\Etc\ExampleSeeder; use Stancl\Tenancy\Events\TenancyInitialized; use Stancl\Tenancy\Listeners\BootstrapTenancy; use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; +use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException; beforeEach(function () { if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) { @@ -109,6 +111,46 @@ test('migrate command loads schema state', function () { expect(Schema::hasTable('users'))->toBeTrue(); }); +test('migrate command only throws exceptions if skip-failing is not passed', function() { + Tenant::create(); + + $tenantWithoutDatabase = Tenant::create(); + $databaseToDrop = $tenantWithoutDatabase->run(fn() => DB::connection()->getDatabaseName()); + + DB::statement("DROP DATABASE `$databaseToDrop`"); + + Tenant::create(); + + expect(fn() => pest()->artisan('tenants:migrate --schema-path="tests/Etc/tenant-schema.dump"'))->toThrow(TenantDatabaseDoesNotExistException::class); + expect(fn() => pest()->artisan('tenants:migrate --schema-path="tests/Etc/tenant-schema.dump" --skip-failing'))->not()->toThrow(TenantDatabaseDoesNotExistException::class); +}); + +test('migrate command does not stop after the first failure if skip-failing is passed', function() { + $tenants = collect([ + Tenant::create(), + $tenantWithoutDatabase = Tenant::create(), + Tenant::create(), + ]); + + $migratedTenants = 0; + + Event::listen(DatabaseMigrated::class, function() use (&$migratedTenants) { + $migratedTenants++; + }); + + $databaseToDrop = $tenantWithoutDatabase->run(fn() => DB::connection()->getDatabaseName()); + + DB::statement("DROP DATABASE `$databaseToDrop`"); + + Artisan::call('tenants:migrate', [ + '--schema-path' => '"tests/Etc/tenant-schema.dump"', + '--skip-failing' => true, + '--tenants' => $tenants->pluck('id')->toArray(), + ]); + + expect($migratedTenants)->toBe(2); +}); + test('dump command works', function () { $tenant = Tenant::create(); $schemaPath = 'tests/Etc/tenant-schema-test.dump'; diff --git a/tests/MailTest.php b/tests/MailTest.php index 544fda1b..c530b7e8 100644 --- a/tests/MailTest.php +++ b/tests/MailTest.php @@ -27,6 +27,8 @@ function assertMailerTransportUsesPassword(string|null $password) { }; test('mailer transport uses the correct credentials', function() { + withTenantDatabases(); + config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'DEFAULT']); MailTenancyBootstrapper::$credentialsMap = ['mail.mailers.smtp.password' => 'smtp_password']; @@ -52,6 +54,8 @@ test('mailer transport uses the correct credentials', function() { test('initializing and ending tenancy binds a fresh MailManager instance without cached mailers', function() { + withTenantDatabases(); + $mailers = fn() => invade(app(MailManager::class))->mailers; app(MailManager::class)->mailer('smtp'); diff --git a/tests/Pest.php b/tests/Pest.php index d7ca8c22..5380da0a 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,6 +1,10 @@ in(__DIR__); @@ -8,3 +12,10 @@ function pest(): TestCase { return Pest\TestSuite::getInstance()->test; } + +function withTenantDatabases() +{ + Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) { + return $event->tenant; + })->toListener()); +} diff --git a/tests/QueueTest.php b/tests/QueueTest.php index c1fa24b8..f88b3934 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -3,23 +3,23 @@ declare(strict_types=1); use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; use Spatie\Valuestore\Valuestore; use Illuminate\Support\Facades\DB; use Stancl\Tenancy\Tests\Etc\User; use Stancl\JobPipeline\JobPipeline; use Stancl\Tenancy\Tests\Etc\Tenant; use Illuminate\Support\Facades\Event; +use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Schema; use Stancl\Tenancy\Events\TenancyEnded; use Stancl\Tenancy\Jobs\CreateDatabase; +use Illuminate\Queue\InteractsWithQueue; use Stancl\Tenancy\Events\TenantCreated; use Illuminate\Database\Schema\Blueprint; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; use Stancl\Tenancy\Events\TenancyInitialized; use Stancl\Tenancy\Listeners\BootstrapTenancy; use Stancl\Tenancy\Listeners\RevertToCentralContext; @@ -48,6 +48,8 @@ afterEach(function () { }); test('tenant id is passed to tenant queues', function () { + withTenantDatabases(); + config(['queue.default' => 'sync']); $tenant = Tenant::create(); @@ -64,6 +66,8 @@ test('tenant id is passed to tenant queues', function () { }); test('tenant id is not passed to central queues', function () { + withTenantDatabases(); + $tenant = Tenant::create(); tenancy()->initialize($tenant); @@ -156,6 +160,8 @@ test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenan })->with([true, false]); test('the tenant used by the job doesnt change when the current tenant changes', function () { + withTenantDatabases(); + $tenant1 = Tenant::create([ 'id' => 'acme', ]); @@ -217,13 +223,6 @@ function withUsers() }); } -function withTenantDatabases() -{ - Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) { - return $event->tenant; - })->toListener()); -} - class TestJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;