mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 16:14:02 +00:00
This method lets the user specify default values for custom non-nullable columns. The primary use case is when the tenants table has a column like 'slug' and createPending() is called with no value for 'slug'. This would produce an exception due to the column having no default value. Here, getPendingAttributes() can set an initial dummy slug (like a randomly generated string) before it's overwritten during a pull. getPendingAttributes() accepts an $attributes array which corresponds to the attributes passed to createPending(). The array returned from getPendingAttributes() is ultimately merged with $attributes, so the user doesn't need to use the $attributes value in getPendingAttributes(), however it serves to provide more context when the pending attributes might be dependent on $attributes and therefore derived from the $attributes actually being used. Also fixed the `finally` branch in createPending() as it was potentially referencing the $tenant variable before it was initialized.
226 lines
7.1 KiB
PHP
226 lines
7.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use Stancl\Tenancy\Commands\ClearPendingTenants;
|
|
use Stancl\Tenancy\Commands\CreatePendingTenants;
|
|
use Stancl\Tenancy\Events\CreatingPendingTenant;
|
|
use Stancl\Tenancy\Events\PendingTenantCreated;
|
|
use Stancl\Tenancy\Events\PendingTenantPulled;
|
|
use Stancl\Tenancy\Events\PullingPendingTenant;
|
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
|
use function Stancl\Tenancy\Tests\pest;
|
|
|
|
beforeEach($cleanup = function () {
|
|
Tenant::$extraCustomColumns = [];
|
|
Tenant::$getPendingAttributesUsing = null;
|
|
});
|
|
|
|
afterEach($cleanup);
|
|
|
|
test('tenants are correctly identified as pending', function (){
|
|
Tenant::createPending();
|
|
|
|
expect(Tenant::onlyPending()->count())->toBe(1);
|
|
|
|
Tenant::onlyPending()->first()->update([
|
|
'pending_since' => null
|
|
]);
|
|
|
|
expect(Tenant::onlyPending()->count())->toBe(0);
|
|
});
|
|
|
|
test('pending trait adds query scopes', function () {
|
|
Tenant::createPending();
|
|
Tenant::create();
|
|
Tenant::create();
|
|
|
|
expect(Tenant::onlyPending()->count())->toBe(1)
|
|
->and(Tenant::withPending(true)->count())->toBe(3)
|
|
->and(Tenant::withPending(false)->count())->toBe(2)
|
|
->and(Tenant::withoutPending()->count())->toBe(2);
|
|
|
|
});
|
|
|
|
test('pending tenants can be created and deleted using commands', function () {
|
|
config(['tenancy.pending.count' => 4]);
|
|
|
|
Artisan::call(CreatePendingTenants::class);
|
|
|
|
expect(Tenant::onlyPending()->count())->toBe(4);
|
|
|
|
Artisan::call(ClearPendingTenants::class);
|
|
|
|
expect(Tenant::onlyPending()->count())->toBe(0);
|
|
});
|
|
|
|
test('CreatePendingTenants command can have an older than constraint', function () {
|
|
config(['tenancy.pending.count' => 2]);
|
|
|
|
Artisan::call(CreatePendingTenants::class);
|
|
|
|
tenancy()->model()->query()->onlyPending()->first()->update([
|
|
'pending_since' => now()->subDays(5)->timestamp
|
|
]);
|
|
|
|
Artisan::call('tenants:pending-clear --older-than-days=2');
|
|
|
|
expect(Tenant::onlyPending()->count())->toBe(1);
|
|
});
|
|
|
|
test('CreatePendingTenants command cannot run with both time constraints', function () {
|
|
pest()->artisan('tenants:pending-clear --older-than-days=2 --older-than-hours=2')
|
|
->assertFailed();
|
|
});
|
|
|
|
test('tenancy can check if there are any pending tenants', function () {
|
|
expect(Tenant::onlyPending()->exists())->toBeFalse();
|
|
|
|
Tenant::createPending();
|
|
|
|
expect(Tenant::onlyPending()->exists())->toBeTrue();
|
|
});
|
|
|
|
test('tenancy can pull a pending tenant', function () {
|
|
Tenant::createPending();
|
|
|
|
expect(Tenant::pullPendingFromPool())->toBeInstanceOf(Tenant::class);
|
|
});
|
|
|
|
test('pulling a tenant from the pending tenant pool removes it from the pool', function () {
|
|
Tenant::createPending();
|
|
|
|
expect(Tenant::onlyPending()->count())->toEqual(1);
|
|
|
|
Tenant::pullPendingFromPool();
|
|
|
|
expect(Tenant::onlyPending()->count())->toEqual(0);
|
|
});
|
|
|
|
test('a new tenant gets created while pulling a pending tenant if the pending pool is empty', function () {
|
|
expect(Tenant::withPending()->get()->count())->toBe(0); // All tenants
|
|
|
|
Tenant::pullPending();
|
|
|
|
expect(Tenant::withPending()->get()->count())->toBe(1); // All tenants
|
|
});
|
|
|
|
test('pending tenants are included in all queries based on the include_in_queries config', function () {
|
|
Tenant::createPending();
|
|
|
|
config(['tenancy.pending.include_in_queries' => false]);
|
|
|
|
expect(Tenant::all()->count())->toBe(0);
|
|
|
|
config(['tenancy.pending.include_in_queries' => true]);
|
|
|
|
expect(Tenant::all()->count())->toBe(1);
|
|
});
|
|
|
|
test('pending events are dispatched', function () {
|
|
Event::fake([
|
|
CreatingPendingTenant::class,
|
|
PendingTenantCreated::class,
|
|
PullingPendingTenant::class,
|
|
PendingTenantPulled::class,
|
|
]);
|
|
|
|
Tenant::createPending();
|
|
|
|
Event::assertDispatched(CreatingPendingTenant::class);
|
|
Event::assertDispatched(PendingTenantCreated::class);
|
|
|
|
Tenant::pullPending();
|
|
|
|
Event::assertDispatched(PullingPendingTenant::class);
|
|
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]);
|
|
|
|
$tenants = collect([
|
|
Tenant::create(),
|
|
Tenant::create(),
|
|
Tenant::createPending(),
|
|
Tenant::createPending(),
|
|
]);
|
|
|
|
pest()->artisan('tenants:migrate --with-pending');
|
|
|
|
$artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz'");
|
|
|
|
$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()}"));
|
|
|
|
$artisan->assertExitCode(0);
|
|
});
|
|
|
|
test('commands run for pending tenants too if tenancy.pending.include_in_queries is true', function() {
|
|
config(['tenancy.pending.include_in_queries' => true]);
|
|
|
|
$tenants = collect([
|
|
Tenant::create(),
|
|
Tenant::create(),
|
|
Tenant::createPending(),
|
|
Tenant::createPending(),
|
|
]);
|
|
|
|
pest()->artisan('tenants:migrate --with-pending');
|
|
|
|
$artisan = pest()->artisan("tenants:run 'foo foo --b=bar --c=xyz'");
|
|
|
|
$tenants->each(fn ($tenant) => $artisan->expectsOutputToContain("Tenant: {$tenant->getTenantKey()}"));
|
|
|
|
$artisan->assertExitCode(0);
|
|
});
|
|
|
|
test('commands run for pending tenants too if the with pending option is passed', function() {
|
|
config(['tenancy.pending.include_in_queries' => false]);
|
|
|
|
$tenants = collect([
|
|
Tenant::create(),
|
|
Tenant::create(),
|
|
Tenant::createPending(),
|
|
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);
|
|
});
|
|
|
|
test('pending tenants can have default attributes for non-nullable columns', function (bool $withPendingAttributes) {
|
|
Schema::table('tenants', function (Blueprint $table) {
|
|
$table->string('slug')->unique();
|
|
});
|
|
|
|
Tenant::$extraCustomColumns = ['slug'];
|
|
if ($withPendingAttributes) Tenant::$getPendingAttributesUsing = fn () => [
|
|
'slug' => Str::random(8),
|
|
];
|
|
|
|
$fn = fn () => Tenant::createPending();
|
|
|
|
// If there are non-nullable custom columns, and createPending() is called
|
|
// on its own without any values passed for those columns (as it would be called
|
|
// by the tenants:pending-create artisan command), we expect it to fail, unless
|
|
// getPendingAttributes() provides default values for those custom columns.
|
|
if ($withPendingAttributes)
|
|
expect($fn)->not()->toThrow(QueryException::class);
|
|
else
|
|
expect($fn)->toThrow(QueryException::class);
|
|
})->with([true, false]);
|