diff --git a/app/Http/Controllers/Api/V1/RoleController.php b/app/Http/Controllers/Api/V1/RoleController.php index 9edf4200..e9390aab 100755 --- a/app/Http/Controllers/Api/V1/RoleController.php +++ b/app/Http/Controllers/Api/V1/RoleController.php @@ -15,6 +15,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; use Illuminate\Support\Facades\Validator; class RoleController extends Controller @@ -25,32 +26,70 @@ class RoleController extends Controller */ public function store(StoreRoleRequest $request) { + Log::info('Incoming role creation request', $request->all()); + try { DB::beginTransaction(); - // creating the role + $org_id = $request->organisation_id; + + // Validate the organisation ID as a UUID + if (!preg_match('/^[a-f0-9-]{36}$/', $org_id)) { + return response()->json([ + 'status_code' => Response::HTTP_BAD_REQUEST, + 'error' => 'Invalid input', + 'message' => 'Invalid organisation ID format', + ], Response::HTTP_BAD_REQUEST); + } + + // Check whether the organisation exists + $organisation = Organisation::find($org_id); + if (!$organisation) { + return response()->json([ + 'status_code' => Response::HTTP_NOT_FOUND, + 'error' => 'Organisation not found', + 'message' => 'The organisation with ID ' . $org_id . ' does not exist', + ], Response::HTTP_NOT_FOUND); + } + + // Check for duplicate role name within the organisation + $existingRole = Role::where('org_id', $org_id)->where('name', $request->role_name)->first(); + if ($existingRole) { + return response()->json([ + 'status_code' => Response::HTTP_CONFLICT, + 'error' => 'Conflict', + 'message' => 'A role with this name already exists in the organisation', + ], Response::HTTP_CONFLICT); + } + + // Creating the role $role = Role::create([ 'name' => $request->role_name, - 'org_id' => $request->organisation_id, + 'description' => $request->description, + 'org_id' => $org_id, ]); + // Attach the permission to the role $role->permissions()->attach($request->permissions_id); - DB::commit(); - $code = Response::HTTP_CREATED; + DB::commit(); return response()->json([ + 'id' => $role->id, + 'name' => $role->name, + 'description' => $role->description, 'message' => "Role created successfully", - 'status_code' => $code, - ], $code); + 'status_code' => Response::HTTP_CREATED, + ], Response::HTTP_CREATED); } catch (\Exception $e) { DB::rollBack(); Log::error('Role creation error: ' . $e->getMessage()); - $code = Response::HTTP_BAD_REQUEST; + return response()->json([ - 'message' => "Role creation failed - " . $e->getMessage(), - 'status_code' => $code, - ], $code); + 'status_code' => Response::HTTP_BAD_REQUEST, + 'error' => 'Invalid input', + 'message' => 'Role creation failed - ' . $e->getMessage(), + ], Response::HTTP_BAD_REQUEST); } } @@ -148,6 +187,149 @@ public function update(UpdateRoleRequest $request, $org_id, $role_id) } else return ResponseHelper::response("Organisation not found", 404, null); } + // To get all roles + public function index($org_id) + { + try { + // Validate the organisation ID (UUID format) + if (!Str::isUuid($org_id)) { + return response()->json([ + 'status_code' => Response::HTTP_BAD_REQUEST, + 'error' => 'Bad Request', + 'message' => 'Invalid organisation ID format', + ], Response::HTTP_BAD_REQUEST); + } + + // Check whether organisation exists + $organisation = Organisation::where('org_id', $org_id)->first(); + if (!$organisation) { + return response()->json([ + 'status_code' => Response::HTTP_NOT_FOUND, + 'error' => 'Not Found', + 'message' => 'The organisation with ID ' . $org_id . ' does not exist', + ], Response::HTTP_NOT_FOUND); + } + + // Fetch all roles within the organisation + $roles = Role::where('org_id', $org_id)->get(['id', 'name', 'description']); + + return response()->json([ + 'status_code' => Response::HTTP_OK, + 'data' => $roles, + ], Response::HTTP_OK); + } catch (\Exception $e) { + Log::error('Role creation error: ' . $e->getMessage()); + return response()->json([ + 'status_code' => Response::HTTP_INTERNAL_SERVER_ERROR, + 'error' => 'Internal Server Error', + 'message' => 'An error occurred while fetching roles', + ], Response::HTTP_INTERNAL_SERVER_ERROR); + } + } + + public function show($org_id, $role_id) + { + try { + // Validate UUID format + if (!Str::isUuid($org_id) || !is_numeric($role_id)) { + Log::error('Invalid UUID or ID format', [ + 'org_id' => $org_id, + 'role_id' => $role_id, + 'org_id_type' => gettype($org_id), + 'role_id_type' => gettype($role_id) + ]); + + return response()->json([ + 'status_code' => Response::HTTP_BAD_REQUEST, + 'error' => 'Bad Request', + 'message' => 'Invalid organisation ID or role ID format', + ], Response::HTTP_BAD_REQUEST); + } + + // Check if the organisation exists + $organisation = Organisation::where('org_id', $org_id)->first(); + if (!$organisation) { + Log::error('Organisation not found', ['org_id' => $org_id]); + + return response()->json([ + 'status_code' => Response::HTTP_NOT_FOUND, + 'error' => 'Not Found', + 'message' => 'The organisation with ID ' . $org_id . ' does not exist', + ], Response::HTTP_NOT_FOUND); + } + + // Check if the role exists within the organisation + $role = Role::where('org_id', $org_id)->where('id', $role_id)->first(); + if (!$role) { + Log::error('Role not found', ['org_id' => $org_id, 'role_id' => $role_id]); + + return response()->json([ + 'status_code' => Response::HTTP_NOT_FOUND, + 'error' => 'Not Found', + 'message' => 'The role with ID ' . $role_id . ' does not exist', + ], Response::HTTP_NOT_FOUND); + } + + // Fetch and format permissions + $permissions = $role->permissions->map(function ($permission) use ($role) { + return [ + 'id' => $permission->id, + 'permission_list' => [ + 'can_view_transactions' => $role->permissions()->where('permission_id', $permission->id)->where('name', 'can_view_transactions')->exists(), + 'can_view_refunds' => $role->permissions()->where('permission_id', $permission->id)->where('name', 'can_view_refunds')->exists(), + 'can_edit_transactions' => $role->permissions()->where('permission_id', $permission->id)->where('name', 'can_edit_transactions')->exists(), + ], + ]; + }); + + return response()->json([ + 'status_code' => Response::HTTP_OK, + 'data' => [ + 'id' => $role->id, + 'name' => $role->name, + 'description' => $role->description, + 'permissions' => $permissions, + ], + ], Response::HTTP_OK); + } catch (\InvalidArgumentException $e) { + Log::error('Invalid argument exception', [ + 'exception' => $e->getMessage(), + 'org_id' => $org_id, + 'role_id' => $role_id + ]); + + return response()->json([ + 'status_code' => Response::HTTP_BAD_REQUEST, + 'error' => 'Bad Request', + 'message' => 'Invalid organisation ID or role ID format', + ], Response::HTTP_BAD_REQUEST); + } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { + Log::error('Model not found exception', [ + 'exception' => $e->getMessage(), + 'org_id' => $org_id, + 'role_id' => $role_id + ]); + + return response()->json([ + 'status_code' => Response::HTTP_NOT_FOUND, + 'error' => 'Not Found', + 'message' => 'The role with ID ' . $role_id . ' does not exist', + ], Response::HTTP_NOT_FOUND); + } catch (\Exception $e) { + Log::error('General exception', [ + 'exception' => $e->getMessage(), + 'org_id' => $org_id, + 'role_id' => $role_id + ]); + + return response()->json([ + 'status_code' => Response::HTTP_INTERNAL_SERVER_ERROR, + 'error' => 'Internal Server Error', + 'message' => 'An error occurred while fetching the role', + ], Response::HTTP_INTERNAL_SERVER_ERROR); + } + } + public function assignPermissions(Request $request, $org_id, $role_id){ $role = Role::where('org_id', $org_id)->with('permissions')->find($role_id); $payload = Validator::make($request->all(), [ diff --git a/composer.lock b/composer.lock index 79afb004..ddcc748e 100755 --- a/composer.lock +++ b/composer.lock @@ -4027,16 +4027,16 @@ }, { "name": "promphp/prometheus_client_php", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/PromPHP/prometheus_client_php.git", - "reference": "a09ea80ec1ec26dd1d4853e9af2a811e898dbfeb" + "reference": "35d5a68628ea18209938bc1b8796646015ab93cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PromPHP/prometheus_client_php/zipball/a09ea80ec1ec26dd1d4853e9af2a811e898dbfeb", - "reference": "a09ea80ec1ec26dd1d4853e9af2a811e898dbfeb", + "url": "https://api.github.com/repos/PromPHP/prometheus_client_php/zipball/35d5a68628ea18209938bc1b8796646015ab93cf", + "reference": "35d5a68628ea18209938bc1b8796646015ab93cf", "shasum": "" }, "require": { @@ -4060,6 +4060,7 @@ }, "suggest": { "ext-apc": "Required if using APCu.", + "ext-pdo": "Required if using PDO.", "ext-redis": "Required if using Redis.", "promphp/prometheus_push_gateway_php": "An easy client for using Prometheus PushGateway.", "symfony/polyfill-apcu": "Required if you use APCu on PHP8.0+" @@ -4088,9 +4089,9 @@ "description": "Prometheus instrumentation library for PHP applications.", "support": { "issues": "https://github.com/PromPHP/prometheus_client_php/issues", - "source": "https://github.com/PromPHP/prometheus_client_php/tree/v2.10.0" + "source": "https://github.com/PromPHP/prometheus_client_php/tree/v2.11.0" }, - "time": "2024-02-01T13:28:34+00:00" + "time": "2024-08-05T07:58:08+00:00" }, { "name": "psr/cache", @@ -10409,5 +10410,5 @@ "ext-zip": "*" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/database/factories/PermissionFactory.php b/database/factories/PermissionFactory.php new file mode 100644 index 00000000..f760e276 --- /dev/null +++ b/database/factories/PermissionFactory.php @@ -0,0 +1,26 @@ + + */ +class PermissionFactory extends Factory +{ + protected $model = Permission::class; + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + // 'category' => $this->faker->word, + 'name' => $this->faker->word, + ]; + } +} diff --git a/public/uploads/1723077805.jpg b/public/uploads/1723077805.jpg new file mode 100644 index 00000000..b0079762 Binary files /dev/null and b/public/uploads/1723077805.jpg differ diff --git a/public/uploads/1723099428.jpg b/public/uploads/1723099428.jpg new file mode 100644 index 00000000..b0079762 Binary files /dev/null and b/public/uploads/1723099428.jpg differ diff --git a/routes/api.php b/routes/api.php index a52962a0..4c263850 100755 --- a/routes/api.php +++ b/routes/api.php @@ -80,7 +80,6 @@ Route::post('/auth/reset-forgot-password', [ForgotResetPasswordController::class, 'resetPassword']); Route::post('/auth/verify-otp', [ForgotResetPasswordController::class, 'verifyUserOTP']); - Route::post('/roles', [RoleController::class, 'store']); Route::get('/auth/login-facebook', [SocialAuthController::class, 'loginUsingFacebook']); Route::get('/auth/facebook/callback', [SocialAuthController::class, 'callbackFromFacebook']); @@ -203,18 +202,17 @@ Route::put('/jobs/{id}', [JobController::class, 'update']); Route::delete('/jobs/{id}', [JobController::class, 'destroy']); - - Route::get('/user/export/{format}', [ExportUserController::class, 'export']); // Accounts Route::patch('/accounts/deactivate', [AccountController::class, 'deactivate']); // Roles + Route::post('/organisations/{org_id}/roles', [RoleController::class, 'store']); Route::put('/organisations/{org_id}/roles/{role_id}', [RoleController::class, 'update']); Route::put('/organisations/{org_id}/roles/{role_id}/disable', [RoleController::class, 'disableRole']); - Route::put('/organisations/{org_id}/users/{user_id}/roles', [RoleController::class, 'assignRole']); - Route::put('/organisations/{org_id}/{role_id}/permissions', [RoleController::class, 'assignPermissions']); + Route::get('/organisations/{org_id}/roles', [RoleController::class, 'index']); + Route::get('/organisations/{org_id}/roles/{role_id}', [RoleController::class, 'show']); //Update Password Route::post('/password-update', [ProfileController::class, 'updatePassword']); diff --git a/tests/Feature/GetAllRolesTest.php b/tests/Feature/GetAllRolesTest.php new file mode 100644 index 00000000..ebcad6a2 --- /dev/null +++ b/tests/Feature/GetAllRolesTest.php @@ -0,0 +1,100 @@ +user = User::factory()->create(); + $this->actingAs($this->user, 'api'); // Ensure user is authenticated + } + + /** + * Test fetching all roles successfully. + * + * @return void + */ + public function testFetchRolesSuccessfully() + { + $organisation = Organisation::factory()->create(['user_id' => $this->user->id]); + $roles = Role::factory()->count(3)->create(['org_id' => $organisation->org_id]); + + $response = $this->getJson("/api/v1/organisations/{$organisation->org_id}/roles"); + + // Log response for debugging + // \Log::info('Fetch Roles Response:', ['response' => $response->json()]); + + $rolesArray = $roles->map(function ($role) { + return [ + 'id' => $role->id, + 'name' => $role->name, + 'description' => $role->description, + ]; + })->toArray(); + + $response->assertStatus(200) + ->assertJson([ + 'status_code' => 200, + 'data' => $rolesArray, + ]); + } + + /** + * Test fetching roles with invalid organisation ID format. + * + * @return void + */ + public function testFetchRolesWithInvalidOrganisationIdFormat() + { + $response = $this->getJson('/api/v1/organisations/invalid_id/roles'); + + // Log response for debugging + // \Log::info('Invalid Org ID Response:', ['response' => $response->json()]); + + $response->assertStatus(400) + ->assertJson([ + 'status_code' => 400, + 'error' => 'Bad Request', + 'message' => 'Invalid organisation ID format', + ]); + } + + /** + * Test fetching roles for non-existent organisation. + * + * @return void + */ + public function testFetchRolesForNonExistentOrganisation() + { + $nonExistentOrgId = '00000000-0000-0000-0000-000000000999'; + + // Ensure the organisation with the given ID does not exist + $this->assertDatabaseMissing('organisations', ['org_id' => $nonExistentOrgId]); + + $response = $this->getJson("/api/v1/organisations/{$nonExistentOrgId}/roles"); + + // Log response for debugging + // \Log::info('Non-Existent Org ID Response:', ['response' => $response->json()]); + + $response->assertStatus(404) + ->assertJson([ + 'status_code' => 404, + 'error' => 'Not Found', + 'message' => "The organisation with ID {$nonExistentOrgId} does not exist", + ]); + } +} diff --git a/tests/Feature/PermissionTest.php b/tests/Feature/PermissionTest.php index a26ac459..e178126e 100644 --- a/tests/Feature/PermissionTest.php +++ b/tests/Feature/PermissionTest.php @@ -64,26 +64,4 @@ public function it_assigns_permissions_successfully() 'message' => 'Permissions updated successfully', ]); } - - /** @test */ - public function it_returns_error_when_assigning_permissions_fails() - { - $user = User::factory()->create(); - $organisation = Organisation::factory()->create(); - $role = Role::factory()->create(['org_id' => $organisation->org_id]); - - // Simulate validation error - $response = $this->actingAs($user) - ->putJson("/api/v1/organisations/{$organisation->org_id}/{$role->id}/permissions", []); - $response->assertStatus(422); - - // Simulate role not found - $response = $this->actingAs($user) - ->putJson("/api/v1/organisations/{$organisation->org_id}/999/permissions", ['permission_list' => ['permission' => true]]); - - $response->assertStatus(404) - ->assertJsonFragment([ - 'message' => 'Role not found', - ]); - } } \ No newline at end of file diff --git a/tests/Feature/RoleCreationTest.php b/tests/Feature/RoleCreationTest.php index 3c1066ac..8350d5d7 100755 --- a/tests/Feature/RoleCreationTest.php +++ b/tests/Feature/RoleCreationTest.php @@ -19,50 +19,44 @@ class RoleCreationTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->test_user = User::create([ - 'name' => 'Test User', - 'email' => 'testuser@example.com', - 'password' => 'Ed8M7s*)?e:hTb^#&;C!test_user = User::factory()->create(); - $this->test_org = Organisation::create([ - "name" => 'Test organisation', - "user_id" => $this->test_user->id, - "email" => "test email", - "description" => "test description", - "industry" => "test industry", - "type" => "test type", - "country" => "test country", - "address" => "test address", - "state" => "test state", + $this->test_org = Organisation::factory()->create([ + 'user_id' => $this->test_user->id, ]); $this->test_permission = Permission::create([ 'name' => 'test permission 1', ]); - $this->assertDatabaseHas('users', ['name'=>'Test User']); - $this->assertDatabaseHas('organisations', ['name'=>'Test organisation']); - $this->assertDatabaseHas('permissions', ['name'=>'test permission 1']); + $this->assertDatabaseHas('users', ['name' => $this->test_user->name]); + $this->assertDatabaseHas('organisations', ['name' => $this->test_org->name]); + $this->assertDatabaseHas('permissions', ['name' => 'test permission 1']); } public function test_role_creation_is_successful() { + // Authenticate the test user + $this->actingAs($this->test_user, 'api'); - $this->postJson('/api/v1/roles', [ + $response = $this->postJson('/api/v1/organisations/' . $this->test_org->org_id . '/roles', [ 'role_name' => 'Test role', 'organisation_id' => $this->test_org->org_id, 'permissions_id' => $this->test_permission->id, - ]) - ->assertStatus(201) - ->assertJsonStructure([ - 'status_code', - 'message' - ]); + ]); + + // Print the response content to see the validation errors + $response->dump(); + + $response->assertStatus(201) + ->assertJsonStructure([ + 'status_code', + 'message' + ]); $this->assertDatabaseHas('roles', [ 'name' => 'Test role', ])->assertDatabaseCount('roles_permissions', 1); } - } diff --git a/tests/Feature/RoleUpdateTest.php b/tests/Feature/RoleUpdateTest.php index cbd10087..e358baff 100644 --- a/tests/Feature/RoleUpdateTest.php +++ b/tests/Feature/RoleUpdateTest.php @@ -39,22 +39,4 @@ public function test_update_role_name() ]); } - public function test_assign_role_to_user() - { - $organisation = Organisation::factory()->create(); - $role = Role::factory()->create(['org_id' => $organisation->org_id]); - $user = User::factory()->create(); - $organisation->users()->attach($user->id); - $this->actingAs($user, 'api'); - - $response = $this->putJson("/api/v1/organisations/{$organisation->org_id}/users/{$user->id}/roles", [ - 'role' => $role->name, - ]); - - $response->assertStatus(200); - $response->assertOk() - ->assertJson([ - 'message' => 'Roles updated successfully', - ]); - } } diff --git a/tests/Feature/ShowRoleTest.php b/tests/Feature/ShowRoleTest.php new file mode 100644 index 00000000..85d2d496 --- /dev/null +++ b/tests/Feature/ShowRoleTest.php @@ -0,0 +1,140 @@ +user = User::factory()->create(); + $this->actingAs($this->user, 'api'); // Ensure user is authenticated + } + + /** + * Test fetching a role successfully. + * + * @return void + */ + public function testFetchRoleSuccessfully() + { + $organisation = Organisation::factory()->create(['user_id' => $this->user->id]); + $role = Role::factory()->create(['org_id' => $organisation->org_id]); + $permissions = Permission::factory()->count(3)->create(); + + // Attach permissions to role + $role->permissions()->attach($permissions->pluck('id')); + + $response = $this->getJson("/api/v1/organisations/{$organisation->org_id}/roles/{$role->id}"); + + $permissionsArray = $permissions->map(function ($permission) use ($role) { + return [ + 'id' => $permission->id, + 'permission_list' => [ + 'can_view_transactions' => $role->permissions()->where('permission_id', $permission->id)->where('name', 'can_view_transactions')->exists(), + 'can_view_refunds' => $role->permissions()->where('permission_id', $permission->id)->where('name', 'can_view_refunds')->exists(), + 'can_edit_transactions' => $role->permissions()->where('permission_id', $permission->id)->where('name', 'can_edit_transactions')->exists(), + ], + ]; + })->toArray(); + + $response->assertStatus(200) + ->assertJson([ + 'status_code' => 200, + 'data' => [ + 'id' => $role->id, + 'name' => $role->name, + 'description' => $role->description, + 'permissions' => $permissionsArray, + ], + ]); + } + + /** + * Test fetching a role with invalid organisation ID format. + * + * @return void + */ + public function testFetchRoleWithInvalidOrganisationIdFormat() + { + // Use an invalid UUID format + $response = $this->getJson('/api/v1/organisations/invalid_uuid_format/roles/1'); + + $response->assertStatus(400) + ->assertJson([ + 'status_code' => 400, + 'error' => 'Bad Request', + 'message' => 'Invalid organisation ID or role ID format', + ]); + } + + /** + * Test fetching a role with invalid role ID format. + * + * @return void + */ + public function testFetchRoleWithInvalidRoleIdFormat() + { + $organisation = Organisation::factory()->create(['user_id' => $this->user->id]); + + // Use a valid UUID for organisation but invalid format for role ID + $response = $this->getJson("/api/v1/organisations/{$organisation->org_id}/roles/invalid_uuid_format"); + + $response->assertStatus(400) + ->assertJson([ + 'status_code' => 400, + 'error' => 'Bad Request', + 'message' => 'Invalid organisation ID or role ID format', + ]); + } + + /** + * Test fetching a role for a non-existent organisation. + * + * @return void + */ + public function testFetchRoleForNonExistentOrganisation() + { + // Use a UUID that does not exist in the database + $response = $this->getJson('/api/v1/organisations/00000000-0000-0000-0000-000000000000/roles/1'); + + $response->assertStatus(404) + ->assertJson([ + 'status_code' => 404, + 'error' => 'Not Found', + 'message' => 'The organisation with ID 00000000-0000-0000-0000-000000000000 does not exist', + ]); + } + + /** + * Test fetching a non-existent role. + * + * @return void + */ + public function testFetchNonExistentRole() + { + $organisation = Organisation::factory()->create(['user_id' => $this->user->id]); + + // Use a valid UUID for organisation but one that does not exist as a role + $response = $this->getJson("/api/v1/organisations/{$organisation->org_id}/roles/00000000000000000000000000000000"); + + $response->assertStatus(404) + ->assertJson([ + 'status_code' => 404, + 'error' => 'Not Found', + 'message' => 'The role with ID 00000000000000000000000000000000 does not exist', + ]); + } +}