From 3e514461ca5766ddaf62ebaf112e46a2d3e6faf2 Mon Sep 17 00:00:00 2001 From: Abrar Ahmad Date: Tue, 16 Aug 2022 15:27:34 +0500 Subject: [PATCH] wip --- src/Contracts/Syncable.php | 2 +- src/Database/Concerns/ResourceSyncing.php | 5 + src/Listeners/UpdateSyncedResource.php | 19 +- ...000009_add_extra_column_to_users_table.php | 26 ++ tests/ResourceSyncingTest.php | 230 ++++++++++++++++-- 5 files changed, 260 insertions(+), 22 deletions(-) create mode 100644 tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_users_table.php diff --git a/src/Contracts/Syncable.php b/src/Contracts/Syncable.php index fa707719..15f35d8c 100644 --- a/src/Contracts/Syncable.php +++ b/src/Contracts/Syncable.php @@ -16,5 +16,5 @@ interface Syncable public function triggerSyncEvent(); - public function getCreateAttributeNames(): array; + public function getResourceCreationAttributes(): array|null; } diff --git a/src/Database/Concerns/ResourceSyncing.php b/src/Database/Concerns/ResourceSyncing.php index f1026f7a..bfab0f3b 100644 --- a/src/Database/Concerns/ResourceSyncing.php +++ b/src/Database/Concerns/ResourceSyncing.php @@ -32,4 +32,9 @@ trait ResourceSyncing /** @var Syncable $this */ event(new SyncedResourceSaved($this, tenant())); } + + public function getResourceCreationAttributes(): array|null + { + return null; + } } diff --git a/src/Listeners/UpdateSyncedResource.php b/src/Listeners/UpdateSyncedResource.php index 201c7c3b..123298f8 100644 --- a/src/Listeners/UpdateSyncedResource.php +++ b/src/Listeners/UpdateSyncedResource.php @@ -6,6 +6,8 @@ namespace Stancl\Tenancy\Listeners; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Support\Arr; +use Stancl\Tenancy\Contracts\Syncable; use Stancl\Tenancy\Contracts\SyncMaster; use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase; use Stancl\Tenancy\Events\SyncedResourceSaved; @@ -58,7 +60,7 @@ class UpdateSyncedResource extends QueueableListener event(new SyncedResourceChangedInForeignDatabase($event->model, null)); } else { // If the resource doesn't exist at all in the central DB,we create - $centralModel = $event->model->getCentralModelName()::create($event->model->only($event->model->getCreateAttributeNames())); + $centralModel = $event->model->getCentralModelName()::create($this->getResourceCreationAttributes($event->model)); event(new SyncedResourceChangedInForeignDatabase($event->model, null)); } }); @@ -111,11 +113,24 @@ class UpdateSyncedResource extends QueueableListener $localModel->update($syncedAttributes); } else { // When creating, we use all columns, not just the synced ones. - $localModel = $localModelClass::create($eventModel->getAttributes()); + $localModel = $localModelClass::create($this->getResourceCreationAttributes($eventModel)); } event(new SyncedResourceChangedInForeignDatabase($localModel, $tenant)); }); }); } + + function getResourceCreationAttributes(Syncable $model): array + { + $attributes = $model->getAttributes(); + + if ($model->getResourceCreationAttributes()) { + // If array is key-value, We assume default values are provided + // if array is plain values, fetch attributes from model + $attributes = Arr::isAssoc($model->getResourceCreationAttributes()) ? $model->getResourceCreationAttributes() : $model->only($model->getResourceCreationAttributes()); + } + + return $attributes; + } } diff --git a/tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_users_table.php b/tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_users_table.php new file mode 100644 index 00000000..54977000 --- /dev/null +++ b/tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_users_table.php @@ -0,0 +1,26 @@ +string('foo'); + }); + } + + public function down() + { + } +} diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php index 50f0b7f5..6326985b 100644 --- a/tests/ResourceSyncingTest.php +++ b/tests/ResourceSyncingTest.php @@ -47,6 +47,7 @@ beforeEach(function () { UpdateSyncedResource::$shouldQueue = false; // global state cleanup Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class); + // run migrations on central connection pest()->artisan('migrate', [ '--path' => [ __DIR__ . '/Etc/synced_resource_migrations', @@ -126,10 +127,96 @@ test('only the synced columns are updated in the central db', function () { ], ResourceUser::first()->getAttributes()); }); -test('creating the resource in tenant database creates it in central database with provided attribute', function () { +test('creating the resource in tenant database creates it in central database ad 1:1 copy when create attributes are null', function () { // Assert no user exists in central DB expect(ResourceUser::all())->toHaveCount(0); + $tenant = ResourceTenant::create(); + migrateTenantsResource(); + + tenancy()->initialize($tenant); + + // Create the user in tenant DB + ResourceUser::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', // unsynced + ]); + + tenancy()->end(); + + // Assert central user was created without `code` property + expect(CentralUser::first()->toArray())->toEqual(ResourceUser::first()->toArray()); +}); + +test('creating the resource in tenant database creates it in central database with provided default attributes values', function () { + // override method in ResourceUser class to return attribute values + class ResourceUserWithDefaultValues extends ResourceUser { + public function getResourceCreationAttributes(): array + { + // Attributes default values when creating resources from tenant to central DB + return + [ + 'global_id' => 'abc-123', + 'name' => 'John', + 'password' => 'password', + 'email' => 'john@demo', + 'role' => 'admin', + ]; + } + } + + // Assert no user exists in central DB + expect(ResourceUserWithDefaultValues::all())->toHaveCount(0); + + $tenant = ResourceTenant::create(); + migrateTenantsResource(); + + tenancy()->initialize($tenant); + + // Create the user in tenant DB + ResourceUserWithDefaultValues::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', // unsynced + ]); + + tenancy()->end(); + + expect(CentralUser::first()->global_id)->toBe('abc-123'); + expect(CentralUser::first()->name)->toBe('John'); + expect(CentralUser::first()->password)->toBe('password'); + expect(CentralUser::first()->email)->toBe('john@demo'); + expect(CentralUser::first()->role)->toBe('admin'); +}); + +test('creating the resource in tenant database creates it in central database with provided attributes names', function () { + // override method in ResourceUser class to return attribute names + class ResourceUserWithAttributeNames extends ResourceUser { + public function getResourceCreationAttributes(): array + { + // Attributes used when creating resources from tenant to central DB + // Notice here we are not adding "code" filed because it does + // not exist in central model + return + [ + 'global_id', + 'name', + 'password', + 'email', + 'role' + ]; + } + + } + + // Assert no user exists in central DB + expect(ResourceUserWithAttributeNames::all())->toHaveCount(0); + $tenant = ResourceTenant::create(); pest()->artisan('tenants:migrate', [ '--path' => __DIR__ . '/Etc/synced_resource_migrations/custom', @@ -139,7 +226,7 @@ test('creating the resource in tenant database creates it in central database wi tenancy()->initialize($tenant); // Create the user in tenant DB - ResourceUser::create([ + ResourceUserWithAttributeNames::create([ 'global_id' => 'acme', 'name' => 'John Doe', 'email' => 'john@localhost', @@ -155,6 +242,128 @@ test('creating the resource in tenant database creates it in central database wi expect(CentralUser::first()->code)->toBeNull(); }); +test('creating the resource in central database creates it in tenant database with 1:1 copy when create attributes are null', function () { + $centralUser = CentralUser::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', // unsynced + ]); + + $tenant = ResourceTenant::create([ + 'id' => 't1', + ]); + migrateTenantsResource(); + + $tenant->run(function () { + expect(ResourceUser::all())->toHaveCount(0); + }); + + $centralUser->tenants()->attach('t1'); + + $tenant->run(function () { + expect(ResourceUser::all())->toHaveCount(1); + expect(ResourceUser::first()->global_id)->toBe('acme'); + }); +}); + +test('creating the resource in central database creates it in tenant database with provided default attributes values', function () { + // override method in ResourceUser class to return attribute values + class CentralUserWithDefaultValues extends CentralUser { + public function getResourceCreationAttributes(): array + { + // Attributes default values when creating resources from central to tenant model + return + [ + 'global_id' => 'abc-123', + 'name' => 'John', + 'password' => 'password', + 'email' => 'john@demo', + 'role' => 'admin', + ]; + } + } + + $centralUser = CentralUserWithDefaultValues::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', // unsynced + ]); + + $tenant = ResourceTenant::create([ + 'id' => 't1', + ]); + migrateTenantsResource(); + + $tenant->run(function () { + expect(ResourceUser::all())->toHaveCount(0); + }); + + $centralUser->tenants()->attach('t1'); + + $tenant->run(function () { + expect(ResourceUser::all())->toHaveCount(1); + expect(ResourceUser::first()->global_id)->toBe('abc-123'); + expect(ResourceUser::first()->name)->toBe('John'); + expect(ResourceUser::first()->password)->toBe('password'); + expect(ResourceUser::first()->email)->toBe('john@demo'); + expect(ResourceUser::first()->role)->toBe('admin'); + }); +}); + +test('creating the resource in central database creates it in tenant database with provided attributes names', function () { + // override method in ResourceUser class to return attribute values + class CentralUserWithAttributeNames extends CentralUser { + public function getResourceCreationAttributes(): array + { + // Attributes used when creating resources from central to tenant DB + return + [ + 'global_id', + 'name', + 'password', + 'email', + 'role', + ]; + } + } + + // migrate extra column in central DB + pest()->artisan('migrate', [ + '--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra', + '--realpath' => true, + ])->assertExitCode(0); + + $centralUser = CentralUserWithAttributeNames::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', + 'foo' => 'bar', // foo does not exist in resource + ]); + + $tenant = ResourceTenant::create([ + 'id' => 't1', + ]); + migrateTenantsResource(); + + $tenant->run(function () { + expect(ResourceUser::all())->toHaveCount(0); + }); + + $centralUser->tenants()->attach('t1'); + + $tenant->run(function () { + expect(ResourceUser::all())->toHaveCount(1); + expect(ResourceUser::first()->global_id)->toBe('acme'); + expect(ResourceUser::first()->foo)->toBeNull(); // assert foo is not copied from the central to tenant model + }); +}); + test('creating the resource in tenant database creates it in central database and creates the mapping', function () { creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase(); }); @@ -627,11 +836,6 @@ class CentralUser extends Model implements SyncMaster 'email', ]; } - - public function getCreateAttributeNames(): array - { - return []; - } } class ResourceUser extends Model implements Syncable @@ -667,16 +871,4 @@ class ResourceUser extends Model implements Syncable 'email', ]; } - - public function getCreateAttributeNames(): array - { - // Attributes used when creating resources from tenant to central DB - return [ - 'global_id', - 'name', - 'password', - 'email', - 'role' - ]; - } }