1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 15:34:03 +00:00

Add skip-failing option to the Migrate command (#945)

* Add and test Migrate command's skip-failing option

* Improve naming

* Move migration event dispatching inside try block

* Change test name

* Fix skip-failing test

* Use QueryException instead of Exception

* Correct TenantDatabaseDoesNotExistException import

* Correct test

* Check for the the testing env in DB bootstrapper

* Correct the Migrate command

* Fix code style (php-cs-fixer)

* add docs todo

* Add QueryException to the Migrat command try/catch

* Return status codes in Migrate

* Fix code style (php-cs-fixer)

* Add test for not stopping tenants:migrate after the first failure

* Update Migrate command

* Fix code style (php-cs-fixer)

* Fix code style (php-cs-fixer)

* Use `getTenants()`

* Use withtenantDatabases where needed

* Add withTenantDatabases to test

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
lukinovec 2023-02-01 06:55:26 +01:00 committed by GitHub
parent f741f44527
commit 342c67fe02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 20 deletions

View file

@ -25,7 +25,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
/** @var TenantWithDatabase $tenant */ /** @var TenantWithDatabase $tenant */
// Better debugging, but breaks cached lookup in prod // 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(); $database = $tenant->database()->getName();
if (! $tenant->database()->manager()->databaseExists($database)) { if (! $tenant->database()->manager()->databaseExists($database)) {
throw new TenantDatabaseDoesNotExistException($database); throw new TenantDatabaseDoesNotExistException($database);

View file

@ -7,9 +7,11 @@ namespace Stancl\Tenancy\Commands;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Console\Migrations\MigrateCommand; use Illuminate\Database\Console\Migrations\MigrateCommand;
use Illuminate\Database\Migrations\Migrator; use Illuminate\Database\Migrations\Migrator;
use Illuminate\Database\QueryException;
use Stancl\Tenancy\Concerns\DealsWithMigrations; use Stancl\Tenancy\Concerns\DealsWithMigrations;
use Stancl\Tenancy\Concerns\ExtendsLaravelCommand; use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
use Stancl\Tenancy\Concerns\HasTenantOptions; use Stancl\Tenancy\Concerns\HasTenantOptions;
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
use Stancl\Tenancy\Events\DatabaseMigrated; use Stancl\Tenancy\Events\DatabaseMigrated;
use Stancl\Tenancy\Events\MigratingDatabase; use Stancl\Tenancy\Events\MigratingDatabase;
@ -28,6 +30,8 @@ class Migrate extends MigrateCommand
{ {
parent::__construct($migrator, $dispatcher); parent::__construct($migrator, $dispatcher);
$this->addOption('skip-failing');
$this->specifyParameters(); $this->specifyParameters();
} }
@ -43,16 +47,23 @@ class Migrate extends MigrateCommand
return 1; return 1;
} }
tenancy()->runForMultiple($this->getTenants(), function ($tenant) { foreach ($this->getTenants() as $tenant) {
$this->components->info("Tenant: {$tenant->getTenantKey()}"); try {
$tenant->run(function ($tenant) {
$this->line("Tenant: {$tenant->getTenantKey()}");
event(new MigratingDatabase($tenant)); event(new MigratingDatabase($tenant));
// Migrate
parent::handle();
// Migrate event(new DatabaseMigrated($tenant));
parent::handle(); });
} catch (TenantDatabaseDoesNotExistException|QueryException $th) {
event(new DatabaseMigrated($tenant)); if (! $this->option('skip-failing')) {
}); throw $th;
}
}
}
return 0; return 0;
} }

View file

@ -50,6 +50,8 @@ test('context is switched when tenancy is reinitialized', function () {
}); });
test('central helper runs callbacks in the central state', function () { test('central helper runs callbacks in the central state', function () {
withTenantDatabases();
tenancy()->initialize($tenant = Tenant::create()); tenancy()->initialize($tenant = Tenant::create());
tenancy()->central(function () { 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 () { test('central helper returns the value from the callback', function () {
withTenantDatabases();
tenancy()->initialize(Tenant::create()); tenancy()->initialize(Tenant::create());
pest()->assertSame('foo', tenancy()->central(function () { 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 () { test('central helper reverts back to tenant context', function () {
withTenantDatabases();
tenancy()->initialize($tenant = Tenant::create()); tenancy()->initialize($tenant = Tenant::create());
tenancy()->central(function () { tenancy()->central(function () {

View file

@ -23,6 +23,8 @@ beforeEach(function () {
}); });
test('batch repository is set to tenant connection and reverted', function () { test('batch repository is set to tenant connection and reverted', function () {
withTenantDatabases();
$tenant = Tenant::create(); $tenant = Tenant::create();
$tenant2 = Tenant::create(); $tenant2 = Tenant::create();

View file

@ -18,11 +18,13 @@ use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Events\TenantDeleted; use Stancl\Tenancy\Events\TenantDeleted;
use Stancl\Tenancy\Tests\Etc\TestSeeder; use Stancl\Tenancy\Tests\Etc\TestSeeder;
use Stancl\Tenancy\Events\DeletingTenant; use Stancl\Tenancy\Events\DeletingTenant;
use Stancl\Tenancy\Events\DatabaseMigrated;
use Stancl\Tenancy\Tests\Etc\ExampleSeeder; use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
use Stancl\Tenancy\Events\TenancyInitialized; use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy; use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
beforeEach(function () { beforeEach(function () {
if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) { 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(); 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 () { test('dump command works', function () {
$tenant = Tenant::create(); $tenant = Tenant::create();
$schemaPath = 'tests/Etc/tenant-schema-test.dump'; $schemaPath = 'tests/Etc/tenant-schema-test.dump';

View file

@ -27,6 +27,8 @@ function assertMailerTransportUsesPassword(string|null $password) {
}; };
test('mailer transport uses the correct credentials', function() { test('mailer transport uses the correct credentials', function() {
withTenantDatabases();
config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'DEFAULT']); config(['mail.default' => 'smtp', 'mail.mailers.smtp.password' => $defaultPassword = 'DEFAULT']);
MailTenancyBootstrapper::$credentialsMap = ['mail.mailers.smtp.password' => 'smtp_password']; 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() { test('initializing and ending tenancy binds a fresh MailManager instance without cached mailers', function() {
withTenantDatabases();
$mailers = fn() => invade(app(MailManager::class))->mailers; $mailers = fn() => invade(app(MailManager::class))->mailers;
app(MailManager::class)->mailer('smtp'); app(MailManager::class)->mailer('smtp');

View file

@ -1,6 +1,10 @@
<?php <?php
use Stancl\Tenancy\Tests\TestCase; use Stancl\Tenancy\Tests\TestCase;
use Stancl\JobPipeline\JobPipeline;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Events\TenantCreated;
uses(TestCase::class)->in(__DIR__); uses(TestCase::class)->in(__DIR__);
@ -8,3 +12,10 @@ function pest(): TestCase
{ {
return Pest\TestSuite::getInstance()->test; return Pest\TestSuite::getInstance()->test;
} }
function withTenantDatabases()
{
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
}

View file

@ -3,23 +3,23 @@
declare(strict_types=1); declare(strict_types=1);
use Illuminate\Bus\Queueable; 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 Spatie\Valuestore\Valuestore;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Tests\Etc\User; use Stancl\Tenancy\Tests\Etc\User;
use Stancl\JobPipeline\JobPipeline; use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Tests\Etc\Tenant; use Stancl\Tenancy\Tests\Etc\Tenant;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Events\TenancyEnded; use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Jobs\CreateDatabase; use Stancl\Tenancy\Jobs\CreateDatabase;
use Illuminate\Queue\InteractsWithQueue;
use Stancl\Tenancy\Events\TenantCreated; use Stancl\Tenancy\Events\TenantCreated;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing; use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Stancl\Tenancy\Events\TenancyInitialized; use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy; use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Listeners\RevertToCentralContext;
@ -48,6 +48,8 @@ afterEach(function () {
}); });
test('tenant id is passed to tenant queues', function () { test('tenant id is passed to tenant queues', function () {
withTenantDatabases();
config(['queue.default' => 'sync']); config(['queue.default' => 'sync']);
$tenant = Tenant::create(); $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 () { test('tenant id is not passed to central queues', function () {
withTenantDatabases();
$tenant = Tenant::create(); $tenant = Tenant::create();
tenancy()->initialize($tenant); tenancy()->initialize($tenant);
@ -156,6 +160,8 @@ test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenan
})->with([true, false]); })->with([true, false]);
test('the tenant used by the job doesnt change when the current tenant changes', function () { test('the tenant used by the job doesnt change when the current tenant changes', function () {
withTenantDatabases();
$tenant1 = Tenant::create([ $tenant1 = Tenant::create([
'id' => 'acme', '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 class TestJob implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;