-
-
Notifications
You must be signed in to change notification settings - Fork 486
[4.x] Create pending tenants with pending_since, improve --with-pending #1458
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
950ff0f
6b4d22b
b592d3d
66114e6
f309dcc
0bb112d
e3673f5
e81e6ac
4764b99
0c463ea
5ab14fd
b7bd439
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,10 +16,25 @@ | |
| use Stancl\Tenancy\Events\PullingPendingTenant; | ||
| use Stancl\Tenancy\Tests\Etc\Tenant; | ||
| use function Stancl\Tenancy\Tests\pest; | ||
| use Stancl\Tenancy\Events\TenantCreated; | ||
| use Stancl\JobPipeline\JobPipeline; | ||
| use Stancl\Tenancy\Jobs\CreateDatabase; | ||
| use Stancl\Tenancy\Jobs\MigrateDatabase; | ||
| use Stancl\Tenancy\Jobs\SeedDatabase; | ||
| use Stancl\Tenancy\Tests\Etc\User; | ||
| use Stancl\Tenancy\Tests\Etc\TestSeeder; | ||
| use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; | ||
| use Stancl\Tenancy\Events\TenancyInitialized; | ||
| use Stancl\Tenancy\Listeners\BootstrapTenancy; | ||
| use Stancl\Tenancy\Events\TenancyEnded; | ||
| use Stancl\Tenancy\Listeners\RevertToCentralContext; | ||
|
|
||
| beforeEach($cleanup = function () { | ||
| Tenant::$extraCustomColumns = []; | ||
| Tenant::$getPendingAttributesUsing = null; | ||
|
|
||
| MigrateDatabase::$includePending = true; | ||
| SeedDatabase::$includePending = true; | ||
| }); | ||
|
|
||
| afterEach($cleanup); | ||
|
|
@@ -154,8 +169,8 @@ | |
| Event::assertDispatched(PendingTenantPulled::class); | ||
| }); | ||
|
|
||
| test('commands do not run for pending tenants if tenancy.pending.include_in_queries is false and the with pending option does not get passed', function() { | ||
| config(['tenancy.pending.include_in_queries' => false]); | ||
| test('commands include tenants based on the include_in_queries config when --with-pending is not passed', function (bool $includeInQueries) { | ||
| config(['tenancy.pending.include_in_queries' => $includeInQueries]); | ||
|
|
||
| $tenants = collect([ | ||
| Tenant::create(), | ||
|
|
@@ -164,21 +179,21 @@ | |
| Tenant::createPending(), | ||
| ]); | ||
|
|
||
| pest()->artisan('tenants:migrate --with-pending'); | ||
|
|
||
| $artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz'"); | ||
| $command = pest()->artisan("tenants:run 'bar testing testing@test.test password foo'"); | ||
|
|
||
| $pendingTenants = $tenants->filter->pending(); | ||
| $readyTenants = $tenants->reject->pending(); | ||
|
|
||
| $pendingTenants->each(fn ($tenant) => $artisan->doesntExpectOutputToContain("Tenant: {$tenant->getTenantKey()}")); | ||
| $readyTenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); | ||
| $tenants->each(function ($tenant) use ($command, $includeInQueries) { | ||
| if ($tenant->pending() && ! $includeInQueries) { | ||
| $command->doesntExpectOutputToContain("Tenant: {$tenant->getTenantKey()}"); | ||
| } else { | ||
| $command->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}"); | ||
| } | ||
| }); | ||
|
|
||
| $artisan->assertExitCode(0); | ||
| }); | ||
| $command->assertSuccessful(); | ||
| })->with([true, false]); | ||
|
|
||
| test('commands run for pending tenants too if tenancy.pending.include_in_queries is true', function() { | ||
| config(['tenancy.pending.include_in_queries' => true]); | ||
| test('commands include pending tenants when truthy --with-pending is passed', function (bool $includeInQueries) { | ||
| config(['tenancy.pending.include_in_queries' => $includeInQueries]); | ||
|
|
||
| $tenants = collect([ | ||
| Tenant::create(), | ||
|
|
@@ -187,17 +202,22 @@ | |
| Tenant::createPending(), | ||
| ]); | ||
|
|
||
| pest()->artisan('tenants:migrate --with-pending'); | ||
| foreach ([ | ||
| '--with-pending', | ||
| '--with-pending=true', | ||
| '--with-pending=1' | ||
| ] as $option) { | ||
| $command = pest()->artisan("tenants:run 'bar testing testing@test.test password foo' {$option}"); | ||
|
|
||
| $artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz'"); | ||
| // Pending tenants are included regardless of tenancy.pending.include_in_queries | ||
| $tenants->each(fn ($tenant) => $command->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); | ||
|
|
||
| $tenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); | ||
|
|
||
| $artisan->assertExitCode(0); | ||
| }); | ||
| $command->assertSuccessful(); | ||
| } | ||
| })->with([true, false]); | ||
|
|
||
| test('commands run for pending tenants too if the with pending option is passed', function() { | ||
| config(['tenancy.pending.include_in_queries' => false]); | ||
| test('commands exclude pending tenants when falsy --with-pending is passed', function (bool $includeInQueries) { | ||
| config(['tenancy.pending.include_in_queries' => $includeInQueries]); | ||
|
|
||
| $tenants = collect([ | ||
| Tenant::create(), | ||
|
|
@@ -206,14 +226,25 @@ | |
| Tenant::createPending(), | ||
| ]); | ||
|
|
||
| pest()->artisan('tenants:migrate --with-pending'); | ||
|
|
||
| $artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz' --with-pending"); | ||
|
|
||
| $tenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); | ||
|
|
||
| $artisan->assertExitCode(0); | ||
| }); | ||
| foreach ([ | ||
| '--with-pending=false', | ||
| '--with-pending=0', | ||
| '--with-pending=foo' // Invalid values are treated as false | ||
| ] as $option) { | ||
| $command = pest()->artisan("tenants:run 'bar testing testing@test.test password foo' {$option}"); | ||
|
|
||
| $tenants->each(function ($tenant) use ($command) { | ||
| if ($tenant->pending()) { | ||
| // Pending tenants are excluded regardless of tenancy.pending.include_in_queries | ||
| $command->doesntExpectOutputToContain("Tenant: {$tenant->getTenantKey()}"); | ||
| } else { | ||
| $command->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}"); | ||
| } | ||
| }); | ||
|
|
||
| $command->assertSuccessful(); | ||
| } | ||
| })->with([true, false]); | ||
|
|
||
| test('pending tenants can have default attributes for non-nullable columns', function (bool $withPendingAttributes) { | ||
| Schema::table('tenants', function (Blueprint $table) { | ||
|
|
@@ -236,3 +267,105 @@ | |
| else | ||
| expect($fn)->toThrow(QueryException::class); | ||
| })->with([true, false]); | ||
|
|
||
| test('pending tenant databases can be migrated using a job unless configured otherwise', function (bool $includeInQueries, ?bool $migrateWithPending) { | ||
| config([ | ||
| 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], | ||
| 'tenancy.pending.include_in_queries' => $includeInQueries, | ||
| ]); | ||
|
|
||
| MigrateDatabase::$includePending = $migrateWithPending; | ||
|
|
||
| Event::listen(TenancyInitialized::class, BootstrapTenancy::class); | ||
| Event::listen(TenancyEnded::class, RevertToCentralContext::class); | ||
| Event::listen(TenantCreated::class, JobPipeline::make([ | ||
| CreateDatabase::class, | ||
| MigrateDatabase::class, | ||
| ])->send(function (TenantCreated $event) { | ||
| return $event->tenant; | ||
| })->toListener()); | ||
|
|
||
| $pendingTenant = Tenant::createPending(); | ||
|
|
||
| expect(Schema::hasTable('users'))->toBeFalse(); | ||
|
|
||
| tenancy()->initialize($pendingTenant); | ||
|
|
||
| // MigrateDatabase includes/excludes pending tenants based on its $includePending property, | ||
| // regardless of the tenancy.pending.include_in_queries config. | ||
| expect(Schema::hasTable('users'))->toBe($migrateWithPending ?? $includeInQueries); | ||
| })->with([ | ||
| 'include pending in queries' => [true], | ||
| 'exclude pending from queries' => [false], | ||
| ])->with([ | ||
| 'migrate with pending' => [true], | ||
| 'migrate without pending' => [false], | ||
| 'default to config' => [null], | ||
| ]); | ||
|
|
||
| test('pending tenant databases can be seeded using a job unless configured otherwise', function (bool $includeInQueries, ?bool $seedWithPending) { | ||
| config([ | ||
| 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], | ||
| 'tenancy.pending.include_in_queries' => $includeInQueries, | ||
| 'tenancy.seeder_parameters.--class' => TestSeeder::class, | ||
| ]); | ||
|
|
||
| MigrateDatabase::$includePending = true; | ||
| SeedDatabase::$includePending = $seedWithPending; | ||
|
|
||
| Event::listen(TenancyInitialized::class, BootstrapTenancy::class); | ||
| Event::listen(TenancyEnded::class, RevertToCentralContext::class); | ||
| Event::listen(TenantCreated::class, JobPipeline::make([ | ||
| CreateDatabase::class, | ||
| MigrateDatabase::class, | ||
| SeedDatabase::class, | ||
| ])->send(function (TenantCreated $event) { | ||
| return $event->tenant; | ||
| })->toListener()); | ||
|
|
||
| $pendingTenant = Tenant::createPending(); | ||
|
|
||
| tenancy()->initialize($pendingTenant); | ||
|
|
||
| // SeedDatabase includes/excludes pending tenants based on its $includePending property, | ||
| // regardless of the tenancy.pending.include_in_queries config. | ||
| expect(User::where('email', 'seeded@user')->exists())->toBe($seedWithPending ?? $includeInQueries); | ||
| })->with([ | ||
| 'include pending in queries' => [true], | ||
| 'exclude pending from queries' => [false], | ||
| ])->with([ | ||
| 'seed with pending' => [true], | ||
| 'seed without pending' => [false], | ||
| 'default to config' => [null], | ||
| ]); | ||
|
|
||
| test('jobs that run before tenants get fully created recognize pending tenants', function () { | ||
| config([ | ||
| 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], | ||
| ]); | ||
|
|
||
| Event::listen(TenancyInitialized::class, BootstrapTenancy::class); | ||
| Event::listen(TenancyEnded::class, RevertToCentralContext::class); | ||
| Event::listen(TenantCreated::class, JobPipeline::make([ | ||
| CreateDatabase::class, | ||
| PendingTenantJob::class, | ||
| ])->send(function (TenantCreated $event) { | ||
|
Comment on lines
+349
to
+352
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR description talks about:
But the added test uses a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying this test on On this branch it passes with both this code as well as Event::listen(TenantCreated::class, JobPipeline::make([
CreateDatabase::class,
])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
Event::listen(DatabaseCreated::class, JobPipeline::make([
PendingTenantJob::class,
])->send(function (DatabaseCreated $event) {
return $event->tenant;
})->toListener());So we can probably merge it like this since it's simpler. Just keeping this review here to note the difference in tests and PR desc. |
||
| return $event->tenant; | ||
| })->toListener()); | ||
|
|
||
| Tenant::createPending(); | ||
|
|
||
| expect(app('tenant_is_pending'))->toBeTrue(); | ||
| }); | ||
|
|
||
| class PendingTenantJob | ||
| { | ||
| public function __construct( | ||
| public Tenant $tenant, | ||
| ) {} | ||
|
|
||
| public function handle() | ||
| { | ||
| app()->instance('tenant_is_pending', $this->tenant->pending()); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.