1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 17:24:03 +00:00
tenancy/tests/ResourceSyncingUsingPolymorphicTest.php
lukinovec 4953c69fd8
Update path identification and Fortify integration-related logic (#13)
* Add commented UrlBinding + FortifyRouteTenancy bootstrappers to the config

* Improve FortifyRoute bootstrapper docblock

* Rename bootstrappers

* Complete renaming

* Pass defaults of the original URL generator to the new one

* Fix URL generator-related test (query string id test WIP)

* Fix code style (php-cs-fixer)

* Make Fortify bootstrapper not depend on the UrlGenerator bootstrapper, update comments

* Fix testing UrlGenerator bootstrapper

* Update TenancyUrlGenerator annotations

* Pass tenant parameter manually in Fortify bootstrapper

* Properly test TenancyUrlGenerator functionality

* Get rid of query string in Fortify bootstrapper

* Fix code style (php-cs-fixer)

* Delete outdated comment

* Improve comment

* Improve before/afterEach

* Encourage passing parameters using TenancyUrlGenerator instead of URL::defaults()

* Delete rest of defaulting logic

* Fix code style (php-cs-fixer)

* Delete test group

* Update ForgetTenantParameter docblock

* Update passTenantParameterToRoutes annotation

* Complete todo in test

* Improve test

* Update comment

* Improve comment

* Add keepQueryParameters bool to Fortify bootstrapper

* Test keepQueryParameters

* minor docblock update

* minor docblock changes

* Delete extra import

* Update src/Overrides/TenancyUrlGenerator.php

Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>

* Improve comment

* Rename test

* Update bypass parameter-related test comments

* Fix merge

* Rename $keepQueryParameters

* Add docblock

* Add comment

* Refactor Fortify bootstrapper

* Fix code style (php-cs-fixer)

* Fix comment

* Skip Fortify bootstrapper test

* minor code improvements

* Improve fortify bootstrapper test

* Add Fortify bootstrapper annotation, improve code

* Fix code style (php-cs-fixer)

* Add commenet

* Complete resource syncing todo (cleanup not needed)

* Delete incorrect namespace

* Complete route context trait name todo

* Fix code style (php-cs-fixer)

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
2023-11-26 21:08:41 +01:00

395 lines
11 KiB
PHP

<?php
declare(strict_types=1);
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Contracts\Syncable;
use Stancl\Tenancy\Contracts\SyncMaster;
use Stancl\Tenancy\Database\Concerns\CentralConnection;
use Stancl\Tenancy\Database\Concerns\ResourceSyncing;
use Stancl\Tenancy\Database\DatabaseConfig;
use Stancl\Tenancy\Database\Models\TenantMorphPivot;
use Stancl\Tenancy\Database\Models\TenantPivot;
use Stancl\Tenancy\Events\SyncedResourceSaved;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Listeners\UpdateSyncedResource;
use Stancl\Tenancy\Tests\Etc\Tenant;
beforeEach(function () {
config([
'tenancy.bootstrappers' => [
DatabaseTenancyBootstrapper::class,
],
'tenancy.models.tenant' => ResourceTenantUsingPolymorphic::class,
]);
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
DatabaseConfig::generateDatabaseNamesUsing(function () {
return 'db' . Str::random(16);
});
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
// Run migrations on central connection
pest()->artisan('migrate', [
'--path' => [
__DIR__ . '/../assets/resource-syncing-migrations',
__DIR__ . '/Etc/synced_resource_migrations/users',
__DIR__ . '/Etc/synced_resource_migrations/companies',
],
'--realpath' => true,
])->assertExitCode(0);
});
test('resource syncing works using a single pivot table for multiple models when syncing from central to tenant', function () {
$tenant1 = ResourceTenantUsingPolymorphic::create(['id' => 't1']);
migrateUsersTableForTenants();
$centralUser = CentralUserUsingPolymorphic::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'password',
'role' => 'commenter',
]);
$tenant1->run(function () {
expect(TenantUserUsingPolymorphic::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
// Assert `tenants` are accessible
expect($centralUser->tenants->pluck('id')->toArray())->toBe(['t1']);
// Users are accessible from tenant
expect($tenant1->users()->pluck('email')->toArray())->toBe(['john@localhost']);
// Assert User resource is synced
$tenant1->run(function () use ($centralUser) {
$tenantUser = TenantUserUsingPolymorphic::first()->toArray();
$centralUser = $centralUser->withoutRelations()->toArray();
unset($centralUser['id'], $tenantUser['id']);
expect($tenantUser)->toBe($centralUser);
});
$tenant2 = ResourceTenantUsingPolymorphic::create(['id' => 't2']);
migrateCompaniesTableForTenants();
$centralCompany = CentralCompanyUsingPolymorphic::create([
'global_id' => 'acme',
'name' => 'ArchTech',
'email' => 'archtech@localhost',
]);
$tenant2->run(function () {
expect(TenantCompanyUsingPolymorphic::all())->toHaveCount(0);
});
$centralCompany->tenants()->attach('t2');
// Assert `tenants` are accessible
expect($centralCompany->tenants->pluck('id')->toArray())->toBe(['t2']);
// Companies are accessible from tenant
expect($tenant2->companies()->pluck('email')->toArray())->toBe(['archtech@localhost']);
// Assert Company resource is synced
$tenant2->run(function () use ($centralCompany) {
$tenantCompany = TenantCompanyUsingPolymorphic::first()->toArray();
$centralCompany = $centralCompany->withoutRelations()->toArray();
unset($centralCompany['id'], $tenantCompany['id']);
expect($tenantCompany)->toBe($centralCompany);
});
});
test('resource syncing works using a single pivot table for multiple models when syncing from tenant to central', function () {
$tenant1 = ResourceTenantUsingPolymorphic::create(['id' => 't1']);
migrateUsersTableForTenants();
tenancy()->initialize($tenant1);
$tenantUser = TenantUserUsingPolymorphic::create([
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'password',
'role' => 'commenter',
]);
tenancy()->end();
// Assert User resource is synced
$centralUser = CentralUserUsingPolymorphic::first();
// Assert `tenants` are accessible
expect($centralUser->tenants->pluck('id')->toArray())->toBe(['t1']);
// Users are accessible from tenant
expect($tenant1->users()->pluck('email')->toArray())->toBe(['john@localhost']);
$centralUser = $centralUser->withoutRelations()->toArray();
$tenantUser = $tenantUser->toArray();
unset($centralUser['id'], $tenantUser['id']);
// array keys use a different order here
expect($tenantUser)->toEqualCanonicalizing($centralUser);
$tenant2 = ResourceTenantUsingPolymorphic::create(['id' => 't2']);
migrateCompaniesTableForTenants();
tenancy()->initialize($tenant2);
$tenantCompany = TenantCompanyUsingPolymorphic::create([
'global_id' => 'acme',
'name' => 'tenant comp',
'email' => 'company@localhost',
]);
tenancy()->end();
// Assert Company resource is synced
$centralCompany = CentralCompanyUsingPolymorphic::first();
// Assert `tenants` are accessible
expect($centralCompany->tenants->pluck('id')->toArray())->toBe(['t2']);
// Companies are accessible from tenant
expect($tenant2->companies()->pluck('email')->toArray())->toBe(['company@localhost']);
$centralCompany = $centralCompany->withoutRelations()->toArray();
$tenantCompany = $tenantCompany->toArray();
unset($centralCompany['id'], $tenantCompany['id']);
expect($tenantCompany)->toBe($centralCompany);
});
test('right resources are accessible from the tenant', function () {
$tenant1 = ResourceTenantUsingPolymorphic::create(['id' => 't1']);
$tenant2 = ResourceTenantUsingPolymorphic::create(['id' => 't2']);
migrateUsersTableForTenants();
$user1 = CentralUserUsingPolymorphic::create([
'global_id' => 'user1',
'name' => 'user1',
'email' => 'user1@localhost',
'password' => 'password',
'role' => 'commenter',
]);
$user2 = CentralUserUsingPolymorphic::create([
'global_id' => 'user2',
'name' => 'user2',
'email' => 'user2@localhost',
'password' => 'password',
'role' => 'commenter',
]);
$user3 = CentralUserUsingPolymorphic::create([
'global_id' => 'user3',
'name' => 'user3',
'email' => 'user3@localhost',
'password' => 'password',
'role' => 'commenter',
]);
$user1->tenants()->attach('t1');
$user2->tenants()->attach('t1');
$user3->tenants()->attach('t2');
expect($tenant1->users()->pluck('email')->toArray())->toBe([$user1->email, $user2->email]);
expect($tenant2->users()->pluck('email')->toArray())->toBe([$user3->email]);
});
function migrateCompaniesTableForTenants(): void
{
pest()->artisan('tenants:migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/companies',
'--realpath' => true,
])->assertExitCode(0);
}
// Tenant model used for resource syncing setup
class ResourceTenantUsingPolymorphic extends Tenant
{
public function users(): MorphToMany
{
return $this->morphedByMany(CentralUserUsingPolymorphic::class, 'tenant_resources', 'tenant_resources', 'tenant_id', 'resource_global_id', 'id', 'global_id')
->using(TenantMorphPivot::class);
}
public function companies(): MorphToMany
{
return $this->morphedByMany(CentralCompanyUsingPolymorphic::class, 'tenant_resources', 'tenant_resources', 'tenant_id', 'resource_global_id', 'id', 'global_id')
->using(TenantMorphPivot::class);
}
}
class CentralUserUsingPolymorphic extends Model implements SyncMaster
{
use ResourceSyncing, CentralConnection;
protected $guarded = [];
public $timestamps = false;
public $table = 'users';
public function getTenantModelName(): string
{
return TenantUserUsingPolymorphic::class;
}
public function getGlobalIdentifierKey(): string|int
{
return $this->getAttribute($this->getGlobalIdentifierKeyName());
}
public function getGlobalIdentifierKeyName(): string
{
return 'global_id';
}
public function getCentralModelName(): string
{
return static::class;
}
public function getSyncedAttributeNames(): array
{
return [
'global_id',
'name',
'password',
'email',
];
}
}
class TenantUserUsingPolymorphic extends Model implements Syncable
{
use ResourceSyncing;
protected $table = 'users';
protected $guarded = [];
public $timestamps = false;
public function getGlobalIdentifierKey(): string|int
{
return $this->getAttribute($this->getGlobalIdentifierKeyName());
}
public function getGlobalIdentifierKeyName(): string
{
return 'global_id';
}
public function getCentralModelName(): string
{
return CentralUserUsingPolymorphic::class;
}
public function getSyncedAttributeNames(): array
{
return [
'global_id',
'name',
'password',
'email',
];
}
}
class CentralCompanyUsingPolymorphic extends Model implements SyncMaster
{
use ResourceSyncing, CentralConnection;
protected $guarded = [];
public $timestamps = false;
public $table = 'companies';
public function getTenantModelName(): string
{
return TenantCompanyUsingPolymorphic::class;
}
public function getGlobalIdentifierKey(): string|int
{
return $this->getAttribute($this->getGlobalIdentifierKeyName());
}
public function getGlobalIdentifierKeyName(): string
{
return 'global_id';
}
public function getCentralModelName(): string
{
return static::class;
}
public function getSyncedAttributeNames(): array
{
return [
'global_id',
'name',
'email',
];
}
}
class TenantCompanyUsingPolymorphic extends Model implements Syncable
{
use ResourceSyncing;
protected $table = 'companies';
protected $guarded = [];
public $timestamps = false;
public function getGlobalIdentifierKey(): string|int
{
return $this->getAttribute($this->getGlobalIdentifierKeyName());
}
public function getGlobalIdentifierKeyName(): string
{
return 'global_id';
}
public function getCentralModelName(): string
{
return CentralCompanyUsingPolymorphic::class;
}
public function getSyncedAttributeNames(): array
{
return [
'global_id',
'name',
'email',
];
}
}