mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 12:54:05 +00:00
Resource syncing rework (#30)
* Add &Model to docblock * Fix code style (php-cs-fixer) * Only delete synced resource if the central resource shouldSync * Add central resource detached event and listener * Add SyncedTenant interface * Use the event & listener in the test file * Add getGlobalIdentifierKey(Name) to TenantMorphPivot * Refactor TriggerSyncingEvents * Fix code style (php-cs-fixer) * Test queueing the detaching listener * Move finding the central resource into the event, naming changes * Fix code style (php-cs-fixer) * Simplify listener code * Refactor detaching logic * Create tenant resource after attaching central to tenant, test queueing related listener * Delete dd() * Fix code style (php-cs-fixer) * Move triggerAttachEvent from SyncMaster * Update attach event-related code * Move findResource from SyncedTenant to the pivot trait * Add annotation * Update annotation * Simplify getAttributesForCreation in CreateTenantResourceFromSyncMaster * Update naming * Add tenant trait for attaching/detaching resources * Update test names * Move creation attribute parsing method to trait * Rename variable * Fix code style (php-cs-fixer) * Delete complete to-do * Delete event comment * Rename event property * Find tenant resource in detach listener * Use global ID key of tenant resource in cascade deletes listener * Use global ID key name of the central resource while creating/deleting tenant resources * Add getSyncedCreationAttributes example in the annotation * Fix inconsistencies in SyncedTenant methods * Improve annotation * Don't return the query in `$scopeGetModelQuery` Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * Fix code style (php-cs-fixer) * Update scoping getModel query * Only use detach event instead of using both detach and delete events, refactor code * Test that detaching tenant from a central resource doesn't affect other tenants * Delete extra imports * Fix code style (php-cs-fixer) * Add PivotWithRelation, test attaching/detaching resources without polymorphic relations * Refactor TriggerSyncingEvents to work with non-polymorphic relations too * Fix code style (php-cs-fixer) * Rename synced resource changed event, fix tests * Enforce passing Tenant&Model to attach/detach events * Prevent firing saved event automatically in CreateTenantResource * Improve TriggerSyncingEvents trait * Delete unused import * Make TriggerSyncingEvents methods non-static, improve annotations * Pass saved model to event * Move attach/detach queueing tests to ResourceSyncingTest, pass models instead of IDs to attach/detach * Move events to ResourceSyncing\Events * Fix code style (php-cs-fixer) * Use SerializesModels in queueable listeners instead of events * Delete redundant $shouldQueue setting * Rename listener, test cascade deletes from both sides of many-to-many * Move creation attributes-related code to a separate test file, improve comments (wip) * Improve comments, fix variable name capitalization * Delete tracing comma * Extract duplicate code into a trait * Don't accept nullable tenant in SyncMasterDeleted * Fix annotation * Fix code style (php-cs-fixer) * Update annotation * Fix PHPStan error * Fix annotation * Update comments and test naming * Move triggerDeleteEvent to CascadeDeletes interface * Rename test file * Import TenantPivot in Tenant class (tests/Etc) * Add central resource not available in pivot exception * Rename SaveSyncedResource to UpdateOrCreateSyncedResource * Add new events and listeners to TSP stub * Improve comments and naming * Only keep SerializesModels in classes that utilize it * Use tenant->run() * Import events in stub * Move RS listeners to separate namespace, use `Event/`Listener/` in stub for consistency * Fix code style (php-cs-fixer) * Fix namespace changes * Use cursor instead of get * Update src/ResourceSyncing/ParsesCreationAttributes.php Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * Update naming, structure (discussed on Discord) * Update uses in in test file * remove double ;; * Add comments * Test if static properties work fine with queueable listeners * Update $shouldQuery test * Update creation attributes * Work on updating the tests * Make synced attributes configurable using static properties * Update resource syncing tests * Get rid of mixed attribute classes * Get rid of TenantUserWIthCreationAttributes * Fix imports * Get rid of the conditionally synced classes, improve tests * Simplify resource creation tests (only test the more complex cases instead of each case - if the complex case works, the simpler cases work too) * Clean up ResourceSyncingTest (mostly duplicate tests that were already in AutomaticResourceCreationTest) * Simplify class naming in polymorhpic tests * Move automatic resource creation tests to ResourceSyncingTest * Test that the sync event doesn't get triggered excessively * Only trigger the sync event if the synced attributes were changed or if the resource was recently created * Update synced attribute instead of unsynced in test * Fix sync event test * Update static property redefining test * Use getGlobalIdentifierKeyName() instead of hardcoding the key name * Delete static properties from the ResourceSyncing trait * Reuse user classes in polymorphic tests * Update tests/ResourceSyncingTest.php Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * Use the default tenants() method in central user, override the default in ResourceSyncingTest * Use BelongsToMany as tenants() return type * Fix code style (php-cs-fixer) * Delete extra static property from trait * Delete duplicate events/listeners from TSP stub * Delete weird expectation, use $model->trashed() * Change ResourceUser to TenantUser * Add defaults for getGlobalIdentifierKey(Name) * Use singular tenant in DeleteResourceInTenants name * Rename getSyncedCreationAttributes to getCreationAttributes * Fix comma position in comment * minor fixes in traits and interfaces * Fix code style (php-cs-fixer) * Correct comment * Use $tenant->run() * Update scopeGetModelQuery annotation * Use static property for testing shouldSync * Improve test * Get rid of datasets * Add trashed assertions * Always merge synced attributes with the creation attributes during parsing * Update creation attributes in test's beforeEach * Use only the necessary creation attributes (no need to include the synced attributes because they get merged automatically) * Rename ResourceTenant to MorphTenant * Add TriggerSyncingEvents docblock Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * Add force deletes test * Fix code style (php-cs-fixer) * Delete pivot if it can't access the resource class * Make parseCreationAttributes more readable * Comment out setting $scopeGetModelQuery in the stub * Add @var annotations to bootTriggerSyncingEvents * Fix attach()/detach() exception test * Interrupt creation of broken pivots instead of deleting the pivots at a later point * Add more comments * Update CreateTenantResource comment * Assert that forceDelete() doesn't affect other tenant resources * Rename test * Correct with() array formatting * Expand test with soft deletes * Merge SyncedResourceSaved tests * Improve naming, comments and minor details in the assertions * Move test * Fix failing test * Delete duplicate test * Minor test improvement * Delete duplicate test * Improve old test * Minor test improvement * Improve event test * Improve tests (naming, code, comments) * Delete extra test, add comments to the larger test * Refactor central -> tenant attach() test * Apply changes from central -> tenant attach() test on tenant -> central test * Fix assertions in central -> tenant * Correct comment and assertion * Refactor tenant -> central attach() test * Fix inconsistency * Delete unused import * Add comments * Update polymorphic test names * Rename polymorphic tests * Update listener test name * Delete redundant tenant ID assignments * Improve test names * Move polymorphic tests to ResourceSyncingTest * Mention alternative solutions in CentralResourceNotAvailableInPivotException * Add comments * Update test comments * minor changes to tests + review comments * Delete extra tests, update comments * Remove unneeded part of test * Fix comment * Improve comments * Add test for companies() realationship accessibility * Update test name * Complete to-do, add comment * Improve naming and comments (resolve some priority reviews) * Move test * Comment, resolve to-dos * Add low-level pivot assertions * Restore trashed resources if the central resource got restored, try improving tests * Fix code style (php-cs-fixer) * Dekete redundnat unsynced comments * Add to-do, test WIP * Fix restoring logic * Update todo * Add todo to fix phpdoc * Fix code style (php-cs-fixer) * PHPStan error fix wip * Fix PHPStan error * Add regression test * Delete unused trait * Add and test restoring WIP * Fix code style (php-cs-fixer) * Add to-do * Delete comment from test * Focus on restoring in the restore test * Improve maming * Fix stub * Delete redundant part of test * Delete incorrect test leftover * Add triggerRestoredEvent * Fix restore test * Correct tests and restore(() logic * Fix code style (php-cs-fixer) * Check if SoftDeletes are used before firing SyncMasterRestored * Fix comment * Revert restore action changes (phpstan errors) * Delete CascadeDeletes interface * Remove CascadeDeletes from most of the tests * Fix code style (php-cs-fixer) * Rename tests * Fix restoring + tests WIP * Fix restoring * Fix restoring tests * Fix code style (php-cs-fixer) * Test that detaching force deletes the tenant resources * Implement cacscade force deleting * Delete redundant changes * Fix typo * Fix SyncMaster * Improve test * Add force deleting logic back and fix tests * Improve comment * Delete extra assertion * Improve restoring test * Simplify assertion * Delete redundant query scoping from test * Test restore listener queueing * use strict in_array() checks * fix phpstan errors --------- Co-authored-by: lukinovec <lukinovec@gmail.com> Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
This commit is contained in:
parent
aa1437fb5e
commit
6784685054
39 changed files with 1903 additions and 1325 deletions
51
tests/Etc/ResourceSyncing/CentralUser.php
Normal file
51
tests/Etc/ResourceSyncing/CentralUser.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\ResourceSyncing;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Stancl\Tenancy\Database\Concerns\CentralConnection;
|
||||
use Stancl\Tenancy\ResourceSyncing\ResourceSyncing;
|
||||
use Stancl\Tenancy\ResourceSyncing\SyncMaster;
|
||||
|
||||
class CentralUser extends Model implements SyncMaster
|
||||
{
|
||||
use ResourceSyncing, CentralConnection;
|
||||
protected $guarded = [];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public $table = 'users';
|
||||
|
||||
public static array $syncedAttributes = [];
|
||||
|
||||
public static array $creationAttributes = [];
|
||||
|
||||
public static bool $shouldSync = true;
|
||||
|
||||
public function getTenantModelName(): string
|
||||
{
|
||||
return TenantUser::class;
|
||||
}
|
||||
|
||||
public function shouldSync(): bool
|
||||
{
|
||||
return static::$shouldSync;
|
||||
}
|
||||
|
||||
public function getCentralModelName(): string
|
||||
{
|
||||
return static::class;
|
||||
}
|
||||
|
||||
public function getSyncedAttributeNames(): array
|
||||
{
|
||||
return static::$syncedAttributes;
|
||||
}
|
||||
|
||||
public function getCreationAttributes(): array
|
||||
{
|
||||
return count(static::$creationAttributes) ? static::$creationAttributes : $this->getSyncedAttributeNames();
|
||||
}
|
||||
}
|
||||
23
tests/Etc/ResourceSyncing/CustomPivot.php
Normal file
23
tests/Etc/ResourceSyncing/CustomPivot.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\ResourceSyncing;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Stancl\Tenancy\ResourceSyncing\PivotWithRelation;
|
||||
use Stancl\Tenancy\ResourceSyncing\TenantPivot;
|
||||
|
||||
class CustomPivot extends TenantPivot implements PivotWithRelation
|
||||
{
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(CentralUser::class);
|
||||
}
|
||||
|
||||
public function getRelatedModel(): Model
|
||||
{
|
||||
return $this->users()->getModel();
|
||||
}
|
||||
}
|
||||
31
tests/Etc/ResourceSyncing/Tenant.php
Normal file
31
tests/Etc/ResourceSyncing/Tenant.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\ResourceSyncing;
|
||||
|
||||
use CentralUserWithSoftDeletes;
|
||||
use Stancl\Tenancy\ResourceSyncing\TenantPivot;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant as BaseTenant;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Tenant extends BaseTenant
|
||||
{
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(CentralUser::class, 'tenant_users', 'tenant_id', 'global_user_id', 'id', 'global_id')
|
||||
->using(TenantPivot::class);
|
||||
}
|
||||
|
||||
public function customPivotUsers(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(CentralUser::class, 'tenant_users', 'tenant_id', 'global_user_id', 'id', 'global_id', 'users')
|
||||
->using(CustomPivot::class);
|
||||
}
|
||||
|
||||
public function softDeletesUsers(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(CentralUserWithSoftDeletes::class, 'tenant_users', 'tenant_id', 'global_user_id', 'id', 'global_id', 'users')
|
||||
->using(CustomPivot::class);
|
||||
}
|
||||
}
|
||||
46
tests/Etc/ResourceSyncing/TenantUser.php
Normal file
46
tests/Etc/ResourceSyncing/TenantUser.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\ResourceSyncing;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Stancl\Tenancy\ResourceSyncing\ResourceSyncing;
|
||||
use Stancl\Tenancy\ResourceSyncing\Syncable;
|
||||
|
||||
class TenantUser extends Model implements Syncable
|
||||
{
|
||||
use ResourceSyncing;
|
||||
|
||||
protected $table = 'users';
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public static array $syncedAttributes = [];
|
||||
|
||||
public static array $creationAttributes = [];
|
||||
|
||||
public static bool $shouldSync = true;
|
||||
|
||||
public function shouldSync(): bool
|
||||
{
|
||||
return static::$shouldSync;
|
||||
}
|
||||
|
||||
public function getCentralModelName(): string
|
||||
{
|
||||
return CentralUser::class;
|
||||
}
|
||||
|
||||
public function getSyncedAttributeNames(): array
|
||||
{
|
||||
return static::$syncedAttributes;
|
||||
}
|
||||
|
||||
public function getCreationAttributes(): array
|
||||
{
|
||||
return count(static::$creationAttributes) ? static::$creationAttributes : $this->getSyncedAttributeNames();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddDeletedAtToUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dateTime('deleted_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Events\CallQueuedListener;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Events\BootstrappingTenancy;
|
||||
use Stancl\Tenancy\Events\CreatingDatabase;
|
||||
use Stancl\Tenancy\Events\CreatingTenant;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Events\TenantCreated;
|
||||
use Stancl\Tenancy\Events\UpdatingDomain;
|
||||
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||
use Stancl\Tenancy\Events\TenantCreated;
|
||||
use Stancl\Tenancy\Jobs\MigrateDatabase;
|
||||
use Illuminate\Events\CallQueuedListener;
|
||||
use Stancl\Tenancy\Events\CreatingTenant;
|
||||
use Stancl\Tenancy\Events\UpdatingDomain;
|
||||
use Stancl\Tenancy\Events\CreatingDatabase;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||
use Stancl\Tenancy\Events\BootstrappingTenancy;
|
||||
use Stancl\Tenancy\Listeners\QueueableListener;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
FooListener::$shouldQueue = false;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
|||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||
use Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||
use Stancl\Tenancy\Listeners\QueueableListener;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->mockConsoleOutput = false;
|
||||
|
|
@ -117,7 +118,30 @@ test('tenancy is initialized inside queues', function (bool $shouldEndTenancy) {
|
|||
$tenant->run(function () use ($user) {
|
||||
expect($user->fresh()->name)->toBe('Bar');
|
||||
});
|
||||
})->with([true, false]);;
|
||||
})->with([true, false]);
|
||||
|
||||
test('changing the shouldQueue static property in parent class affects child classes unless the property is redefined', function () {
|
||||
// Parent – $shouldQueue is true
|
||||
expect(app(ShouldQueueListener::class)->shouldQueue(new stdClass()))->toBeTrue();
|
||||
|
||||
// Child – $shouldQueue is redefined and set to false
|
||||
expect(app(ShouldNotQueueListener::class)->shouldQueue(new stdClass()))->toBeFalse();
|
||||
|
||||
// Child – inherits $shouldQueue from ShouldQueueListener (true)
|
||||
expect(app(InheritedQueueListener::class)->shouldQueue(new stdClass()))->toBeTrue();
|
||||
|
||||
// Update $shouldQueue of InheritedQueueListener's parent to see if it affects the child
|
||||
ShouldQueueListener::$shouldQueue = false;
|
||||
|
||||
// Parent's $shouldQueue changed to false
|
||||
expect(app(InheritedQueueListener::class)->shouldQueue(new stdClass()))->toBeFalse();
|
||||
|
||||
ShouldQueueListener::$shouldQueue = true;
|
||||
|
||||
// Parent's $shouldQueue changed back to true
|
||||
// Child's $shouldQueue is still false because it was redefined
|
||||
expect(app(ShouldNotQueueListener::class)->shouldQueue(new stdClass()))->toBeFalse();
|
||||
});
|
||||
|
||||
test('tenancy is initialized when retrying jobs', function (bool $shouldEndTenancy) {
|
||||
withFailedJobs();
|
||||
|
|
@ -254,3 +278,31 @@ class TestJob implements ShouldQueue
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShouldQueueListener extends QueueableListener
|
||||
{
|
||||
public static bool $shouldQueue = true;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
return static::$shouldQueue;
|
||||
}
|
||||
}
|
||||
|
||||
class ShouldNotQueueListener extends ShouldQueueListener
|
||||
{
|
||||
public static bool $shouldQueue = false;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
return static::$shouldQueue;
|
||||
}
|
||||
}
|
||||
|
||||
class InheritedQueueListener extends ShouldQueueListener
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
return static::$shouldQueue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,395 +0,0 @@
|
|||
<?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',
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue