diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..ae9cea7 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,49 @@ +name: Java CI with Gradle + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew build -x test + + - name: Run tests + run: ./gradlew test + + - name: Publish Test Results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: | + build/reports/tests/test/ + build/test-results/test/ + + - name: Update status check + if: success() + run: echo "Tests passed" > status.txt || echo "Tests failed" > status.txt + + - name: Upload status check + uses: actions/upload-artifact@v4 + with: + name: status-check + path: status.txt diff --git a/README.md b/README.md index bd9162d..374b86e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Upwork-Clone +![Build Status](https://github.com/AhmedMohamedAbdelaty/Upwork/actions/workflows/gradle.yml/badge.svg) + A platform connecting freelancers and clients for job postings, proposals, and real-time chat. ## Table of Contents @@ -11,6 +13,7 @@ A platform connecting freelancers and clients for job postings, proposals, and r - [User Management](#user-management) - [Password Management](#password-management) - [Token Management](#token-management) + - [Role Management](#role-management) - [Test Endpoints](#test-endpoints) - [Swagger UI](#swagger-ui) - [To-Do](#to-do) @@ -155,6 +158,7 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr - `id`: The ID of the user to reactivate - **Response:** A `ResponseDto` object containing the result of the operation. +
Get user profile @@ -162,15 +166,15 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr - **Method:** `GET` - **Description:** Retrieve the profile information for a specific user. - **Path Parameters:** - - `id` (required): The ID of the user whose profile is to be retrieved. - - **Response:** - - **Status Code:** `200 OK` - - **Body:** - ```json - { - "status": "OK", - "success": true, - "data": { + - `userId` (required): The ID of the user whose profile is to be retrieved. +- **Response:** + - **Status Code:** `200 OK` + - **Body:** + ```json + { + "status": "OK", + "success": true, + "data": { "id": 160, "firstName": "Teddy", "lastName": "Johnson", @@ -178,11 +182,12 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr "description": null, "hourlyRate": null, "location": null - }, - "error": null - } - ``` + }, + "error": null + } + ```
+
Update user profile @@ -191,28 +196,28 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr - **Description:** Update the profile information for a specific user. - **Path Parameters:** - `id` (required): The ID of the user whose currently logged in. - - **Request Body:** - - **Content-Type:** `application/json` - - **Body Example:** - ```json - { - "id": 160, - "firstName": "string", - "lastName": "string", - "title": "string", - "description": "string", - "hourlyRate": 0, - "location": "string" - } - ``` +- **Request Body:** + - **Content-Type:** `application/json` + - **Body Example:** + ```json + { + "id": 160, + "firstName": "string", + "lastName": "string", + "title": "string", + "description": "string", + "hourlyRate": 0, + "location": "string" + } + ``` - **Response:** - **Status Code:** `200 OK` - **Body Example:** ```json - { - "status": "OK", - "success": true, - "data": { + { + "status": "OK", + "success": true, + "data": { "id": 160, "firstName": "string", "lastName": "string", @@ -220,10 +225,9 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr "description": "string", "hourlyRate": 0, "location": "string" - }, - "error": null - } - + }, + "error": null + } ```
@@ -301,6 +305,128 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr - **Response:** An object indicating the result of the operation. +### Role Management + +
+ Add a new role + +- **URL:** `/api/roles/add` +- **Method:** `POST` +- **Description:** Add a new role, accessible only by admins. +- **Request Body:** + ```json + { + "name": "string" + } + ``` +- **Response:** + ```json + { + "status": "CREATED", + "success": true, + "data": { + "id": 1, + "name": "string" + }, + "error": null + } + ``` +
+ +
+ Remove a role + +- **URL:** `/api/roles/remove/{roleId}` +- **Method:** `DELETE` +- **Description:** Remove a role, accessible only by admins. +- **Path Parameters:** + - `roleId` (required): The ID of the role to remove. +- **Response:** + ```json + { + "status": "OK", + "success": true, + "data": "Role removed successfully.", + "error": null + } + ``` +
+ +
+ Update a role + +- **URL:** `/api/roles/update/{roleId}` +- **Method:** `PUT` +- **Description:** Update a role, accessible only by admins. +- **Path Parameters:** + - `roleId` (required): The ID of the role to update. +- **Request Body:** + ```json + { + "name": "string" + } + ``` +- **Response:** + ```json + { + "status": "OK", + "success": true, + "data": { + "id": 1, + "name": "string" + }, + "error": null + } + ``` +
+ +
+ Get all roles + +- **URL:** `/api/roles/all` +- **Method:** `GET` +- **Description:** Retrieve a list of all roles, accessible only by admins. +- **Response:** + ```json + { + "status": "OK", + "success": true, + "data": [ + { + "id": 1, + "name": "string" + } + ], + "error": null + } + ``` +
+ +
+ Assign roles to users + +- **URL:** `/api/roles/{id}/assign-roles` +- **Method:** `POST` +- **Description:** Assign roles to users, accessible only by admins. +- **Path Parameters:** + - `id` (required): The ID of the user to assign roles to. +- **Request Body:** + ```json + { + "roles": ["string"] + } + ``` +- **Response:** + ```json + { + "status": "OK", + "success": true, + "data": "Roles assigned successfully.", + "error": null + } + ``` +
+ ### Test Endpoints These endpoints are likely for testing purposes and may be removed in production: @@ -325,4 +451,4 @@ You can access the Swagger UI documentation for this API at: http://localhost:80 - [ ] Add unit and integration tests for all endpoints. - [ ] Implement logging and monitoring solutions. - [ ] Create a Dockerfile and build a Docker image for the application. -- [ ] Set up Docker Compose and document Docker setup for the frontend team. \ No newline at end of file +- [ ] Set up Docker Compose and document Docker setup for the frontend team. diff --git a/build.gradle b/build.gradle index d79cf4c..2bfdcbb 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation 'com.h2database:h2' } tasks.named('test') { diff --git a/src/main/java/com/activecourses/upwork/config/security/SecurityConfig.java b/src/main/java/com/activecourses/upwork/config/security/SecurityConfig.java index 7f0d091..312cad0 100644 --- a/src/main/java/com/activecourses/upwork/config/security/SecurityConfig.java +++ b/src/main/java/com/activecourses/upwork/config/security/SecurityConfig.java @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -55,8 +54,6 @@ public class SecurityConfig { "/api/client/**" }; - private final CustomUserDetailsService customUserDetailsService; - private final AuthEntryPointJwt unauthorizedHandler; @Bean @@ -64,14 +61,6 @@ public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } - @Bean - public DaoAuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider auth = new DaoAuthenticationProvider(); - auth.setUserDetailsService(customUserDetailsService); - auth.setPasswordEncoder(passwordEncoder()); - return auth; - } - @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); @@ -96,12 +85,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ); http.httpBasic(Customizer.withDefaults()); - http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); -// -// -// } } diff --git a/src/main/java/com/activecourses/upwork/controller/TestController.java b/src/main/java/com/activecourses/upwork/controller/TestController.java index e366c7f..8009e80 100644 --- a/src/main/java/com/activecourses/upwork/controller/TestController.java +++ b/src/main/java/com/activecourses/upwork/controller/TestController.java @@ -1,6 +1,10 @@ package com.activecourses.upwork.controller; - +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; @@ -10,33 +14,67 @@ @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/test") +@Tag(name = "Test", description = "Test API") public class TestController { + + private static final Logger logger = LoggerFactory.getLogger(TestController.class); + + @Operation( + summary = "Public access", + description = "Accessible by anyone", + security = @SecurityRequirement(name = "") + ) @GetMapping("/all") public String allAccess() { + logger.info("Accessing public content"); return "Public Content."; } + @Operation( + summary = "Access for all users", + description = "Accessible by CLIENT, FREELANCER, and ADMIN roles", + security = @SecurityRequirement(name = "bearerAuth") + ) @GetMapping("/user") @PreAuthorize("hasRole('CLIENT') or hasRole('FREELANCER') or hasRole('ADMIN')") public String allUsersAccess() { + logger.info("Accessing content for all users"); return "All Users Content."; } + @Operation( + summary = "Client access", + description = "Accessible by CLIENT role", + security = @SecurityRequirement(name = "bearerAuth") + ) @GetMapping("/client") @PreAuthorize("hasRole('CLIENT')") public String clientAccess() { + logger.info("Accessing client content"); return "Client Board."; } + @Operation( + summary = "Admin access", + description = "Accessible by ADMIN role", + security = @SecurityRequirement(name = "bearerAuth") + ) @GetMapping("/admin") @PreAuthorize("hasRole('ADMIN')") public String adminAccess() { + logger.info("Accessing admin content"); return "Admin Board."; } + @Operation( + summary = "Freelancer access", + description = "Accessible by FREELANCER role", + security = @SecurityRequirement(name = "bearerAuth") + ) @GetMapping("/freelancer") @PreAuthorize("hasRole('FREELANCER')") public String freelancerAccess() { + logger.info("Accessing freelancer content"); return "Freelancer Board."; } -} \ No newline at end of file +} diff --git a/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java b/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java index 9de71dc..471d06c 100644 --- a/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java +++ b/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java @@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; @@ -25,7 +27,7 @@ @RequestMapping("/api/auth/") public class AuthController { private final AuthService authService; - private final JwtService jwtService; + private static final Logger logger = LoggerFactory.getLogger(AuthController.class); @Operation( summary = "Register a new user", @@ -34,6 +36,7 @@ public class AuthController { ) @PostMapping("/register") public ResponseEntity registerUser(@Valid @RequestBody RegistrationRequestDto registrationRequestDto) { + logger.info("Registering user with email: {}", registrationRequestDto.getEmail()); return ResponseEntity .status(HttpStatus.OK) .body(ResponseDto @@ -50,8 +53,9 @@ public ResponseEntity registerUser(@Valid @RequestBody Registration description = "Login", security = @SecurityRequirement(name = "") ) - @PostMapping("login") + @PostMapping("login") public ResponseEntity login(@Valid @RequestBody LoginRequestDto loginRequestDto) { + logger.info("User login attempt with email: {}", loginRequestDto.getEmail()); ResponseDto responseDto = authService.login(loginRequestDto); Map cookies = (Map) responseDto.getData(); @@ -62,13 +66,14 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto loginRequestD } @Operation( - summary = "Deactivate user", - description = "Deactivate user", - security = @SecurityRequirement(name = "") + summary = "Deactivate user", + description = "Deactivate user", + security = @SecurityRequirement(name = "bearerAuth") ) @PreAuthorize("hasRole('ADMIN')") @PostMapping("/{id}/deactivate") public ResponseDto deactivateUser(@PathVariable int id) { + logger.info("Deactivating user with id: {}", id); boolean success = authService.deactivateUser(id); if (success) { return ResponseDto.builder() @@ -88,11 +93,12 @@ public ResponseDto deactivateUser(@PathVariable int id) { @Operation( summary = "Reactivate user", description = "Reactivate user", - security = @SecurityRequirement(name = "") + security = @SecurityRequirement(name = "bearerAuth") ) @PreAuthorize("hasRole('ADMIN')") @PostMapping("/{id}/reactivate") public ResponseDto reactivateUser(@PathVariable int id) { + logger.info("Reactivating user with id: {}", id); boolean success = authService.reactivateUser(id); if (success) { return ResponseDto.builder() @@ -116,6 +122,7 @@ public ResponseDto reactivateUser(@PathVariable int id) { ) @PostMapping("logout") public ResponseEntity logout() { + logger.info("User logout attempt"); return authService.logout(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/activecourses/upwork/controller/role/RoleController.java b/src/main/java/com/activecourses/upwork/controller/role/RoleController.java new file mode 100644 index 0000000..ba6f6d5 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/controller/role/RoleController.java @@ -0,0 +1,111 @@ +package com.activecourses.upwork.controller.role; + +import com.activecourses.upwork.dto.ResponseDto; +import com.activecourses.upwork.model.Role; +import com.activecourses.upwork.service.role.RoleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@Tag(name = "Role", description = "Role Management API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/roles/") +public class RoleController { + private final RoleService roleService; + private static final Logger logger = LoggerFactory.getLogger(RoleController.class); + + @Operation( + summary = "Add a new role", + description = "Add a new role, accessible only by admins", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("/add") + public ResponseEntity addRole(@RequestBody Role role) { + logger.info("Adding new role: {}", role.getName()); + Role createdRole = roleService.addRole(role); + return buildResponseEntity(HttpStatus.CREATED, true, createdRole, null); + } + + @Operation( + summary = "Remove a role", + description = "Remove a role, accessible only by admins", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PreAuthorize("hasRole('ADMIN')") + @DeleteMapping("/remove/{roleId}") + public ResponseEntity removeRole(@PathVariable int roleId) { + logger.info("Removing role with id: {}", roleId); + boolean success = roleService.removeRole(roleId); + return success + ? buildResponseEntity(HttpStatus.OK, true, "Role removed successfully.", null) + : buildResponseEntity(HttpStatus.NOT_FOUND, false, null, "Role not found."); + } + + @Operation( + summary = "Update a role", + description = "Update a role, accessible only by admins", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PreAuthorize("hasRole('ADMIN')") + @PutMapping("/update/{roleId}") + public ResponseEntity updateRole(@PathVariable int roleId, @RequestBody Role role) { + logger.info("Updating role with id: {}", roleId); + Role updatedRole = roleService.updateRole(roleId, role); + return updatedRole != null + ? buildResponseEntity(HttpStatus.OK, true, updatedRole, null) + : buildResponseEntity(HttpStatus.NOT_FOUND, false, null, "Role not found."); + } + + @Operation( + summary = "Get all roles", + description = "Retrieve a list of all roles, accessible only by admins", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/all") + public ResponseEntity getAllRoles() { + logger.info("Retrieving all roles"); + List roles = roleService.getAllRoles(); + return buildResponseEntity(HttpStatus.OK, true, roles, null); + } + + @Operation( + summary = "Assign roles to users", + description = "Assign roles to users, accessible only by admins", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("/{id}/assign-roles") + public ResponseEntity assignRolesToUser(@PathVariable int id, @RequestBody Map roles) { + logger.info("Assigning roles to user with id: {}", id); + boolean success = roleService.assignRolesToUser(id, roles); + return success + ? buildResponseEntity(HttpStatus.OK, true, "Roles assigned successfully.", null) + : buildResponseEntity(HttpStatus.NOT_FOUND, false, null, "User not found."); + } + + private ResponseEntity buildResponseEntity(HttpStatus status, boolean success, Object data, Object error) { + return ResponseEntity + .status(status) + .body(ResponseDto + .builder() + .status(status) + .success(success) + .data(data) + .error(error) + .build() + ); + } +} diff --git a/src/main/java/com/activecourses/upwork/controller/user/UserController.java b/src/main/java/com/activecourses/upwork/controller/user/UserController.java index f96e565..f60767a 100644 --- a/src/main/java/com/activecourses/upwork/controller/user/UserController.java +++ b/src/main/java/com/activecourses/upwork/controller/user/UserController.java @@ -3,7 +3,7 @@ import com.activecourses.upwork.dto.ResponseDto; import com.activecourses.upwork.dto.user.UserProfileDto; import com.activecourses.upwork.dto.user.UserResponseDto; -import com.activecourses.upwork.service.user.UserProfileService; +import com.activecourses.upwork.service.user.profile.UserProfileService; import com.activecourses.upwork.service.user.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -30,10 +30,10 @@ public class UserController { @Operation(summary = "Get all users", description = "Retrieve a paginated list of all users. Only accessible by users with the ROLE_ADMIN role.", parameters = { - @Parameter(name = "pageNo", in = ParameterIn.QUERY, description = "Page number", required = false, schema = @Schema(type = "integer", defaultValue = "0")), - @Parameter(name = "pageSize", in = ParameterIn.QUERY, description = "Page size", required = false, schema = @Schema(type = "integer", defaultValue = "10")), - @Parameter(name = "sortBy", in = ParameterIn.QUERY, description = "Sort by", required = false, schema = @Schema(type = "string", defaultValue = "id")), - @Parameter(name = "sortDir", in = ParameterIn.QUERY, description = "Sort direction", required = false, schema = @Schema(type = "string", defaultValue = "asc")) + @Parameter(name = "pageNo", in = ParameterIn.QUERY, description = "Page number", schema = @Schema(type = "integer", defaultValue = "0")), + @Parameter(name = "pageSize", in = ParameterIn.QUERY, description = "Page size", schema = @Schema(type = "integer", defaultValue = "10")), + @Parameter(name = "sortBy", in = ParameterIn.QUERY, description = "Sort by", schema = @Schema(type = "string", defaultValue = "id")), + @Parameter(name = "sortDir", in = ParameterIn.QUERY, description = "Sort direction", schema = @Schema(type = "string", defaultValue = "asc")) } ) @PreAuthorize("hasRole('ROLE_ADMIN')") @@ -77,7 +77,7 @@ public ResponseEntity updateUserProfile( @PathVariable int userId, @RequestBody @Valid UserProfileDto updateRequest) { try { - return userProfileService.UpdateUserProfile(userId,updateRequest); + return userProfileService.UpdateUserProfile(userId, updateRequest); } catch (Exception e) { return ResponseEntity.badRequest().body(ResponseDto.builder() .status(HttpStatus.BAD_REQUEST) diff --git a/src/main/java/com/activecourses/upwork/dto/authentication/login/LoginRequestDto.java b/src/main/java/com/activecourses/upwork/dto/authentication/login/LoginRequestDto.java index 1061ea7..98d33c7 100644 --- a/src/main/java/com/activecourses/upwork/dto/authentication/login/LoginRequestDto.java +++ b/src/main/java/com/activecourses/upwork/dto/authentication/login/LoginRequestDto.java @@ -19,4 +19,10 @@ public class LoginRequestDto { @NotNull(message = "Password is required") @Size(min = 6, message = "Password must be at least 6 characters long") private String password; + + public LoginRequestDto(@Email(message = "Email is not valid") @NotEmpty(message = "Email is required") @NotNull(message = "Email is required") String email, + @NotEmpty(message = "Password is required") @NotNull(message = "Password is required") @Size(min = 6, message = "Password must be at least 6 characters long") String password) { + this.email = email; + this.password = password; + } } diff --git a/src/main/java/com/activecourses/upwork/service/RefreshTokenService.java b/src/main/java/com/activecourses/upwork/service/RefreshTokenService.java index 7485161..9fe742d 100644 --- a/src/main/java/com/activecourses/upwork/service/RefreshTokenService.java +++ b/src/main/java/com/activecourses/upwork/service/RefreshTokenService.java @@ -75,9 +75,11 @@ public ResponseEntity refreshToken(HttpServletRequest request) { return buildBadRequestResponse("Refresh Token is empty!"); } } + private String getRefreshTokenFromRequest(HttpServletRequest request) { return jwtService.getJwtRefreshFromCookies(request); } + private String getTokenFromRequest(HttpServletRequest request) { return jwtService.getJwtFromCookies(request); } @@ -117,7 +119,7 @@ public RefreshToken createRefreshToken(int userId) { public void verifyExpiration(RefreshToken token) { if (token.getExpiryDate().compareTo(Instant.now()) < 0) { refreshTokenRepository.delete(token); - throw new TokenRefreshException(token.getToken(), "Refresh token was expired. Please make a new signin request"); + throw new TokenRefreshException(token.getToken(), "Refresh token was expired. Please sign in again to obtain a new refresh token."); } } diff --git a/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java b/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java index 218e245..45207a0 100644 --- a/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java @@ -10,6 +10,7 @@ import com.activecourses.upwork.model.User; import com.activecourses.upwork.model.UserProfile; import com.activecourses.upwork.repository.user.UserRepository; +import com.activecourses.upwork.repository.role.RoleRepository; import com.activecourses.upwork.config.security.jwt.JwtService; import com.activecourses.upwork.service.RefreshTokenService; @@ -18,6 +19,8 @@ import java.util.Map; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; @@ -38,6 +41,7 @@ public class AuthServiceImpl implements AuthService { private final UserRepository userRepository; + private final RoleRepository roleRepository; private final PasswordEncoder passwordEncoder; private final AuthenticationManager authenticationManager; private final JwtService jwtService; @@ -45,10 +49,11 @@ public class AuthServiceImpl implements AuthService { private final Mapper userMapper; private final CustomUserDetailsService customUserDetailsService; private final RefreshTokenService refreshTokenService; - + private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class); @Override public RegistrationResponseDto registerUser(RegistrationRequestDto registrationRequestDto) { + logger.info("Registering user with email: {}", registrationRequestDto.getEmail()); User user = userMapper.mapFrom(registrationRequestDto); user.setPassword(passwordEncoder.encode(user.getPassword())); user.setUserProfile(new UserProfile()); @@ -63,10 +68,11 @@ public RegistrationResponseDto registerUser(RegistrationRequestDto registrationR @Transactional @Override public ResponseDto login(LoginRequestDto loginRequestDto) { - + logger.info("User login attempt with email: {}", loginRequestDto.getEmail()); User user = findByEmail(loginRequestDto.getEmail()); if (!user.isAccountEnabled()) { + logger.warn("Account is disabled for user: {}", loginRequestDto.getEmail()); return ResponseDto .builder() .status(HttpStatus.FORBIDDEN) @@ -74,8 +80,9 @@ public ResponseDto login(LoginRequestDto loginRequestDto) { .error("Account is disabled.") .build(); } - + if (user.isAccountLocked()) { + logger.warn("Account is locked for user: {}", loginRequestDto.getEmail()); return ResponseDto .builder() .status(HttpStatus.LOCKED) @@ -109,6 +116,7 @@ public ResponseDto login(LoginRequestDto loginRequestDto) { @Override public ResponseEntity logout() { + logger.info("User logout attempt"); Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (!principal.toString().equals("anonymousUser")) { int userId = ((User) principal).getId(); @@ -132,31 +140,32 @@ public ResponseEntity logout() { @Override public Optional refreshToken(String refreshToken) { + logger.info("Refreshing token"); String username = jwtService.getUserNameFromJwtToken(refreshToken); return userRepository.findByEmail(username); } @Override public boolean verifyUser(String token) { + logger.info("Verifying user with token: {}", token); Optional user = userRepository.findByVerificationToken(token); User unwrappedUser = unwrapUser(user); - if (unwrappedUser != null) { - unwrappedUser.setAccountEnabled(true); - unwrappedUser.setVerificationToken(null); // Clear the token - userRepository.save(unwrappedUser); - return true; - } - return false; + unwrappedUser.setAccountEnabled(true); + unwrappedUser.setVerificationToken(null); // Clear the token + userRepository.save(unwrappedUser); + return true; } @Override public User findByEmail(String email) { + logger.info("Finding user by email: {}", email); Optional user = userRepository.findByEmail(email); return unwrapUser(user); } @Override public void sendVerificationEmail(User user) { + logger.info("Sending verification email to: {}", user.getEmail()); String verificationLink = "http://localhost:8080/api/users/verify?token=" + user.getVerificationToken(); SimpleMailMessage message = new SimpleMailMessage(); @@ -165,33 +174,29 @@ public void sendVerificationEmail(User user) { message.setText("Click the link to verify your email: " + verificationLink); mailSender.send(message); } - + @Override public boolean deactivateUser(int userId) { + logger.info("Deactivating user with id: {}", userId); Optional user = userRepository.findById(userId); User unwrappedUser = unwrapUser(user); - if (unwrappedUser != null) { - unwrappedUser.setAccountEnabled(false); - userRepository.save(unwrappedUser); - return true; - } - return false; + unwrappedUser.setAccountEnabled(false); + userRepository.save(unwrappedUser); + return true; } @Override public boolean reactivateUser(int userId) { + logger.info("Reactivating user with id: {}", userId); Optional user = userRepository.findById(userId); User unwrappedUser = unwrapUser(user); - if (unwrappedUser != null) { - unwrappedUser.setAccountEnabled(true); - userRepository.save(unwrappedUser); - return true; - } - return false; + unwrappedUser.setAccountEnabled(true); + userRepository.save(unwrappedUser); + return true; } static User unwrapUser(Optional entity) { if (entity.isPresent()) return entity.get(); else throw new UnsupportedOperationException("Unimplemented method 'unwrapUser'"); } -} \ No newline at end of file +} diff --git a/src/main/java/com/activecourses/upwork/service/role/RoleService.java b/src/main/java/com/activecourses/upwork/service/role/RoleService.java new file mode 100644 index 0000000..eb537ce --- /dev/null +++ b/src/main/java/com/activecourses/upwork/service/role/RoleService.java @@ -0,0 +1,18 @@ +package com.activecourses.upwork.service.role; + +import com.activecourses.upwork.model.Role; + +import java.util.List; +import java.util.Map; + +public interface RoleService { + Role addRole(Role role); + + boolean removeRole(int roleId); + + Role updateRole(int roleId, Role role); + + List getAllRoles(); + + boolean assignRolesToUser(int userId, Map roles); +} diff --git a/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java b/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java new file mode 100644 index 0000000..5951727 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java @@ -0,0 +1,69 @@ +package com.activecourses.upwork.service.role; + +import com.activecourses.upwork.model.Role; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.repository.role.RoleRepository; +import com.activecourses.upwork.repository.user.UserRepository; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RoleServiceImpl implements RoleService { + + private final RoleRepository roleRepository; + private final UserRepository userRepository; + private static final Logger logger = LoggerFactory.getLogger(RoleServiceImpl.class); + + @Override + public Role addRole(Role role) { + return roleRepository.save(role); + } + + @Override + public boolean removeRole(int roleId) { + Optional role = roleRepository.findById(roleId); + if (role.isPresent()) { + roleRepository.delete(role.get()); + return true; + } + return false; + } + + @Override + public Role updateRole(int roleId, Role role) { + Optional existingRole = roleRepository.findById(roleId); + if (existingRole.isPresent()) { + Role updatedRole = existingRole.get(); + updatedRole.setName(role.getName()); + return roleRepository.save(updatedRole); + } + return null; + } + + @Override + public List getAllRoles() { + return roleRepository.findAll(); + } + + @Override + public boolean assignRolesToUser(int userId, Map roles) { + logger.info("Assigning roles to user with id: {}", userId); + Optional user = userRepository.findById(userId); + User unwrappedUser = user.orElseThrow(() -> new RuntimeException("User not found: " + userId)); + List roleList = roles.keySet().stream() + .map(roleName -> roleRepository.findByName(roleName) + .orElseThrow(() -> new RuntimeException("Role not found: " + roleName))) + .collect(Collectors.toList()); + unwrappedUser.setRoles(roleList); + userRepository.save(unwrappedUser); + return true; + } +} diff --git a/src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java b/src/main/java/com/activecourses/upwork/service/user/UserServiceImpl.java similarity index 96% rename from src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java rename to src/main/java/com/activecourses/upwork/service/user/UserServiceImpl.java index 72afd00..6e54e68 100644 --- a/src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/user/UserServiceImpl.java @@ -23,7 +23,6 @@ public class UserServiceImpl implements UserService { private final UserDtoMapper userDtoMapper; - @Override public UserResponseDto getAllUsers(int pageNo, int pageSize, String sortBy, String sortDir) { // Set the sorting direction @@ -54,11 +53,10 @@ public UserResponseDto getAllUsers(int pageNo, int pageSize, String sortBy, Stri userResponseDto.setLast(pagedResult.isLast()); return userResponseDto; } + @Override public User findByEmail(String email) { return userRepository.findByEmail(email). - orElseThrow(()->new UsernameNotFoundException("User Not Found")); + orElseThrow(() -> new UsernameNotFoundException("User Not Found")); } - - } diff --git a/src/main/java/com/activecourses/upwork/service/user/ImplUserProfileService.java b/src/main/java/com/activecourses/upwork/service/user/profile/ImplUserProfileService.java similarity index 68% rename from src/main/java/com/activecourses/upwork/service/user/ImplUserProfileService.java rename to src/main/java/com/activecourses/upwork/service/user/profile/ImplUserProfileService.java index 6c34eb4..fb50f5a 100644 --- a/src/main/java/com/activecourses/upwork/service/user/ImplUserProfileService.java +++ b/src/main/java/com/activecourses/upwork/service/user/profile/ImplUserProfileService.java @@ -1,4 +1,4 @@ -package com.activecourses.upwork.service.user; +package com.activecourses.upwork.service.user.profile; import com.activecourses.upwork.dto.ResponseDto; import com.activecourses.upwork.dto.user.UserProfileDto; @@ -14,9 +14,10 @@ @Service @RequiredArgsConstructor -public class ImplUserProfileService implements UserProfileService{ +public class ImplUserProfileService implements UserProfileService { private final UserRepository userRepository; private final Mapper userProfileMapper; + @Override public ResponseEntity getUserProfile(int id) { User user = userRepository.findById(id). @@ -31,7 +32,7 @@ public ResponseEntity getUserProfile(int id) { .build()); } - public UserProfile createUserProfile(User user){ + public UserProfile createUserProfile(User user) { return UserProfile.builder() .user(user).build(); } @@ -40,34 +41,34 @@ public UserProfile createUserProfile(User user){ public ResponseEntity UpdateUserProfile(int userId, UserProfileDto updateRequest) { User user = userRepository.findById(updateRequest.getId()). orElseThrow(() -> new UsernameNotFoundException("user Not Found")); - // check if this user try to update another user profile - if(userId!=user.getId()){ + // Check if this user try to update another user profile + if (userId != user.getId()) { throw new RuntimeException("this User can't update another user profile"); } return ResponseEntity.ok() .body(ResponseDto.builder() .status(HttpStatus.OK) .success(true) - .data(doUpdate(updateRequest,user)) + .data(doUpdate(updateRequest, user)) .build()); } private UserProfileDto doUpdate(UserProfileDto updateRequest, User user) { - //update fields that in user table - user.setFirstName(updateRequest.getFirstName()); - user.setLastName(updateRequest.getLastName()); + // Update fields that in user table + user.setFirstName(updateRequest.getFirstName()); + user.setLastName(updateRequest.getLastName()); - //get userProfile and update it. - UserProfile userProfile=user.getUserProfile(); - userProfile.setDescription(updateRequest.getDescription()); - userProfile.setLocation(updateRequest.getLocation()); - userProfile.setTitle(userProfile.getTitle()); - userProfile.setHourlyRate(updateRequest.getHourlyRate()); + // Get userProfile and update it. + UserProfile userProfile = user.getUserProfile(); + userProfile.setDescription(updateRequest.getDescription()); + userProfile.setLocation(updateRequest.getLocation()); + userProfile.setTitle(userProfile.getTitle()); + userProfile.setHourlyRate(updateRequest.getHourlyRate()); - // update user in database - user.setUserProfile(userProfile); - userRepository.save(user); - return updateRequest; + // Update user in database + user.setUserProfile(userProfile); + userRepository.save(user); + return updateRequest; } } diff --git a/src/main/java/com/activecourses/upwork/service/user/UserProfileService.java b/src/main/java/com/activecourses/upwork/service/user/profile/UserProfileService.java similarity index 88% rename from src/main/java/com/activecourses/upwork/service/user/UserProfileService.java rename to src/main/java/com/activecourses/upwork/service/user/profile/UserProfileService.java index 0d50830..c8f1340 100644 --- a/src/main/java/com/activecourses/upwork/service/user/UserProfileService.java +++ b/src/main/java/com/activecourses/upwork/service/user/profile/UserProfileService.java @@ -1,4 +1,4 @@ -package com.activecourses.upwork.service.user; +package com.activecourses.upwork.service.user.profile; import com.activecourses.upwork.dto.user.UserProfileDto; import com.activecourses.upwork.model.User; @@ -7,6 +7,7 @@ public interface UserProfileService { ResponseEntity getUserProfile(int id); + UserProfile createUserProfile(User user); ResponseEntity UpdateUserProfile(int userId, UserProfileDto updateRequest); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index b63e64c..63e533e 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -22,9 +22,9 @@ application: security: jwt: secret-key: ${JWT_SECRET_KEY} - expiration: 60000 # 1 minute, for testing + expiration: 600000 # 10 minutes refresh-token: - expiration: 180000 # 3 minutes, for testing + expiration: 604800000 # 7 days cookie: jwt-cookie-name: ${JWT_COOKIE_NAME} jwt-refresh-cookie-name: ${JWT_REFRESH_COOKIE_NAME} diff --git a/src/main/resources/env.properties b/src/main/resources/env.properties index 5cf956c..5cb5170 100644 --- a/src/main/resources/env.properties +++ b/src/main/resources/env.properties @@ -3,14 +3,16 @@ POSTGRES_USER=postgres POSTGRES_PASSWORD=root POSTGRES_DB=upwork -JWT_SECRET_KEY=382b27314b5b3b637a2c766f41596c50636b25755b2b3f4e7721494f61 -JWT_COOKIE_NAME=jwt_token -JWT_REFRESH_COOKIE_NAME=jwt_refresh_token -JWT_COOKIE_MAX_AGE=86400 - spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=test@gmail.com spring.mail.password=test spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true + +application.security.jwt.secret-key=382b27314b5b3b637a2c766f41596c50636b25755b2b3f4e7721494f61 +application.security.jwt.expiration=3600000 +application.security.jwt.refresh-token.expiration=7200000 +application.security.jwt.cookie.jwt-cookie-name=jwt_token +application.security.jwt.cookie.jwt-refresh-cookie-name=jwt_refresh_token +application.security.jwt.cookie.max-age=86400 \ No newline at end of file diff --git a/src/test/java/com/activecourses/upwork/UpworkApplicationTests.java b/src/test/java/com/activecourses/upwork/UpworkApplicationTests.java index 27d9540..c9214f7 100644 --- a/src/test/java/com/activecourses/upwork/UpworkApplicationTests.java +++ b/src/test/java/com/activecourses/upwork/UpworkApplicationTests.java @@ -2,12 +2,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest +@ActiveProfiles("test") class UpworkApplicationTests { - @Test - void contextLoads() { - } - -} + @Test + void contextLoads() { + } +} \ No newline at end of file diff --git a/src/test/java/com/activecourses/upwork/service/AuthServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/AuthServiceImplTest.java new file mode 100644 index 0000000..7975704 --- /dev/null +++ b/src/test/java/com/activecourses/upwork/service/AuthServiceImplTest.java @@ -0,0 +1,237 @@ +package com.activecourses.upwork.service; + +import com.activecourses.upwork.dto.ResponseDto; +import com.activecourses.upwork.dto.authentication.login.LoginRequestDto; +import com.activecourses.upwork.dto.authentication.registration.RegistrationRequestDto; +import com.activecourses.upwork.mapper.Mapper; +import com.activecourses.upwork.model.RefreshToken; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.repository.user.UserRepository; +import com.activecourses.upwork.repository.role.RoleRepository; +import com.activecourses.upwork.service.authentication.AuthServiceImpl; +import com.activecourses.upwork.config.security.jwt.JwtService; +import com.activecourses.upwork.config.security.CustomUserDetailsService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.http.ResponseCookie; + +import java.util.Objects; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class AuthServiceImplTest { + + @Mock + private UserRepository userRepository; + + @Mock + private RoleRepository roleRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @Mock + private Mapper userMapper; + + @Mock + private AuthenticationManager authenticationManager; + + @Mock + private JwtService jwtService; + + @Mock + private JavaMailSender mailSender; + + @Mock + private CustomUserDetailsService customUserDetailsService; + + @Mock + private RefreshTokenService refreshTokenService; + + @InjectMocks + private AuthServiceImpl authService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void registerUser() { + RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto(); + registrationRequestDto.setFirstName("Ahmed"); + registrationRequestDto.setLastName("Muhammed"); + registrationRequestDto.setEmail("am0103738@gmail.com"); + registrationRequestDto.setPassword("password123"); + + User user = new User(); + user.setFirstName("Ahmed"); + user.setLastName("Muhammed"); + user.setEmail("am0103738@gmail.com"); + user.setPassword("encodedPassword"); + + when(userMapper.mapFrom(any(RegistrationRequestDto.class))).thenReturn(user); + when(passwordEncoder.encode(any(String.class))).thenReturn("encodedPassword"); + when(userRepository.save(any(User.class))).thenReturn(user); + when(userRepository.findByEmail("am0103738@gmail.com")).thenReturn(Optional.of(user)); + + authService.registerUser(registrationRequestDto); + + verify(userRepository, times(1)).save(user); + assertNotNull(userRepository.findByEmail("am0103738@gmail.com")); + assertEquals("encodedPassword", user.getPassword()); + } + + @Test + void login() { + LoginRequestDto loginRequestDto = new LoginRequestDto("am0103738@gmail.com", "password123"); + + User user = new User(); + user.setId(1); + user.setEmail("am0103738@gmail.com"); + user.setPassword("encodedPassword"); + user.setAccountEnabled(true); + user.setAccountLocked(false); + + UserDetails userDetails = mock(UserDetails.class); + when(userDetails.getUsername()).thenReturn(user.getEmail()); + when(userDetails.getPassword()).thenReturn(user.getPassword()); + + when(userRepository.findByEmail("am0103738@gmail.com")).thenReturn(Optional.of(user)); + when(authenticationManager.authenticate(any(Authentication.class))).thenReturn(mock(Authentication.class)); + when(customUserDetailsService.loadUserByUsername("am0103738@gmail.com")).thenReturn(user); + + ResponseCookie jwtCookie = mock(ResponseCookie.class); + when(jwtService.generateJwtCookie(any(UserDetails.class))).thenReturn(jwtCookie); + + RefreshToken refreshToken = mock(RefreshToken.class); + when(refreshTokenService.createRefreshToken(anyInt())).thenReturn(refreshToken); + when(refreshToken.getToken()).thenReturn("refreshToken"); + + ResponseCookie refreshJwtCookie = mock(ResponseCookie.class); + when(jwtService.generateRefreshJwtCookie(anyString())).thenReturn(refreshJwtCookie); + + ResponseDto responseDto = authService.login(loginRequestDto); + + assertEquals(HttpStatus.OK, responseDto.getStatus()); + assertTrue(responseDto.isSuccess()); + } + + @Test + void logout() { + User user = new User(); + user.setId(1); + + SecurityContextHolder.getContext().setAuthentication(mock(Authentication.class)); + when(SecurityContextHolder.getContext().getAuthentication().getPrincipal()).thenReturn(user); + when(jwtService.getCleanJwtCookie()).thenReturn(mock(ResponseCookie.class)); + when(jwtService.getCleanJwtRefreshCookie()).thenReturn(mock(ResponseCookie.class)); + + ResponseEntity responseEntity = authService.logout(); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertTrue(Objects.requireNonNull(responseEntity.getBody()).isSuccess()); + } + + @Test + void refreshToken() { + String refreshToken = "refreshToken"; + User user = new User(); + user.setEmail("am0103738@gmail.com"); + + when(jwtService.getUserNameFromJwtToken(refreshToken)).thenReturn("am0103738@gmail.com"); + when(userRepository.findByEmail("am0103738@gmail.com")).thenReturn(Optional.of(user)); + + Optional refreshedUser = authService.refreshToken(refreshToken); + + assertTrue(refreshedUser.isPresent()); + assertEquals("am0103738@gmail.com", refreshedUser.get().getEmail()); + } + + @Test + void verifyUser() { + String token = "verificationToken"; + User user = new User(); + user.setVerificationToken(token); + + when(userRepository.findByVerificationToken(token)).thenReturn(Optional.of(user)); + + boolean isVerified = authService.verifyUser(token); + + assertTrue(isVerified); + assertTrue(user.isAccountEnabled()); + assertNull(user.getVerificationToken()); + } + + @Test + void findByEmail() { + String email = "am0103738@gmail.com"; + User user = new User(); + user.setEmail(email); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + + User foundUser = authService.findByEmail(email); + + assertNotNull(foundUser); + assertEquals(email, foundUser.getEmail()); + } + + @Test + void sendVerificationEmail() { + User user = new User(); + user.setEmail("am0103738@gmail.com"); + user.setVerificationToken("verificationToken"); + + doNothing().when(mailSender).send(any(SimpleMailMessage.class)); + + authService.sendVerificationEmail(user); + + verify(mailSender, times(1)).send(any(SimpleMailMessage.class)); + } + + @Test + void deactivateUser() { + int userId = 1; + User user = new User(); + user.setId(userId); + user.setAccountEnabled(true); + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + boolean success = authService.deactivateUser(userId); + + assertTrue(success); + assertFalse(user.isAccountEnabled()); + } + + @Test + void reactivateUser() { + int userId = 1; + User user = new User(); + user.setId(userId); + user.setAccountEnabled(false); + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + boolean success = authService.reactivateUser(userId); + + assertTrue(success); + assertTrue(user.isAccountEnabled()); + } +} diff --git a/src/test/java/com/activecourses/upwork/service/AuthServiceTest.java b/src/test/java/com/activecourses/upwork/service/AuthServiceTest.java deleted file mode 100644 index c3d35c2..0000000 --- a/src/test/java/com/activecourses/upwork/service/AuthServiceTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.activecourses.upwork.service; - -import com.activecourses.upwork.dto.authentication.registration.RegistrationRequestDto; -import com.activecourses.upwork.mapper.Mapper; -import com.activecourses.upwork.model.User; -import com.activecourses.upwork.repository.user.UserRepository; -import com.activecourses.upwork.service.authentication.AuthServiceImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -class AuthServiceTest { - - @Mock - private UserRepository userRepository; - - @Mock - private PasswordEncoder passwordEncoder; - - @Mock - private Mapper userMapper; - - @InjectMocks - private AuthServiceImpl userService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - @Test - void registerUser() { - RegistrationRequestDto registrationRequestDto = new RegistrationRequestDto(); - registrationRequestDto.setFirstName("Ahmed"); - registrationRequestDto.setLastName("Muhammed"); - registrationRequestDto.setEmail("am0103738@gmail.com"); - registrationRequestDto.setPassword("password123"); - - User user = new User(); - user.setFirstName("Ahmed"); - user.setLastName("Muhammed"); - user.setEmail("am0103738@gmail.com"); - user.setPassword("encodedPassword"); - - when(userMapper.mapFrom(any(RegistrationRequestDto.class))).thenReturn(user); - // Verify that the UserService correctly calls the passwordEncoder.encode method - // Just simulate the behavior of the password encoder without actually performing the encoding - when(passwordEncoder.encode(any(String.class))).thenReturn("encodedPassword"); - when(userRepository.save(any(User.class))).thenReturn(user); - when(userRepository.findByEmail("am0103738@gmail.com")).thenReturn(Optional.of(user)); - - userService.registerUser(registrationRequestDto); - - verify(userRepository, times(1)).save(user); - assertNotNull(userRepository.findByEmail("am0103738@gmail.com")); - assertEquals("encodedPassword", user.getPassword()); - } -} \ No newline at end of file diff --git a/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java new file mode 100644 index 0000000..560e0da --- /dev/null +++ b/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java @@ -0,0 +1,134 @@ +package com.activecourses.upwork.service; + +import com.activecourses.upwork.model.Role; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.repository.role.RoleRepository; +import com.activecourses.upwork.repository.user.UserRepository; +import com.activecourses.upwork.service.role.RoleServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class RoleServiceImplTest { + + @Mock + private RoleRepository roleRepository; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private RoleServiceImpl roleService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void addRole() { + Role role = new Role(); + role.setName("ROLE_TEST"); + + when(roleRepository.save(any(Role.class))).thenReturn(role); + + Role createdRole = roleService.addRole(role); + + assertNotNull(createdRole); + assertEquals("ROLE_TEST", createdRole.getName()); + } + + @Test + void removeRole() { + int roleId = 1; + + Role role = new Role(); + role.setId(roleId); + + when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); + + boolean success = roleService.removeRole(roleId); + + assertTrue(success); + verify(roleRepository, times(1)).delete(role); + } + + @Test + void updateRole() { + int roleId = 1; + + Role role = new Role(); + role.setId(roleId); + role.setName("ROLE_OLD"); + + Role updatedRole = new Role(); + updatedRole.setName("ROLE_NEW"); + + when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); + when(roleRepository.save(any(Role.class))).thenReturn(updatedRole); + + Role result = roleService.updateRole(roleId, updatedRole); + + assertNotNull(result); + assertEquals("ROLE_NEW", result.getName()); + } + + @Test + void getAllRoles() { + Role role1 = new Role(); + role1.setName("ROLE_1"); + + Role role2 = new Role(); + role2.setName("ROLE_2"); + + List roles = List.of(role1, role2); + + when(roleRepository.findAll()).thenReturn(roles); + + List result = roleService.getAllRoles(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(role1)); + assertTrue(result.contains(role2)); + } + + @Test + void assignRolesToUser() { + int userId = 1; + Map roles = new HashMap<>(); + roles.put("ROLE_ADMIN", null); + roles.put("ROLE_USER", null); + + User user = new User(); + user.setId(userId); + + Role adminRole = new Role(); + adminRole.setName("ROLE_ADMIN"); + + Role userRole = new Role(); + userRole.setName("ROLE_USER"); + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(roleRepository.findByName("ROLE_ADMIN")).thenReturn(Optional.of(adminRole)); + when(roleRepository.findByName("ROLE_USER")).thenReturn(Optional.of(userRole)); + + boolean success = roleService.assignRolesToUser(userId, roles); + + assertTrue(success); + assertEquals(2, user.getRoles().size()); + assertTrue(user.getRoles().contains(adminRole)); + assertTrue(user.getRoles().contains(userRole)); + } +} diff --git a/src/test/java/com/activecourses/upwork/service/UserServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/UserServiceImplTest.java new file mode 100644 index 0000000..637be10 --- /dev/null +++ b/src/test/java/com/activecourses/upwork/service/UserServiceImplTest.java @@ -0,0 +1,100 @@ +package com.activecourses.upwork.service; + +import com.activecourses.upwork.dto.user.UserDto; +import com.activecourses.upwork.dto.user.UserResponseDto; +import com.activecourses.upwork.mapper.user.UserDtoMapper; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.repository.user.UserRepository; +import com.activecourses.upwork.service.user.UserServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class UserServiceImplTest { + + @Mock + private UserRepository userRepository; + + @Mock + private UserDtoMapper userDtoMapper; + + @InjectMocks + private UserServiceImpl userService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void getAllUsers() { + User user1 = new User(); + user1.setId(1); + user1.setEmail("user1@example.com"); + + User user2 = new User(); + user2.setId(2); + user2.setEmail("user2@example.com"); + + List users = List.of(user1, user2); + Page pagedResult = new PageImpl<>(users); + + Pageable pageable = PageRequest.of(0, 10, Sort.by("id").ascending()); + + when(userRepository.findAll(any(Pageable.class))).thenReturn(pagedResult); + + UserDto userDto1 = new UserDto(); + userDto1.setId(1); + + UserDto userDto2 = new UserDto(); + userDto2.setId(2); + + when(userDtoMapper.mapTo(user1)).thenReturn(userDto1); + when(userDtoMapper.mapTo(user2)).thenReturn(userDto2); + + UserResponseDto userResponseDto = userService.getAllUsers(0, 10, "id", "asc"); + + assertNotNull(userResponseDto); + assertEquals(2, userResponseDto.getContent().size()); + assertEquals(1, userResponseDto.getContent().get(0).getId()); + assertEquals(2, userResponseDto.getContent().get(1).getId()); + } + + @Test + void findByEmail() { + String email = "user@example.com"; + User user = new User(); + user.setEmail(email); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + + User foundUser = userService.findByEmail(email); + + assertNotNull(foundUser); + assertEquals(email, foundUser.getEmail()); + } + + @Test + void findByEmail_UserNotFound() { + String email = "user@example.com"; + + when(userRepository.findByEmail(email)).thenReturn(Optional.empty()); + + assertThrows(UsernameNotFoundException.class, () -> userService.findByEmail(email)); + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 0000000..dc0d8bb --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,10 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + driver-class-name: org.h2.Driver + username: sa + password: password + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: create-drop \ No newline at end of file