1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-05 13:34:04 +00:00

Merge branch 'master' into poly-sync

This commit is contained in:
Abrar Ahmad 2022-12-06 11:35:40 +05:00
commit dc69a112eb
34 changed files with 231 additions and 125 deletions

View file

@ -10,10 +10,16 @@ Run `composer docker-up` to start the containers. Then run `composer test` to ru
If you need to pass additional flags to phpunit, use `./test --foo` instead of `composer test --foo`. Composer scripts unfortunately don't pass CLI arguments.
If you want to run a specific test (or test file), you can also use `./t 'name of the test'`. This is equivalent to `./test --no-coverage --filter 'name of the test'`.
If you want to run a specific test (or test file), you can also use `./t 'name of the test'`. This is equivalent to `./test --no-coverage --filter 'name of the test'` (`--no-coverage` speeds up the execution time).
When you're done testing, run `composer docker-down` to shut down the containers.
### Debugging tests
If you're developing some feature and you encounter `SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry` errors, it's likely that some PHP errors were thrown in past test runs and prevented the test cleanup from running properly.
To fix this, simply delete the database memory by shutting down containers and starting them again: `composer docker-down && composer docker-up`.
### Docker on M1
Run `composer docker-m1` to symlink `docker-compose-m1.override.yml` to `docker-compose.override.yml`. This will reconfigure a few services in the docker compose config to be compatible with M1.

View file

@ -6,10 +6,29 @@ use Stancl\Tenancy\Middleware;
use Stancl\Tenancy\Resolvers;
return [
'tenant_model' => Stancl\Tenancy\Database\Models\Tenant::class,
'domain_model' => Stancl\Tenancy\Database\Models\Domain::class,
/**
* Configuration for the models used by Tenancy.
*/
'models' => [
'tenant' => Stancl\Tenancy\Database\Models\Tenant::class,
'domain' => Stancl\Tenancy\Database\Models\Domain::class,
'id_generator' => Stancl\Tenancy\UUIDGenerator::class,
/**
* Name of the column used to relate models to tenants.
*
* This is used by the HasDomains trait, and models that use the BelongsToTenant trait (used in single-database tenancy).
*/
'tenant_key_column' => 'tenant_id',
/**
* Used for generating tenant IDs.
*
* - Feel free to override this with a custom class that implements the UniqueIdentifierGenerator interface.
* - To use autoincrement IDs, set this to null and update the `tenants` table migration to use an autoincrement column.
* SECURITY NOTE: Keep in mind that autoincrement IDs come with *potential* enumeration issues (such as tenant storage URLs).
*/
'id_generator' => Stancl\Tenancy\UUIDGenerator::class,
],
/**
* The list of domains hosting your central app.
@ -293,12 +312,4 @@ return [
'--class' => 'Database\Seeders\DatabaseSeeder', // root seeder class
// '--force' => true,
],
/**
* Single-database tenancy config.
*/
'single_db' => [
/** The name of the column used by models with the BelongsToTenant trait. */
'tenant_id_column' => 'tenant_id',
],
];

View file

@ -5,8 +5,9 @@ declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Tenancy;
class CreateTenantUserImpersonationTokensTable extends Migration
return new class extends Migration
{
/**
* Run the migrations.
@ -17,13 +18,13 @@ class CreateTenantUserImpersonationTokensTable extends Migration
{
Schema::create('tenant_user_impersonation_tokens', function (Blueprint $table) {
$table->string('token', 128)->primary();
$table->string('tenant_id');
$table->string(Tenancy::tenantKeyColumn());
$table->string('user_id');
$table->string('auth_guard');
$table->string('redirect_url');
$table->timestamp('created_at');
$table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
$table->foreign(Tenancy::tenantKeyColumn())->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
});
}
@ -36,4 +37,4 @@ class CreateTenantUserImpersonationTokensTable extends Migration
{
Schema::dropIfExists('tenant_user_impersonation_tokens');
}
}
};

View file

@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTenantsTable extends Migration
return new class extends Migration
{
/**
* Run the migrations.
@ -34,4 +34,4 @@ class CreateTenantsTable extends Migration
{
Schema::dropIfExists('tenants');
}
}
};

View file

@ -5,8 +5,9 @@ declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Tenancy;
class CreateDomainsTable extends Migration
return new class extends Migration
{
/**
* Run the migrations.
@ -18,10 +19,10 @@ class CreateDomainsTable extends Migration
Schema::create('domains', function (Blueprint $table) {
$table->increments('id');
$table->string('domain', 255)->unique();
$table->string('tenant_id');
$table->string(Tenancy::tenantKeyColumn());
$table->timestamps();
$table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade');
$table->foreign(Tenancy::tenantKeyColumn())->references('id')->on('tenants')->onUpdate('cascade');
});
}
@ -34,4 +35,4 @@ class CreateDomainsTable extends Migration
{
Schema::dropIfExists('domains');
}
}
};

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Controllers\TenantAssetController;
// todo make this work with path identification
Route::get('/tenancy/assets/{path?}', [TenantAssetController::class, 'asset'])
->where('path', '(.*)')
->name('stancl.tenancy.asset');

View file

@ -10,7 +10,6 @@ use Illuminate\Database\Eloquent\Builder;
class ClearPendingTenants extends Command
{
protected $signature = 'tenants:pending-clear
{--all : Override the default settings and deletes all pending tenants}
{--older-than-days= : Deletes all pending tenants older than the amount of days}
{--older-than-hours= : Deletes all pending tenants older than the amount of hours}';
@ -24,32 +23,25 @@ class ClearPendingTenants extends Command
// We compare the original expiration date to the new one to check if the new one is different later
$originalExpirationDate = $expirationDate->copy()->toImmutable();
// Skip the time constraints if the 'all' option is given
if (! $this->option('all')) {
/** @var ?int $olderThanDays */
$olderThanDays = $this->option('older-than-days');
$olderThanDays = (int) $this->option('older-than-days');
$olderThanHours = (int) $this->option('older-than-hours');
/** @var ?int $olderThanHours */
$olderThanHours = $this->option('older-than-hours');
if ($olderThanDays && $olderThanHours) {
$this->line("<options=bold,reverse;fg=red> Cannot use '--older-than-days' and '--older-than-hours' together \n"); // todo@cli refactor all of these styled command outputs to use $this->components
$this->line('Please, choose only one of these options.');
if ($olderThanDays && $olderThanHours) {
$this->line("<options=bold,reverse;fg=red> Cannot use '--older-than-days' and '--older-than-hours' together \n"); // todo@cli refactor all of these styled command outputs to use $this->components
$this->line('Please, choose only one of these options.');
return 1; // Exit code for failure
}
if ($olderThanDays) {
$expirationDate->subDays($olderThanDays);
}
if ($olderThanHours) {
$expirationDate->subHours($olderThanHours);
}
return 1; // Exit code for failure
}
$deletedTenantCount = tenancy()
->query()
if ($olderThanDays) {
$expirationDate->subDays($olderThanDays);
}
if ($olderThanHours) {
$expirationDate->subHours($olderThanHours);
}
$deletedTenantCount = tenancy()->query()
->onlyPending()
->when($originalExpirationDate->notEqualTo($expirationDate), function (Builder $query) use ($expirationDate) {
$query->where($query->getModel()->getColumnForQuery('pending_since'), '<', $expirationDate->timestamp);

View file

@ -30,8 +30,8 @@ class CreatePendingTenants extends Command
$createdCount++;
}
$this->info($createdCount . ' ' . str('tenant')->plural($createdCount) . ' created.');
$this->info($maxPendingTenantCount . ' ' . str('tenant')->plural($maxPendingTenantCount) . ' ready to be used.');
$this->info($createdCount . ' pending ' . str('tenant')->plural($createdCount) . ' created.');
$this->info($maxPendingTenantCount . ' pending ' . str('tenant')->plural($maxPendingTenantCount) . ' ready to be used.');
return 0;
}
@ -39,8 +39,7 @@ class CreatePendingTenants extends Command
/** Calculate the number of currently available pending tenants. */
protected function getPendingTenantCount(): int
{
return tenancy()
->query()
return tenancy()->query()
->onlyPending()
->count();
}

View file

@ -5,13 +5,18 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Commands;
use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Support\Facades\Schema;
class MigrateFreshOverride extends FreshCommand
{
public function handle()
{
if (config('tenancy.database.drop_tenant_databases_on_migrate_fresh')) {
tenancy()->model()::cursor()->each->delete();
$tenantModel = tenancy()->model();
if (Schema::hasTable($tenantModel->getTable())) {
$tenantModel::cursor()->each->delete();
}
}
return parent::handle();

View file

@ -23,7 +23,7 @@ class TenantDump extends DumpCommand
public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher): int
{
if (is_null($this->option('path'))) {
$this->input->setOption('path', database_path('schema/tenant-schema.dump'));
$this->input->setOption('path', config('tenancy.migration_parameters.--schema-path') ?? database_path('schema/tenant-schema.dump'));
}
$tenant = $this->option('tenant')
@ -41,7 +41,7 @@ class TenantDump extends DumpCommand
return 1;
}
parent::handle($connections, $dispatcher);
$tenant->run(fn () => parent::handle($connections, $dispatcher));
return 0;
}

View file

@ -23,8 +23,7 @@ trait HasTenantOptions
protected function getTenants(): LazyCollection
{
return tenancy()
->query()
return tenancy()->query()
->when($this->option('tenants'), function ($query) {
$query->whereIn(tenancy()->model()->getTenantKeyName(), $this->option('tenants'));
})

View file

@ -6,6 +6,7 @@ namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Database\TenantScope;
use Stancl\Tenancy\Tenancy;
/**
* @property-read Tenant $tenant
@ -14,12 +15,7 @@ trait BelongsToTenant
{
public function tenant()
{
return $this->belongsTo(config('tenancy.tenant_model'), static::tenantIdColumn());
}
public static function tenantIdColumn(): string
{
return config('tenancy.single_db.tenant_id_column');
return $this->belongsTo(config('tenancy.models.tenant'), Tenancy::tenantKeyColumn());
}
public static function bootBelongsToTenant(): void
@ -27,9 +23,9 @@ trait BelongsToTenant
static::addGlobalScope(new TenantScope);
static::creating(function ($model) {
if (! $model->getAttribute(static::tenantIdColumn()) && ! $model->relationLoaded('tenant')) {
if (! $model->getAttribute(Tenancy::tenantKeyColumn()) && ! $model->relationLoaded('tenant')) {
if (tenancy()->initialized) {
$model->setAttribute(static::tenantIdColumn(), tenant()->getTenantKey());
$model->setAttribute(Tenancy::tenantKeyColumn(), tenant()->getTenantKey());
$model->setRelation('tenant', tenant());
}
}

View file

@ -2,11 +2,10 @@
declare(strict_types=1);
// todo not sure if this should be in Database\
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\Domain;
use Stancl\Tenancy\Tenancy;
/**
* @property-read Domain[]|\Illuminate\Database\Eloquent\Collection $domains
@ -17,12 +16,12 @@ trait HasDomains
{
public function domains()
{
return $this->hasMany(config('tenancy.domain_model'), 'tenant_id');
return $this->hasMany(config('tenancy.models.domain'), Tenancy::tenantKeyColumn());
}
public function createDomain($data): Domain
{
$class = config('tenancy.domain_model');
$class = config('tenancy.models.domain');
if (! is_array($data)) {
$data = ['domain' => $data];

View file

@ -6,16 +6,17 @@ namespace Stancl\Tenancy\Database\Concerns;
use Illuminate\Validation\Rules\Exists;
use Illuminate\Validation\Rules\Unique;
use Stancl\Tenancy\Tenancy;
trait HasScopedValidationRules
{
public function unique($table, $column = 'NULL')
{
return (new Unique($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey());
return (new Unique($table, $column))->where(Tenancy::tenantKeyColumn(), $this->getTenantKey());
}
public function exists($table, $column = 'NULL')
{
return (new Exists($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey());
return (new Exists($table, $column))->where(Tenancy::tenantKeyColumn(), $this->getTenantKey());
}
}

View file

@ -28,7 +28,7 @@ class Domain extends Model implements Contracts\Domain
public function tenant(): BelongsTo
{
return $this->belongsTo(config('tenancy.tenant_model'));
return $this->belongsTo(config('tenancy.models.tenant'));
}
protected $dispatchesEvents = [

View file

@ -7,7 +7,7 @@ namespace Stancl\Tenancy\Database;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
use Stancl\Tenancy\Tenancy;
class TenantScope implements Scope
{
@ -17,7 +17,7 @@ class TenantScope implements Scope
return;
}
$builder->where($model->qualifyColumn(BelongsToTenant::tenantIdColumn()), tenant()->getTenantKey());
$builder->where($model->qualifyColumn(Tenancy::tenantKeyColumn()), tenant()->getTenantKey());
}
public function extend(Builder $builder): void

View file

@ -20,7 +20,7 @@ class UserImpersonation implements Feature
{
$tenancy->macro('impersonate', function (Tenant $tenant, string $userId, string $redirectUrl, string $authGuard = null): ImpersonationToken {
return ImpersonationToken::create([
'tenant_id' => $tenant->getTenantKey(),
Tenancy::tenantKeyColumn() => $tenant->getTenantKey(),
'user_id' => $userId,
'redirect_url' => $redirectUrl,
'auth_guard' => $authGuard,
@ -39,7 +39,7 @@ class UserImpersonation implements Feature
abort_if($tokenExpired, 403);
$tokenTenantId = (string) $token->tenant_id;
$tokenTenantId = (string) $token->getAttribute(Tenancy::tenantKeyColumn());
$currentTenantId = (string) tenant()->getTenantKey();
abort_unless($tokenTenantId === $currentTenantId, 403);

View file

@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Listeners;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Events\Contracts\TenantEvent;
use Stancl\Tenancy\Events\Contracts\TenancyEvent;
class CreateTenantConnection
{
@ -15,11 +15,12 @@ class CreateTenantConnection
) {
}
public function handle(TenantEvent $event): void
public function handle(TenancyEvent $event): void
{
/** @var TenantWithDatabase */
$tenant = $event->tenant;
/** @var TenantWithDatabase $tenant */
$tenant = $event->tenancy->tenant;
$this->database->purgeTenantConnection();
$this->database->createTenantConnection($tenant);
}
}

View file

@ -14,6 +14,7 @@ use Stancl\Tenancy\Database\TenantCollection;
use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase;
use Stancl\Tenancy\Events\SyncedResourceSaved;
use Stancl\Tenancy\Exceptions\ModelNotSyncMasterException;
use Stancl\Tenancy\Tenancy;
// todo@v4 review all code related to resource syncing
@ -77,7 +78,7 @@ class UpdateSyncedResource extends QueueableListener
/** @var Tenant */
$tenant = $event->tenant;
return ((string) $model->pivot->tenant_id) === ((string) $tenant->getTenantKey());
return ((string) $model->pivot->getAttribute(Tenancy::tenantKeyColumn())) === ((string) $tenant->getTenantKey());
};
$mappingExists = $centralModel->tenants->contains($currentTenantMapping);

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Listeners;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Events\Contracts\TenancyEvent;
class UseCentralConnection
{
public function __construct(
protected DatabaseManager $database,
) {
}
public function handle(TenancyEvent $event): void
{
$this->database->reconnectToCentral();
}
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Listeners;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Events\Contracts\TenancyEvent;
class UseTenantConnection
{
public function __construct(
protected DatabaseManager $database,
) {
}
public function handle(TenancyEvent $event): void
{
$this->database->setDefaultConnection('tenant');
}
}

View file

@ -18,7 +18,7 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
{
$domain = $args[0];
$tenant = config('tenancy.tenant_model')::query()
$tenant = config('tenancy.models.tenant')::query()
->whereHas('domains', fn (Builder $query) => $query->where('domain', $domain))
->with('domains')
->first();

View file

@ -97,7 +97,7 @@ class Tenancy
public static function model(): Tenant&Model
{
$class = config('tenancy.tenant_model');
$class = config('tenancy.models.tenant');
/** @var Tenant&Model $model */
$model = new $class;
@ -105,6 +105,12 @@ class Tenancy
return $model;
}
/** Name of the column used to relate models to tenants. */
public static function tenantKeyColumn(): string
{
return config('tenancy.models.tenant_key_column') ?? 'tenant_id';
}
/**
* Try to find a tenant using an ID.
*

View file

@ -54,9 +54,9 @@ class TenancyServiceProvider extends ServiceProvider
$this->app->singleton($bootstrapper);
}
// Bind the class in the tenancy.id_generator config to the UniqueIdentifierGenerator abstract.
if (! is_null($this->app['config']['tenancy.id_generator'])) {
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.id_generator']);
// Bind the class in the tenancy.models.id_generator config to the UniqueIdentifierGenerator abstract.
if (! is_null($this->app['config']['tenancy.models.id_generator'])) {
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.models.id_generator']);
}
$this->app->singleton(Commands\Migrate::class, function ($app) {

View file

@ -16,7 +16,7 @@ beforeEach(function () {
});
});
config(['tenancy.tenant_model' => CombinedTenant::class]);
config(['tenancy.models.tenant' => CombinedTenant::class]);
});
test('tenant can be identified by subdomain', function () {

View file

@ -25,7 +25,7 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
beforeEach(function () {
if (file_exists($schemaPath = database_path('schema/tenant-schema.dump'))) {
if (file_exists($schemaPath = 'tests/Etc/tenant-schema-test.dump')) {
unlink($schemaPath);
}
@ -111,28 +111,44 @@ test('migrate command loads schema state', function () {
test('dump command works', function () {
$tenant = Tenant::create();
$schemaPath = 'tests/Etc/tenant-schema-test.dump';
Artisan::call('tenants:migrate');
tenancy()->initialize($tenant);
expect($schemaPath)->not()->toBeFile();
Artisan::call('tenants:dump --path="tests/Etc/tenant-schema-test.dump"');
expect('tests/Etc/tenant-schema-test.dump')->toBeFile();
});
test('tenant dump file gets created as tenant-schema.dump in the database schema folder by default', function() {
config(['tenancy.migration_parameters.--schema-path' => $schemaPath = database_path('schema/tenant-schema.dump')]);
$tenant = Tenant::create();
Artisan::call('tenants:migrate');
tenancy()->initialize($tenant);
Artisan::call('tenants:dump');
Artisan::call('tenants:dump ' . "--tenant='$tenant->id' --path='$schemaPath'");
expect($schemaPath)->toBeFile();
});
test('migrate command uses the correct schema path by default', function () {
test('dump command generates dump at the passed path', function() {
$tenant = Tenant::create();
Artisan::call('tenants:migrate');
expect($schemaPath = 'tests/Etc/tenant-schema-test.dump')->not()->toBeFile();
Artisan::call("tenants:dump --tenant='$tenant->id' --path='$schemaPath'");
expect($schemaPath)->toBeFile();
});
test('dump command generates dump at the path specified in the tenancy migration parameters config', function() {
config(['tenancy.migration_parameters.--schema-path' => $schemaPath = 'tests/Etc/tenant-schema-test.dump']);
$tenant = Tenant::create();
Artisan::call('tenants:migrate');
expect($schemaPath)->not()->toBeFile();
Artisan::call("tenants:dump --tenant='$tenant->id'");
expect($schemaPath)->toBeFile();
});
test('migrate command correctly uses the schema dump located at the configured schema path by default', function () {
config(['tenancy.migration_parameters.--schema-path' => 'tests/Etc/tenant-schema.dump']);
$tenant = Tenant::create();
@ -146,6 +162,7 @@ test('migrate command uses the correct schema path by default', function () {
tenancy()->initialize($tenant);
// schema_users is a table included in the tests/Etc/tenant-schema dump
// Check for both tables to see if missing migrations also get executed
expect(Schema::hasTable('schema_users'))->toBeTrue();
expect(Schema::hasTable('users'))->toBeTrue();

View file

@ -6,7 +6,7 @@ use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Jobs\DeleteDomains;
beforeEach(function () {
config(['tenancy.tenant_model' => DatabaseAndDomainTenant::class]);
config(['tenancy.models.tenant' => DatabaseAndDomainTenant::class]);
});
test('job delete domains successfully', function (){
@ -29,4 +29,4 @@ test('job delete domains successfully', function (){
class DatabaseAndDomainTenant extends \Stancl\Tenancy\Tests\Etc\Tenant
{
use HasDomains;
}
}

View file

@ -21,7 +21,7 @@ beforeEach(function () {
});
});
config(['tenancy.tenant_model' => DomainTenant::class]);
config(['tenancy.models.tenant' => DomainTenant::class]);
});
test('tenant can be identified using hostname', function () {

45
tests/ManualModeTest.php Normal file
View file

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
use Illuminate\Support\Facades\Event;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\CreateTenantConnection;
use Stancl\Tenancy\Listeners\UseCentralConnection;
use Stancl\Tenancy\Listeners\UseTenantConnection;
use \Stancl\Tenancy\Tests\Etc\Tenant;
test('manual tenancy initialization works', function () {
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
Event::listen(TenancyInitialized::class, CreateTenantConnection::class);
Event::listen(TenancyInitialized::class, UseTenantConnection::class);
Event::listen(TenancyEnded::class, UseCentralConnection::class);
$tenant = Tenant::create();
expect(app('db')->getDefaultConnection())->toBe('central');
expect(array_keys(app('db')->getConnections()))->toBe(['central', 'tenant_host_connection']);
pest()->assertArrayNotHasKey('tenant', config('database.connections'));
tenancy()->initialize($tenant);
// Trigger creation of the tenant connection
createUsersTable();
expect(app('db')->getDefaultConnection())->toBe('tenant');
expect(array_keys(app('db')->getConnections()))->toBe(['central', 'tenant']);
pest()->assertArrayHasKey('tenant', config('database.connections'));
tenancy()->end();
expect(array_keys(app('db')->getConnections()))->toBe(['central']);
expect(config('database.connections.tenant'))->toBeNull();
expect(app('db')->getDefaultConnection())->toBe(config('tenancy.database.central_connection'));
});

View file

@ -67,23 +67,6 @@ test('CreatePendingTenants command cannot run with both time constraints', funct
->assertFailed();
});
test('CreatePendingTenants commands all option overrides any config constraints', function () {
Tenant::createPending();
Tenant::createPending();
tenancy()->model()->query()->onlyPending()->first()->update([
'pending_since' => now()->subDays(10)
]);
config(['tenancy.pending.older_than_days' => 4]);
Artisan::call(ClearPendingTenants::class, [
'--all' => true
]);
expect(Tenant::onlyPending()->count())->toBe(0);
});
test('tenancy can check if there are any pending tenants', function () {
expect(Tenant::onlyPending()->exists())->toBeFalse();

View file

@ -31,7 +31,7 @@ beforeEach(function () {
$table->foreign('post_id')->references('id')->on('posts')->onUpdate('cascade')->onDelete('cascade');
});
config(['tenancy.tenant_model' => Tenant::class]);
config(['tenancy.models.tenant' => Tenant::class]);
});
test('primary models are scoped to the current tenant', function () {
@ -142,7 +142,7 @@ test('tenant id is not auto added when creating primary resources in central con
});
test('tenant id column name can be customized', function () {
config(['tenancy.single_db.tenant_id_column' => 'team_id']);
config(['tenancy.models.tenant_key_column' => 'team_id']);
Schema::drop('comments');
Schema::drop('posts');

View file

@ -20,7 +20,7 @@ beforeEach(function () {
});
});
config(['tenancy.tenant_model' => SubdomainTenant::class]);
config(['tenancy.models.tenant' => SubdomainTenant::class]);
});
test('tenant can be identified by subdomain', function () {

View file

@ -43,7 +43,7 @@ test('current tenant can be resolved from service container using typehint', fun
});
test('id is generated when no id is supplied', function () {
config(['tenancy.id_generator' => UUIDGenerator::class]);
config(['tenancy.models.id_generator' => UUIDGenerator::class]);
$this->mock(UUIDGenerator::class, function ($mock) {
return $mock->shouldReceive('generate')->once();

View file

@ -109,7 +109,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
'central' => true,
],
'tenancy.seeder_parameters' => [],
'tenancy.tenant_model' => Tenant::class, // Use test tenant w/ DBs & domains
'tenancy.models.tenant' => Tenant::class, // Use test tenant w/ DBs & domains
]);
$app->singleton(RedisTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration