1
0
Fork 0
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:
lukinovec 2025-06-04 12:58:16 +02:00
parent 880622877d
commit 03bf20732f

View file

@ -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