1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-05 21:34:03 +00:00
This commit is contained in:
Abrar Ahmad 2022-09-30 18:34:43 +05:00
parent f614a02860
commit cb73936d71
2 changed files with 233 additions and 222 deletions

View file

@ -147,7 +147,7 @@ class UpdateSyncedResource extends QueueableListener
// We will merge the default values with sync attributes // We will merge the default values with sync attributes
[$attributes, $defaultValues] = $this->getAttributeNamesAndDefaultValues($model); [$attributes, $defaultValues] = $this->getAttributeNamesAndDefaultValues($model);
return array_merge($defaultValues, $model->only(array_merge($model->getSyncedAttributeNames(), $attributes))); return array_merge($model->only(array_merge($model->getSyncedAttributeNames(), $attributes)), $defaultValues);
} }
// Developer provided the attribute names, so we'd use them to pick model attributes // Developer provided the attribute names, so we'd use them to pick model attributes

View file

@ -127,191 +127,20 @@ test('only the synced columns are updated in the central db', function () {
], ResourceUser::first()->getAttributes()); ], ResourceUser::first()->getAttributes());
}); });
test('creating the resource in tenant database creates it in central database as a direct copy when creation attributes are not specified', function () { // ====================
// Assert no user exists in central DB test('sync resource creation works when central model provides attributes and resource model provides default values', function (){
expect(ResourceUser::all())->toHaveCount(0); /**
* when central model provides attributes => resoucre model will be created from the attribute values
* when resource model provides default values => central model will be created using the default values
*/
[$tenant1, $tenant2] = [ResourceTenant::create(['id' => 't1']), ResourceTenant::create(['id' => 't2'])];
$tenant = ResourceTenant::create();
migrateTenantsResource();
tenancy()->initialize($tenant);
// Create the user in tenant DB
$resourceUser = ResourceUser::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
]);
tenancy()->end();
// Assert central user and Resource user has exact same attributes and values
expect($resourceUser->getSyncedCreationAttributes())->toBeNull();
expect(CentralUser::first()->toArray())->toEqual(ResourceUser::first()->toArray());
});
test('creating the resource in tenant database creates it in central database with default attribute values', function () {
// 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();
// Assert model attributes are synced
expect(CentralUser::first()->global_id)->toBe('acme');
expect(CentralUser::first()->name)->toBe('John Doe');
expect(CentralUser::first()->password)->toBe('secret');
expect(CentralUser::first()->email)->toBe('john@localhost');
// Assert the "role" attribute is unsynced and we are using the default value
expect(CentralUser::first()->role)->toBe('admin');
});
test('creating the resource in tenant database creates it in central database with attributes names', function () {
// 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',
'--realpath' => true,
])->assertExitCode(0);
tenancy()->initialize($tenant);
// Create the user in tenant DB
ResourceUserWithAttributeNames::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // unsynced
'code' => 'bar' // extra column which does not exist in central users table
]);
tenancy()->end();
// Assert central user was created without `code` property
expect(CentralUser::first()->global_id)->toBe('acme');
expect(CentralUser::first()->code)->toBeNull();
});
test('creating the resource in tenant database creates it in central database with a mix of attributes names and default values', function () {
// Assert no user exists in central DB
expect(ResourceUserWithAttributeNamesAndDefaultValues::all())->toHaveCount(0);
$tenant = ResourceTenant::create();
pest()->artisan('tenants:migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/custom',
'--realpath' => true,
])->assertExitCode(0);
tenancy()->initialize($tenant);
// Create the user in tenant DB
ResourceUserWithAttributeNamesAndDefaultValues::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // this will not be synced because we are providing default value
'code' => 'bar' // extra column which does not exist in central users table
]);
tenancy()->end();
// Assert central user was created without `code` property
expect(CentralUser::first()->global_id)->toBe('acme');
expect(CentralUser::first()->name)->toBe('John Doe');
expect(CentralUser::first()->email)->toBe('john@localhost');
expect(CentralUser::first()->password)->toBe('secret');
expect(CentralUser::first()->code)->toBeNull();
expect(CentralUser::first()->role)->toBe('admin'); // unsynced so it should be default value
});
test('creating the resource in central database creates it in tenant database as direct copy when creation attributes are not specified', 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');
$centralUser = CentralUser::first();
expect($centralUser->getSyncedCreationAttributes())->toBeNull();
$tenant->run(function () use ($centralUser) {
expect(ResourceUser::all())->toHaveCount(1);
expect(ResourceUser::first()->toArray())->toEqual($centralUser->toArray());
});
});
test('creating the resource in central database creates it in tenant database with default attributes values', function () {
$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);
// Assert model attributes are synced
expect(ResourceUser::first()->global_id)->toBe('acme');
expect(ResourceUser::first()->name)->toBe('John Doe');
expect(ResourceUser::first()->password)->toBe('secret');
expect(ResourceUser::first()->email)->toBe('john@localhost');
// Assert the "role" attribute is unsynced and we are using the default value
expect(ResourceUser::first()->role)->toBe('admin');
});
});
test('creating the resource in central database creates it in tenant database with attributes names', function () {
// migrate extra column "foo" in central DB // migrate extra column "foo" in central DB
pest()->artisan('migrate', [ pest()->artisan('migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra', '--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra',
'--realpath' => true, '--realpath' => true,
])->assertExitCode(0); ])->assertExitCode(0);
migrateTenantsResource();
$centralUser = CentralUserWithAttributeNames::create([ $centralUser = CentralUserWithAttributeNames::create([
'global_id' => 'acme', 'global_id' => 'acme',
@ -322,55 +151,229 @@ test('creating the resource in central database creates it in tenant database wi
'foo' => 'bar', // foo does not exist in resource model 'foo' => 'bar', // foo does not exist in resource model
]); ]);
$tenant = ResourceTenant::create([ $tenant1->run(function () {
'id' => 't1', expect(ResourceUserWithDefaultValues::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
$tenant1->run(function () {
// assert resource model created with provided attributes
$resourceUser = ResourceUserWithDefaultValues::all();
expect($resourceUser)->toHaveCount(1);
expect($resourceUser->first()->global_id)->toBe('acme');
expect($resourceUser->first()->email)->toBe('john@localhost');
// 'foo' attribute is not provided by central model
expect($resourceUser->first()->foo)->toBeNull();
});
tenancy()->initialize($tenant2);
// Create the user in tenant DB
ResourceUserWithDefaultValues::create([
'global_id' => 'asdf',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
]); ]);
tenancy()->end();
// Assert central user was created using the default values
$centralUser = CentralUserWithAttributeNames::whereGlobalId('asdf')->first();
expect($centralUser)->not()->toBeNull();
expect($centralUser->global_id)->toBe('asdf');
expect($centralUser->name)->toBe('Default Name');
expect($centralUser->email)->toBe('default@localhost');
expect($centralUser->password)->toBe('password');
expect($centralUser->role)->toBe('admin');
expect($centralUser->foo)->toBe('bar');
})->group('creation');
test('sync resource creation works when central model provides default values and resource model provides attributes', function (){
/**
* when central model provides default values => resource model will be created using the default values
* when resource model provides attributes => central model will be created from the attribute values
*/
[$tenant1, $tenant2] = [ResourceTenant::create(['id' => 't1']), ResourceTenant::create(['id' => 't2'])];
// migrate extra column "foo" in central DB
pest()->artisan('migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra',
'--realpath' => true,
])->assertExitCode(0);
migrateTenantsResource(); migrateTenantsResource();
$tenant->run(function () { $centralUser = CentralUserWithDefaultValues::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
'foo' => 'bar', // foo does not exist in resource model
]);
$tenant1->run(function () {
expect(ResourceUserWithDefaultValues::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
$tenant1->run(function () {
// assert resource model created with provided default values
$resourceUser = ResourceUserWithDefaultValues::first();
expect($resourceUser)->not()->toBeNull();
expect($resourceUser->global_id)->toBe('acme');
expect($resourceUser->email)->toBe('default@localhost');
expect($resourceUser->password)->toBe('password');
expect($resourceUser->role)->toBe('admin');
});
tenancy()->initialize($tenant2);
// Create the user in tenant DB
ResourceUserWithAttributeNames::create([
'global_id' => 'asdf',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
]);
tenancy()->end();
// Assert central user was created using the provided attributes
$centralUser = CentralUserWithAttributeNames::whereGlobalId('asdf')->first();
expect($centralUser)->not()->toBeNull();
expect($centralUser->global_id)->toBe('asdf');
expect($centralUser->email)->toBe('john@localhost');
expect($centralUser->password)->toBe('secret');
expect($centralUser->role)->toBe('commenter');
})->group('creation');
test('sync resource creation works when central model provides mixture and resource model provides nothing', function (){
/**
* when central model provides mix of attribute and default values => resource model will be created using the mix of attribute values and default values
* when resource model provides nothing => central model will be 1:1 copy
*/
[$tenant1, $tenant2] = [ResourceTenant::create(['id' => 't1']), ResourceTenant::create(['id' => 't2'])];
pest()->artisan('migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra',
'--realpath' => true,
])->assertExitCode(0);
migrateTenantsResource();
$centralUser = CentralUserWithAttributeNamesAndDefaultValues::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
'foo' => 'bar', // foo does not exist in resource model
]);
$tenant1->run(function () {
expect(ResourceUser::all())->toHaveCount(0); expect(ResourceUser::all())->toHaveCount(0);
}); });
$centralUser->tenants()->attach('t1'); $centralUser->tenants()->attach('t1');
$tenant->run(function () { $tenant1->run(function () {
expect(ResourceUser::all())->toHaveCount(1); $resourceUser = ResourceUser::first();
expect(ResourceUser::first()->global_id)->toBe('acme'); expect($resourceUser)->not()->toBeNull();
expect(ResourceUser::first()->foo)->toBeNull(); // assert foo is not copied from the central to tenant model
}); // Provided attributes
expect($resourceUser->global_id)->toBe('acme');
expect($resourceUser->email)->toBe('john@localhost');
// Provided default values
expect($resourceUser->password)->toBe('password');
expect(ResourceUser::first()->role)->toBe('admin');
}); });
test('creating the resource in central database creates it in tenant database with a mix of attributes names and default values', function () {
$centralUser = CentralUserWithAttributeNamesAndDefaultValues::create([ // remove foo from the central model so both models has equal attributes to do 1:1 copy
\Illuminate\Support\Facades\Schema::table('users', function (\Illuminate\Database\Schema\Blueprint $table) {
$table->dropColumn('foo');
});
tenancy()->initialize($tenant2);
// Create the user in tenant DB
$resourceUser = ResourceUser::create([
'id' => random_int(10, 100),
'global_id' => 'absd',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
]);
tenancy()->end();
// Assert central user was created without `code` property
$centralUser = CentralUser::whereGlobalId('absd')->first();
expect($centralUser)->not()->toBeNull();
expect($centralUser->toArray())->toBe($resourceUser->toArray());
})->group('creation');
test('sync resource creation works when central model provides nothing and resource model provides mixture', function (){
/**
* when central model provides nothing => resoucre model will be 1:1 copy
* when resource model provides mix of attribute and default values => central model will be created using the mix of attribute values and default values
*/
[$tenant1, $tenant2] = [ResourceTenant::create(['id' => 't1']), ResourceTenant::create(['id' => 't2'])];
migrateTenantsResource();
$centralUser = CentralUser::create([
'global_id' => 'acme', 'global_id' => 'acme',
'name' => 'John Doe', 'name' => 'John Doe',
'email' => 'john@localhost', 'email' => 'john@localhost',
'password' => 'secret', 'password' => 'secret',
'role' => 'commenter',
]);
$tenant1->run(function () {
expect(ResourceUserWithAttributeNamesAndDefaultValues::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
expect($centralUser->getSyncedCreationAttributes())->toBeNull();
$tenant1->run(function () use ($centralUser) {
$resourceUser = ResourceUserWithAttributeNamesAndDefaultValues::first();
expect($resourceUser)->not()->toBeNull();
expect($resourceUser->toArray())->toEqual($centralUser->withoutRelations()->toArray());
});
tenancy()->initialize($tenant2);
// Create the user in tenant DB
ResourceUserWithAttributeNamesAndDefaultValues::create([
'global_id' => 'absd',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // this will not be synced because we are providing default value 'role' => 'commenter', // this will not be synced because we are providing default value
]); ]);
$tenant = ResourceTenant::create([ tenancy()->end();
'id' => 't1',
]);
migrateTenantsResource();
$tenant->run(function () { // Assert central user was created without `code` property
expect(ResourceUser::all())->toHaveCount(0); $centralUser = CentralUser::whereGlobalId('absd')->first();
}); expect($centralUser)->not()->toBeNull();
expect($centralUser->name)->toBe('John Doe');
expect($centralUser->email)->toBe('john@localhost');
$centralUser->tenants()->attach('t1'); // default values provided by resoucre model
expect($centralUser->password)->toBe('password');
expect($centralUser->role)->toBe('admin');
})->group('creation');
$tenant->run(function () { test('sync resource creation works when central model provides attributes and resource model provides default values havind different schemas ', function (){
expect(ResourceUser::all())->toHaveCount(1);
expect(ResourceUser::first()->global_id)->toBe('acme');
expect(CentralUser::first()->name)->toBe('John Doe');
expect(CentralUser::first()->email)->toBe('john@localhost');
expect(CentralUser::first()->password)->toBe('secret');
expect(ResourceUser::first()->role)->toBe('admin'); // default value
});
});
test('sync resources work when the central model creation method returns attribute names and the resource model creation method returns default values ', function (){
// migrate central_users table and tenant_central_users pivot table // migrate central_users table and tenant_central_users pivot table
pest()->artisan('migrate', [ pest()->artisan('migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/custom/central', '--path' => __DIR__ . '/Etc/synced_resource_migrations/custom/central',
@ -429,8 +432,8 @@ test('sync resources work when the central model creation method returns attribu
expect($centralUserWithExtraAttributes->password)->toBe('secret'); expect($centralUserWithExtraAttributes->password)->toBe('secret');
expect($centralUserWithExtraAttributes->code)->toBe('foo'); expect($centralUserWithExtraAttributes->code)->toBe('foo');
expect($centralUserWithExtraAttributes->role)->toBe('admin'); expect($centralUserWithExtraAttributes->role)->toBe('admin');
}); })->group('creation');
// ================
test('creating the resource in tenant database creates it in central database and creates the mapping', function () { test('creating the resource in tenant database creates it in central database and creates the mapping', function () {
creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase(); creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase();
}); });
@ -946,10 +949,14 @@ class ResourceUser extends Model implements Syncable
class ResourceUserWithDefaultValues extends ResourceUser { class ResourceUserWithDefaultValues extends ResourceUser {
public function getSyncedCreationAttributes(): array public function getSyncedCreationAttributes(): array
{ {
// Attributes default values when creating resources from tenant to central DB // Default values when creating resources from tenant to central DB
return return
[ [
'role' => 'admin', // Provide "role" default value because it is unsynced or does not exist in Resource model 'name' => 'Default Name',
'email' => 'default@localhost',
'password' => 'password',
'role' => 'admin',
'foo' => 'bar'
]; ];
} }
} }
@ -963,11 +970,12 @@ class ResourceUserWithAttributeNames extends ResourceUser {
// exist in central model // exist in central model
return return
[ [
'global_id', 'global_id', // todo@1 remove it
'name', 'name',
'password', 'password',
'email', 'email',
'role' 'role',
'foo' => 'bar'
]; ];
} }
@ -977,17 +985,16 @@ class ResourceUserWithAttributeNames extends ResourceUser {
class ResourceUserWithAttributeNamesAndDefaultValues extends ResourceUser { class ResourceUserWithAttributeNamesAndDefaultValues extends ResourceUser {
public function getSyncedCreationAttributes(): array public function getSyncedCreationAttributes(): array
{ {
// Sync name, email and password but provide default value for role // Sync name, email and password but provide default value for role and password
return return
[ [
'global_id', 'global_id',
'name', 'name',
'password',
'email', 'email',
'role' => 'admin' // default value 'password' => 'password',
'role' => 'admin'
]; ];
} }
} }
// override method in CentralUser class to return attribute default values // override method in CentralUser class to return attribute default values
@ -997,7 +1004,11 @@ class CentralUserWithDefaultValues extends CentralUser {
// Attributes default values when creating resources from central to tenant model // Attributes default values when creating resources from central to tenant model
return return
[ [
'role' => 'admin', // Provide "role" default value because it is unsynced or does not exist in Central model 'global_id',
'name' => 'Default User',
'email' => 'default@localhost',
'password' => 'password',
'role' => 'admin',
]; ];
} }
} }
@ -1027,8 +1038,8 @@ class CentralUserWithAttributeNamesAndDefaultValues extends CentralUser {
[ [
'global_id', 'global_id',
'name', 'name',
'password',
'email', 'email',
'password' => 'password',
'role' => 'admin', 'role' => 'admin',
]; ];
} }