Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Commands/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class Run extends Command
protected $description = 'Run a command for tenant(s)';

protected $signature = 'tenants:run {commandname : The artisan command.}
{--tenants=* : The tenant(s) to run the command for. Default: all}';
{--tenants=* : The tenant(s) to run the command for. Default: all}
{--skip-tenants=* : The tenant(s) to skip}';

public function handle(): int
{
Expand Down
10 changes: 7 additions & 3 deletions src/Concerns/HasTenantOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@
use Symfony\Component\Console\Input\InputOption;

/**
* Adds 'tenants' and 'with-pending' options.
* Adds 'tenants', 'skip-tenants', and 'with-pending' options.
*/
trait HasTenantOptions
{
protected function getOptions()
{
return array_merge([
new InputOption('tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to run this command for. Leave empty for all tenants', null),
new InputOption('with-pending', null, InputOption::VALUE_NONE, 'Include pending tenants in query'), // todo@pending should we also offer without-pending? if we add this, mention in docs
new InputOption('tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to run this command for. Leave empty for all tenants', null),
new InputOption('skip-tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to skip when running this command', null),
new InputOption('with-pending', null, InputOption::VALUE_NONE, 'Include pending tenants in query'), // todo@pending should we also offer without-pending? if we add this, mention in docs
], parent::getOptions());
}

Expand All @@ -42,6 +43,9 @@ protected function getTenantsQuery(?array $tenantKeys = null): Builder
->when($this->option('tenants'), function ($query) {
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
})
->when($this->option('skip-tenants'), function ($query) {
$query->whereNotIn(tenancy()->model()->getTenantKeyName(), $this->option('skip-tenants'));
})
->when(tenancy()->model()::hasGlobalScope(PendingScope::class), function ($query) {
$query->withPending(config('tenancy.pending.include_in_queries') ?: $this->option('with-pending'));
});
Expand Down
48 changes: 48 additions & 0 deletions tests/CommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,51 @@
expect($tenantHasDatabase($tenant))->toBe($shouldHaveDBAfterMigrateFresh);
}
})->with([true, false]);

test('migrate commands can skip specified tenants', function (string $command) {
$tenant1 = Tenant::create();
$tenant2 = Tenant::create();
$tenant3 = Tenant::create();

pest()->artisan("{$command} --skip-tenants={$tenant1->getTenantKey()} --skip-tenants={$tenant2->getTenantKey()}");

tenancy()->initialize($tenant1);

expect(Schema::hasTable('users'))->toBeFalse();

tenancy()->initialize($tenant2);

expect(Schema::hasTable('users'))->toBeFalse();

tenancy()->initialize($tenant3);

expect(Schema::hasTable('users'))->toBeTrue();
})->with([
'tenants:migrate',
'tenants:migrate-fresh',
]);
Comment on lines +519 to +540
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing exit-code assertion on the artisan() call.

Unlike the other two new tests (lines 551, 564), this pest()->artisan(...) call has no ->assertExitCode(0) (or ->assertSuccessful()). A command crash would go undetected if the schema assertions still happen to pass.

✅ Proposed fix
-    pest()->artisan("{$command} --skip-tenants={$tenant1->getTenantKey()} --skip-tenants={$tenant2->getTenantKey()}");
+    pest()->artisan("{$command} --skip-tenants={$tenant1->getTenantKey()} --skip-tenants={$tenant2->getTenantKey()}")
+        ->assertExitCode(0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/CommandsTest.php` around lines 519 - 540, The pest()->artisan(...)
invocation in the test closure (the migrate commands test that creates
tenant1/2/3) lacks an exit-code assertion; modify the call to chain an assertion
(for example ->assertExitCode(0) or ->assertSuccessful()) onto the
pest()->artisan(...) expression so the test fails if the artisan command crashes
before the subsequent tenancy/schema expectations run.


test('run command can skip specified tenants', function () {
$tenant1 = Tenant::create()->getTenantKey();
$tenant2 = Tenant::create()->getTenantKey();
$tenant3 = Tenant::create()->getTenantKey();

pest()->artisan("tenants:run --skip-tenants=$tenant1 --skip-tenants=$tenant2 'bar foo foo@bar foobar arg --option=option'")
->doesntExpectOutputToContain("Tenant: $tenant1")
->doesntExpectOutputToContain("Tenant: $tenant2")
->expectsOutputToContain("Tenant: $tenant3")
->assertExitCode(0);
});

test('tenants and skip-tenants options can be used together', function () {
$tenant1 = Tenant::create()->getTenantKey();
$tenant2 = Tenant::create()->getTenantKey();
$tenant3 = Tenant::create()->getTenantKey();

// Scope to tenant1+tenant2, then skip tenant2 — only tenant1 should run
pest()->artisan("tenants:run --tenants=$tenant1 --tenants=$tenant2 --skip-tenants=$tenant2 'bar foo foo@bar foobar arg --option=option'")
->expectsOutputToContain("Tenant: $tenant1")
->doesntExpectOutputToContain("Tenant: $tenant2")
->doesntExpectOutputToContain("Tenant: $tenant3")
->assertExitCode(0);
});
Loading