|
16 | 16 | use Stancl\Tenancy\Events\PullingPendingTenant; |
17 | 17 | use Stancl\Tenancy\Tests\Etc\Tenant; |
18 | 18 | use function Stancl\Tenancy\Tests\pest; |
| 19 | +use Stancl\Tenancy\Events\TenantCreated; |
| 20 | +use Stancl\JobPipeline\JobPipeline; |
| 21 | +use Stancl\Tenancy\Jobs\CreateDatabase; |
| 22 | +use Stancl\Tenancy\Jobs\MigrateDatabase; |
| 23 | +use Stancl\Tenancy\Jobs\SeedDatabase; |
| 24 | +use Stancl\Tenancy\Tests\Etc\User; |
| 25 | +use Stancl\Tenancy\Tests\Etc\TestSeeder; |
| 26 | +use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; |
| 27 | +use Stancl\Tenancy\Events\TenancyInitialized; |
| 28 | +use Stancl\Tenancy\Listeners\BootstrapTenancy; |
| 29 | +use Stancl\Tenancy\Events\TenancyEnded; |
| 30 | +use Stancl\Tenancy\Listeners\RevertToCentralContext; |
19 | 31 |
|
20 | 32 | beforeEach($cleanup = function () { |
21 | 33 | Tenant::$extraCustomColumns = []; |
22 | 34 | Tenant::$getPendingAttributesUsing = null; |
| 35 | + |
| 36 | + MigrateDatabase::$includePending = true; |
| 37 | + SeedDatabase::$includePending = true; |
23 | 38 | }); |
24 | 39 |
|
25 | 40 | afterEach($cleanup); |
|
154 | 169 | Event::assertDispatched(PendingTenantPulled::class); |
155 | 170 | }); |
156 | 171 |
|
157 | | -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() { |
158 | | - config(['tenancy.pending.include_in_queries' => false]); |
| 172 | +test('commands include tenants based on the include_in_queries config when --with-pending is not passed', function (bool $includeInQueries) { |
| 173 | + config(['tenancy.pending.include_in_queries' => $includeInQueries]); |
159 | 174 |
|
160 | 175 | $tenants = collect([ |
161 | 176 | Tenant::create(), |
|
164 | 179 | Tenant::createPending(), |
165 | 180 | ]); |
166 | 181 |
|
167 | | - pest()->artisan('tenants:migrate --with-pending'); |
168 | | - |
169 | | - $artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz'"); |
| 182 | + $command = pest()->artisan("tenants:run 'bar testing testing@test.test password foo'"); |
170 | 183 |
|
171 | | - $pendingTenants = $tenants->filter->pending(); |
172 | | - $readyTenants = $tenants->reject->pending(); |
173 | | - |
174 | | - $pendingTenants->each(fn ($tenant) => $artisan->doesntExpectOutputToContain("Tenant: {$tenant->getTenantKey()}")); |
175 | | - $readyTenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); |
| 184 | + $tenants->each(function ($tenant) use ($command, $includeInQueries) { |
| 185 | + if ($tenant->pending() && ! $includeInQueries) { |
| 186 | + $command->doesntExpectOutputToContain("Tenant: {$tenant->getTenantKey()}"); |
| 187 | + } else { |
| 188 | + $command->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}"); |
| 189 | + } |
| 190 | + }); |
176 | 191 |
|
177 | | - $artisan->assertExitCode(0); |
178 | | -}); |
| 192 | + $command->assertSuccessful(); |
| 193 | +})->with([true, false]); |
179 | 194 |
|
180 | | -test('commands run for pending tenants too if tenancy.pending.include_in_queries is true', function() { |
181 | | - config(['tenancy.pending.include_in_queries' => true]); |
| 195 | +test('commands include pending tenants when truthy --with-pending is passed', function (bool $includeInQueries) { |
| 196 | + config(['tenancy.pending.include_in_queries' => $includeInQueries]); |
182 | 197 |
|
183 | 198 | $tenants = collect([ |
184 | 199 | Tenant::create(), |
|
187 | 202 | Tenant::createPending(), |
188 | 203 | ]); |
189 | 204 |
|
190 | | - pest()->artisan('tenants:migrate --with-pending'); |
| 205 | + foreach ([ |
| 206 | + '--with-pending', |
| 207 | + '--with-pending=true', |
| 208 | + '--with-pending=1' |
| 209 | + ] as $option) { |
| 210 | + $command = pest()->artisan("tenants:run 'bar testing testing@test.test password foo' {$option}"); |
191 | 211 |
|
192 | | - $artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz'"); |
| 212 | + // Pending tenants are included regardless of tenancy.pending.include_in_queries |
| 213 | + $tenants->each(fn ($tenant) => $command->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); |
193 | 214 |
|
194 | | - $tenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); |
195 | | - |
196 | | - $artisan->assertExitCode(0); |
197 | | -}); |
| 215 | + $command->assertSuccessful(); |
| 216 | + } |
| 217 | +})->with([true, false]); |
198 | 218 |
|
199 | | -test('commands run for pending tenants too if the with pending option is passed', function() { |
200 | | - config(['tenancy.pending.include_in_queries' => false]); |
| 219 | +test('commands exclude pending tenants when falsy --with-pending is passed', function (bool $includeInQueries) { |
| 220 | + config(['tenancy.pending.include_in_queries' => $includeInQueries]); |
201 | 221 |
|
202 | 222 | $tenants = collect([ |
203 | 223 | Tenant::create(), |
|
206 | 226 | Tenant::createPending(), |
207 | 227 | ]); |
208 | 228 |
|
209 | | - pest()->artisan('tenants:migrate --with-pending'); |
210 | | - |
211 | | - $artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz' --with-pending"); |
212 | | - |
213 | | - $tenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}")); |
214 | | - |
215 | | - $artisan->assertExitCode(0); |
216 | | -}); |
| 229 | + foreach ([ |
| 230 | + '--with-pending=false', |
| 231 | + '--with-pending=0', |
| 232 | + '--with-pending=foo' // Invalid values are treated as false |
| 233 | + ] as $option) { |
| 234 | + $command = pest()->artisan("tenants:run 'bar testing testing@test.test password foo' {$option}"); |
| 235 | + |
| 236 | + $tenants->each(function ($tenant) use ($command) { |
| 237 | + if ($tenant->pending()) { |
| 238 | + // Pending tenants are excluded regardless of tenancy.pending.include_in_queries |
| 239 | + $command->doesntExpectOutputToContain("Tenant: {$tenant->getTenantKey()}"); |
| 240 | + } else { |
| 241 | + $command->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}"); |
| 242 | + } |
| 243 | + }); |
| 244 | + |
| 245 | + $command->assertSuccessful(); |
| 246 | + } |
| 247 | +})->with([true, false]); |
217 | 248 |
|
218 | 249 | test('pending tenants can have default attributes for non-nullable columns', function (bool $withPendingAttributes) { |
219 | 250 | Schema::table('tenants', function (Blueprint $table) { |
|
236 | 267 | else |
237 | 268 | expect($fn)->toThrow(QueryException::class); |
238 | 269 | })->with([true, false]); |
| 270 | + |
| 271 | +test('pending tenant databases can be migrated using a job unless configured otherwise', function (bool $includeInQueries, ?bool $migrateWithPending) { |
| 272 | + config([ |
| 273 | + 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], |
| 274 | + 'tenancy.pending.include_in_queries' => $includeInQueries, |
| 275 | + ]); |
| 276 | + |
| 277 | + MigrateDatabase::$includePending = $migrateWithPending; |
| 278 | + |
| 279 | + Event::listen(TenancyInitialized::class, BootstrapTenancy::class); |
| 280 | + Event::listen(TenancyEnded::class, RevertToCentralContext::class); |
| 281 | + Event::listen(TenantCreated::class, JobPipeline::make([ |
| 282 | + CreateDatabase::class, |
| 283 | + MigrateDatabase::class, |
| 284 | + ])->send(function (TenantCreated $event) { |
| 285 | + return $event->tenant; |
| 286 | + })->toListener()); |
| 287 | + |
| 288 | + $pendingTenant = Tenant::createPending(); |
| 289 | + |
| 290 | + expect(Schema::hasTable('users'))->toBeFalse(); |
| 291 | + |
| 292 | + tenancy()->initialize($pendingTenant); |
| 293 | + |
| 294 | + // MigrateDatabase includes/excludes pending tenants based on its $includePending property, |
| 295 | + // regardless of the tenancy.pending.include_in_queries config. |
| 296 | + expect(Schema::hasTable('users'))->toBe($migrateWithPending ?? $includeInQueries); |
| 297 | +})->with([ |
| 298 | + 'include pending in queries' => [true], |
| 299 | + 'exclude pending from queries' => [false], |
| 300 | +])->with([ |
| 301 | + 'migrate with pending' => [true], |
| 302 | + 'migrate without pending' => [false], |
| 303 | + 'default to config' => [null], |
| 304 | +]); |
| 305 | + |
| 306 | +test('pending tenant databases can be seeded using a job unless configured otherwise', function (bool $includeInQueries, ?bool $seedWithPending) { |
| 307 | + config([ |
| 308 | + 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], |
| 309 | + 'tenancy.pending.include_in_queries' => $includeInQueries, |
| 310 | + 'tenancy.seeder_parameters.--class' => TestSeeder::class, |
| 311 | + ]); |
| 312 | + |
| 313 | + MigrateDatabase::$includePending = true; |
| 314 | + SeedDatabase::$includePending = $seedWithPending; |
| 315 | + |
| 316 | + Event::listen(TenancyInitialized::class, BootstrapTenancy::class); |
| 317 | + Event::listen(TenancyEnded::class, RevertToCentralContext::class); |
| 318 | + Event::listen(TenantCreated::class, JobPipeline::make([ |
| 319 | + CreateDatabase::class, |
| 320 | + MigrateDatabase::class, |
| 321 | + SeedDatabase::class, |
| 322 | + ])->send(function (TenantCreated $event) { |
| 323 | + return $event->tenant; |
| 324 | + })->toListener()); |
| 325 | + |
| 326 | + $pendingTenant = Tenant::createPending(); |
| 327 | + |
| 328 | + tenancy()->initialize($pendingTenant); |
| 329 | + |
| 330 | + // SeedDatabase includes/excludes pending tenants based on its $includePending property, |
| 331 | + // regardless of the tenancy.pending.include_in_queries config. |
| 332 | + expect(User::where('email', 'seeded@user')->exists())->toBe($seedWithPending ?? $includeInQueries); |
| 333 | +})->with([ |
| 334 | + 'include pending in queries' => [true], |
| 335 | + 'exclude pending from queries' => [false], |
| 336 | +])->with([ |
| 337 | + 'seed with pending' => [true], |
| 338 | + 'seed without pending' => [false], |
| 339 | + 'default to config' => [null], |
| 340 | +]); |
| 341 | + |
| 342 | +test('jobs that run before tenants get fully created recognize pending tenants', function () { |
| 343 | + config([ |
| 344 | + 'tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class], |
| 345 | + ]); |
| 346 | + |
| 347 | + Event::listen(TenancyInitialized::class, BootstrapTenancy::class); |
| 348 | + Event::listen(TenancyEnded::class, RevertToCentralContext::class); |
| 349 | + Event::listen(TenantCreated::class, JobPipeline::make([ |
| 350 | + CreateDatabase::class, |
| 351 | + PendingTenantJob::class, |
| 352 | + ])->send(function (TenantCreated $event) { |
| 353 | + return $event->tenant; |
| 354 | + })->toListener()); |
| 355 | + |
| 356 | + Tenant::createPending(); |
| 357 | + |
| 358 | + expect(app('tenant_is_pending'))->toBeTrue(); |
| 359 | +}); |
| 360 | + |
| 361 | +class PendingTenantJob |
| 362 | +{ |
| 363 | + public function __construct( |
| 364 | + public Tenant $tenant, |
| 365 | + ) {} |
| 366 | + |
| 367 | + public function handle() |
| 368 | + { |
| 369 | + app()->instance('tenant_is_pending', $this->tenant->pending()); |
| 370 | + } |
| 371 | +} |
0 commit comments