mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 15:54: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:
parent
f741f44527
commit
342c67fe02
8 changed files with 95 additions and 20 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Migrate
|
||||||
parent::handle();
|
parent::handle();
|
||||||
|
|
||||||
event(new DatabaseMigrated($tenant));
|
event(new DatabaseMigrated($tenant));
|
||||||
});
|
});
|
||||||
|
} catch (TenantDatabaseDoesNotExistException|QueryException $th) {
|
||||||
|
if (! $this->option('skip-failing')) {
|
||||||
|
throw $th;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue