mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 01:14:03 +00:00
Update the scopeGetModelQuery test so that it tests a realistic case
This commit is contained in:
parent
880622877d
commit
03bf20732f
1 changed files with 46 additions and 36 deletions
|
|
@ -1198,67 +1198,70 @@ test('resource creation works correctly when central resource provides defaults
|
||||||
expect($centralUser->foo)->toBe('bar');
|
expect($centralUser->foo)->toBe('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('scopeGetModelQuery can make resource syncing work with global scopes', function (bool $scopeGetModelQuery) {
|
test('scopeGetModelQuery allows customizing the query used to find resources during syncing', function (bool $scopeGetModelQuery) {
|
||||||
// The TenantUserWithScope model has whereNull('name') global scope, but we're creating users WITH names.
|
[$tenant1, $tenant2] = createTenantsAndRunMigrations();
|
||||||
// This creates a conflict during resource syncing when trying to find existing central users.
|
migrateUsersTableForTenants();
|
||||||
|
addExtraColumns(true);
|
||||||
|
|
||||||
|
TenantUserWithSoftDeletes::$creationAttributes = [
|
||||||
|
'role' => 'role',
|
||||||
|
'foo' => 'foo',
|
||||||
|
];
|
||||||
|
|
||||||
if ($scopeGetModelQuery) {
|
if ($scopeGetModelQuery) {
|
||||||
// Add a scope that bypasses the global scope
|
// Configure resource syncing to include soft deleted records
|
||||||
|
// when looking up resources (the documented use case for $scopeGetModelQuery)
|
||||||
UpdateOrCreateSyncedResource::$scopeGetModelQuery = function (Builder $query) {
|
UpdateOrCreateSyncedResource::$scopeGetModelQuery = function (Builder $query) {
|
||||||
if ($query->getModel()->hasGlobalScope(TestingScope::class)) {
|
if ($query->hasMacro('withTrashed')) {
|
||||||
$query->withoutGlobalScope(TestingScope::class);
|
$query->withTrashed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a central user that will be synced to tenant databases
|
// Create a central user with soft deletes
|
||||||
$centralUser = CentralUser::create([
|
$centralUser = CentralUserWithSoftDeletes::create([
|
||||||
|
'global_id' => 'user',
|
||||||
'email' => 'user@test.cz',
|
'email' => 'user@test.cz',
|
||||||
'name' => 'User', // Note: TenantUserWithScope has whereNull('name') global scope
|
'name' => 'User',
|
||||||
'password' => bcrypt('****'),
|
'password' => bcrypt('****'),
|
||||||
'role' => 'admin',
|
'role' => 'admin',
|
||||||
|
'foo' => 'bar',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
[$tenant1, $tenant2] = createTenantsAndRunMigrations();
|
$centralUser->tenants()->attach($tenant1);
|
||||||
|
|
||||||
// Create user in tenant1
|
// Soft delete the central user
|
||||||
tenancy()->initialize($tenant1);
|
$centralUser->delete();
|
||||||
|
|
||||||
TenantUserWithScope::create([
|
// Try to create a tenant resource with the same global_id in tenant2
|
||||||
'global_id' => $centralUser->global_id,
|
|
||||||
'name' => $centralUser->name, // This has a name, conflicting with global scope
|
|
||||||
'email' => $centralUser->email,
|
|
||||||
'password' => $centralUser->password,
|
|
||||||
'role' => 'admin'
|
|
||||||
]);
|
|
||||||
|
|
||||||
tenancy()->end();
|
|
||||||
|
|
||||||
// Try to create the same user in tenant2
|
|
||||||
tenancy()->initialize($tenant2);
|
tenancy()->initialize($tenant2);
|
||||||
|
|
||||||
if (! $scopeGetModelQuery) {
|
if (! $scopeGetModelQuery) {
|
||||||
// Without scopeGetModelQuery, the global scope whereNull('name') prevents
|
// WITHOUT scopeGetModelQuery: syncing can't find the soft-deleted central user
|
||||||
// finding the existing central user with a name.
|
// and tries to create a new one, violating the unique constraint on global_id
|
||||||
// This causes it to attempt creating a duplicate, which violates unique constraints.
|
|
||||||
pest()->expectException(QueryException::class);
|
pest()->expectException(QueryException::class);
|
||||||
pest()->expectExceptionMessage('Duplicate entry');
|
pest()->expectExceptionMessage('Duplicate entry');
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should sync to the existing central user, not create a duplicate
|
// Create a tenant resource - should sync with the existing soft-deleted central user
|
||||||
TenantUserWithScope::create([
|
TenantUserWithSoftDeletes::create([
|
||||||
'global_id' => $centralUser->global_id,
|
'global_id' => 'user',
|
||||||
'name' => $centralUser->name,
|
'name' => 'Updated Name',
|
||||||
'email' => $centralUser->email,
|
'email' => 'updated@example.com',
|
||||||
'password' => $centralUser->password,
|
'password' => bcrypt('password'),
|
||||||
'role' => 'admin'
|
'role' => 'admin',
|
||||||
|
'foo' => 'bar',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
tenancy()->end();
|
tenancy()->end();
|
||||||
|
|
||||||
// Verify the syncing worked correctly when scopeGetModelQuery was used
|
// With scopeGetModelQuery, verify the soft-deleted central user was found and updated
|
||||||
if ($scopeGetModelQuery) {
|
if ($scopeGetModelQuery) {
|
||||||
expect(CentralUser::count())->toBe(1); // Should still be just one central user
|
$updatedCentralUser = CentralUserWithSoftDeletes::withTrashed()->where('global_id', 'user')->first();
|
||||||
expect(CentralUser::first()->global_id)->toBe($centralUser->global_id);
|
|
||||||
|
expect($updatedCentralUser->name)->toBe('Updated Name');
|
||||||
|
expect($updatedCentralUser->trashed())->toBeTrue(); // Still soft deleted
|
||||||
|
expect(CentralUserWithSoftDeletes::withTrashed()->where('global_id', 'user')->count())->toBe(1);
|
||||||
}
|
}
|
||||||
})->with([
|
})->with([
|
||||||
true,
|
true,
|
||||||
|
|
@ -1443,6 +1446,13 @@ class TenantCompany extends Model implements Syncable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An artificial test global scope that only shows users with null names.
|
||||||
|
* This simulates real-world scenarios where tenant models might have:
|
||||||
|
* - Row Level Security (RLS) policies based on session variables
|
||||||
|
* - User-specific data filtering based on permissions
|
||||||
|
* - Other scoping mechanisms that could interfere with resource syncing
|
||||||
|
*/
|
||||||
class TestingScope implements Scope
|
class TestingScope implements Scope
|
||||||
{
|
{
|
||||||
public function apply(Builder $builder, Model $model): void
|
public function apply(Builder $builder, Model $model): void
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue