diff --git a/app/Models/User.php b/app/Models/User.php index ff47358..b54d95c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -26,7 +26,8 @@ class User extends Authenticatable public function hasRole(string $role): bool { - return $this->role()->where('roles.name', $role)->exists(); + $this->loadMissing('role'); + return $this->role->contains('name', $role); } public function tenant() @@ -58,7 +59,9 @@ class User extends Authenticatable protected static function booted(): void { static::deleting(function (User $user) { - // Soft-delete cascade: DB foreign key cascades don't fire on soft-delete + if ($user->isForceDeleting()) { + return; // FK cascadeOnDelete handles hard-delete + } \DB::table('role_user')->where('user_id', $user->id)->delete(); \DB::table('license_assignments')->where('user_id', $user->id)->delete(); \DB::table('parent_child') diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000003_create_users_table.php similarity index 100% rename from database/migrations/0001_01_01_000000_create_users_table.php rename to database/migrations/0001_01_01_000003_create_users_table.php diff --git a/database/migrations/2026_05_22_005035_create_role_user_table.php b/database/migrations/2026_05_22_005036_create_role_user_table.php similarity index 100% rename from database/migrations/2026_05_22_005035_create_role_user_table.php rename to database/migrations/2026_05_22_005036_create_role_user_table.php diff --git a/database/migrations/2026_05_22_005036_create_license_assignments_table.php b/database/migrations/2026_05_22_005037_create_license_assignments_table.php similarity index 100% rename from database/migrations/2026_05_22_005036_create_license_assignments_table.php rename to database/migrations/2026_05_22_005037_create_license_assignments_table.php diff --git a/database/migrations/2026_05_22_005036_create_parent_child_table.php b/database/migrations/2026_05_22_005038_create_parent_child_table.php similarity index 100% rename from database/migrations/2026_05_22_005036_create_parent_child_table.php rename to database/migrations/2026_05_22_005038_create_parent_child_table.php diff --git a/tests/Unit/Models/HasUuidTest.php b/tests/Feature/Models/HasUuidTest.php similarity index 100% rename from tests/Unit/Models/HasUuidTest.php rename to tests/Feature/Models/HasUuidTest.php diff --git a/tests/Feature/Tenant/TenantIsolationTest.php b/tests/Feature/Tenant/TenantIsolationTest.php index a219c7b..b782005 100644 --- a/tests/Feature/Tenant/TenantIsolationTest.php +++ b/tests/Feature/Tenant/TenantIsolationTest.php @@ -3,6 +3,7 @@ use App\Models\Tenant; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use function Pest\Laravel\actingAs; uses(RefreshDatabase::class); @@ -12,6 +13,6 @@ it('verhindert Zugriff auf Daten anderer Tenants', function () { $user1 = User::factory()->for($tenant1)->create(); $user2 = User::factory()->for($tenant2)->create(); - $this->actingAs($user1); + actingAs($user1); expect(User::all()->pluck('id'))->not->toContain($user2->id); }); diff --git a/tests/Feature/Tenant/TenantWritePathTest.php b/tests/Feature/Tenant/TenantWritePathTest.php new file mode 100644 index 0000000..7215913 --- /dev/null +++ b/tests/Feature/Tenant/TenantWritePathTest.php @@ -0,0 +1,23 @@ +create(); + $tenant2 = Tenant::factory()->create(); + $user = User::factory()->for($tenant1)->create(); + + actingAs($user); + $license = License::create([ + 'type' => 'per_student', + 'tenant_id' => $tenant2->id, // attempt cross-tenant write + ]); + + expect($license->tenant_id)->toBe($tenant1->id); +});