diff --git a/app/Domains/Authorization/Models/Permission.php b/app/Domains/Authorization/Models/Permission.php new file mode 100644 index 0000000..2c59269 --- /dev/null +++ b/app/Domains/Authorization/Models/Permission.php @@ -0,0 +1,32 @@ + + */ + public function roles(): BelongsToMany + { + return $this->belongsToMany( + Role::class, + 'roles_permisos', + 'codigo_permiso', + 'rol_codigo', + 'codigo', + 'codigo' + )->withTimestamps(); + } +} diff --git a/app/Domains/Authorization/Models/Role.php b/app/Domains/Authorization/Models/Role.php new file mode 100644 index 0000000..bc9abc0 --- /dev/null +++ b/app/Domains/Authorization/Models/Role.php @@ -0,0 +1,32 @@ + + */ + public function permissions(): BelongsToMany + { + return $this->belongsToMany( + Permission::class, + 'roles_permisos', + 'rol_codigo', + 'codigo_permiso', + 'codigo', + 'codigo' + )->withTimestamps(); + } +} diff --git a/app/Domains/Authorization/Models/RolePermission.php b/app/Domains/Authorization/Models/RolePermission.php new file mode 100644 index 0000000..c9a8b15 --- /dev/null +++ b/app/Domains/Authorization/Models/RolePermission.php @@ -0,0 +1,32 @@ + + */ + public function permission(): BelongsTo + { + return $this->belongsTo(Permission::class, 'codigo_permiso', 'codigo'); + } + + /** + * @return BelongsTo + */ + public function role(): BelongsTo + { + return $this->belongsTo(Role::class, 'rol_codigo', 'codigo'); + } +} diff --git a/database/migrations/2026_07_28_000000_create_roles_and_permissions_tables.php b/database/migrations/2026_07_28_000000_create_roles_and_permissions_tables.php new file mode 100644 index 0000000..cd42e96 --- /dev/null +++ b/database/migrations/2026_07_28_000000_create_roles_and_permissions_tables.php @@ -0,0 +1,53 @@ +id(); + $table->string('codigo')->unique(); + $table->string('nombre'); + $table->text('descripcion')->nullable(); + $table->timestamps(); + }); + + Schema::create('roles', function (Blueprint $table): void { + $table->id(); + $table->string('codigo')->unique(); + $table->string('nombre'); + $table->text('descripcion')->nullable(); + $table->timestamps(); + }); + + Schema::create('roles_permisos', function (Blueprint $table): void { + $table->id(); + $table->string('codigo_permiso'); + $table->string('rol_codigo'); + $table->timestamps(); + + $table->foreign('codigo_permiso') + ->references('codigo') + ->on('permisos') + ->cascadeOnUpdate() + ->cascadeOnDelete(); + $table->foreign('rol_codigo') + ->references('codigo') + ->on('roles') + ->cascadeOnUpdate() + ->cascadeOnDelete(); + $table->unique(['rol_codigo', 'codigo_permiso']); + }); + } + + public function down(): void + { + Schema::dropIfExists('roles_permisos'); + Schema::dropIfExists('roles'); + Schema::dropIfExists('permisos'); + } +}; diff --git a/tests/Feature/Authorization/RolePermissionModelTest.php b/tests/Feature/Authorization/RolePermissionModelTest.php new file mode 100644 index 0000000..5bd7d11 --- /dev/null +++ b/tests/Feature/Authorization/RolePermissionModelTest.php @@ -0,0 +1,126 @@ +assertEqualsCanonicalizing([ + 'id', + 'codigo', + 'nombre', + 'descripcion', + 'created_at', + 'updated_at', + ], Schema::getColumnListing('permisos')); + + $this->assertEqualsCanonicalizing([ + 'id', + 'codigo', + 'nombre', + 'descripcion', + 'created_at', + 'updated_at', + ], Schema::getColumnListing('roles')); + + $this->assertEqualsCanonicalizing([ + 'id', + 'codigo_permiso', + 'rol_codigo', + 'created_at', + 'updated_at', + ], Schema::getColumnListing('roles_permisos')); + } + + public function test_a_role_and_permission_can_be_related_by_their_codes(): void + { + $role = Role::query()->create([ + 'codigo' => 'administrador', + 'nombre' => 'Administrador', + 'descripcion' => 'Administra la tienda.', + ]); + $permission = Permission::query()->create([ + 'codigo' => 'catalogo.editar', + 'nombre' => 'Editar catálogo', + ]); + + $role->permissions()->attach($permission->codigo); + + $this->assertTrue($role->permissions()->firstOrFail()->is($permission)); + $this->assertTrue($permission->roles()->firstOrFail()->is($role)); + $this->assertDatabaseHas('roles_permisos', [ + 'rol_codigo' => $role->codigo, + 'codigo_permiso' => $permission->codigo, + ]); + } + + public function test_the_pivot_model_exposes_its_role_and_permission(): void + { + $role = Role::query()->create([ + 'codigo' => 'editor', + 'nombre' => 'Editor', + ]); + $permission = Permission::query()->create([ + 'codigo' => 'catalogo.editar', + 'nombre' => 'Editar catálogo', + ]); + $assignment = RolePermission::query()->create([ + 'rol_codigo' => $role->codigo, + 'codigo_permiso' => $permission->codigo, + ]); + + $this->assertTrue($assignment->role->is($role)); + $this->assertTrue($assignment->permission->is($permission)); + } + + public function test_a_permission_cannot_be_assigned_twice_to_the_same_role(): void + { + $role = Role::query()->create([ + 'codigo' => 'editor', + 'nombre' => 'Editor', + ]); + $permission = Permission::query()->create([ + 'codigo' => 'catalogo.editar', + 'nombre' => 'Editar catálogo', + ]); + $role->permissions()->attach($permission->codigo); + + $this->expectException(QueryException::class); + + $role->permissions()->attach($permission->codigo); + } + + public function test_deleting_a_role_removes_its_permission_assignments(): void + { + $role = Role::query()->create([ + 'codigo' => 'editor', + 'nombre' => 'Editor', + ]); + $permission = Permission::query()->create([ + 'codigo' => 'catalogo.editar', + 'nombre' => 'Editar catálogo', + ]); + $role->permissions()->attach($permission->codigo); + + $role->delete(); + + $this->assertDatabaseMissing('roles_permisos', [ + 'rol_codigo' => $role->codigo, + 'codigo_permiso' => $permission->codigo, + ]); + $this->assertDatabaseHas('permisos', [ + 'codigo' => $permission->codigo, + ]); + } +}