diff --git a/Backend/prepAIred.API/Controllers/AuthController.cs b/Backend/prepAIred.API/Controllers/AuthController.cs index 530fb33..5721b74 100644 --- a/Backend/prepAIred.API/Controllers/AuthController.cs +++ b/Backend/prepAIred.API/Controllers/AuthController.cs @@ -1,5 +1,6 @@ using prepAIred.Data; using prepAIred.Services; +using prepAIred.Exceptions; using Microsoft.AspNetCore.Mvc; namespace prepAIred.API @@ -9,43 +10,107 @@ namespace prepAIred.API /// /// /// This controller is responsible for handling HTTP requests related to authentication flows. It interacts - /// with the for authentication operations and for token management. + /// with the for authentication operations and for token management. /// - /// Repository for handling authentication operations - /// Repository for managing refresh tokens + /// Repository for handling authentication operations + /// Repository for managing refresh tokens [ApiController] - [Route("api/[controller]")] - public class AuthController(IAuthRepository authRepository, IRefreshTokenRepository refreshTokenRepository) : Controller + [Route("api/auth")] + public class AuthController(IAuthService authService, IRefreshTokenService refreshTokenService) : Controller { - private readonly IAuthRepository _authRepository = authRepository; - private readonly IRefreshTokenRepository _refreshTokenService = refreshTokenRepository; + private readonly IAuthService _authService = authService; + private readonly IRefreshTokenService _refreshTokenService = refreshTokenService; [HttpPost("register")] public async Task Register([FromBody] UserCredentialsDTO userCredentialsDto) { - await _authRepository.RegisterAsync(userCredentialsDto); - return Ok("Register successful"); + try + { + await _authService.RegisterAsync(userCredentialsDto); + return Ok("Register successful"); + } + catch (InvalidCredentialsException ex) + { + return Unauthorized(ex.Message); + } + catch (UserAlreadyExistsException ex) + { + return Conflict(ex.Message); + } + catch (EmptyFieldsException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } [HttpPost("login")] public async Task Login([FromBody] LoginDTO loginDto) { - await _authRepository.LoginAsync(loginDto); - return Ok("Login successful"); + try + { + await _authService.LoginAsync(loginDto); + return Ok("Login successful"); + } + catch (InvalidCredentialsException ex) + { + return Unauthorized(ex.Message); + } + catch (EmptyFieldsException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } [HttpPost("refresh-token")] public async Task GenerateNewRefreshToken() { - RefreshTokenResponseDTO newRefreshToken = await _refreshTokenService.GenerateNewRefreshTokenAsync(); - return Ok(newRefreshToken); + try + { + RefreshTokenResponseDTO newRefreshToken = await _refreshTokenService.GenerateNewRefreshTokenAsync(); + return Ok(newRefreshToken); + } + catch (InvalidRefreshTokenException ex) + { + return Unauthorized(ex.Message); + } + catch (InvalidAccessTokenException ex) + { + return Unauthorized(ex.Message); + } + catch (NoUserLoggedInException ex) + { + return Unauthorized(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } [HttpPost("logout")] public async Task Logout() { - await _authRepository.LogoutAsync(); - return Ok("Logged out successfully"); + try + { + await _authService.LogoutAsync(); + return Ok("Logged out successfully"); + } + catch (NoUserLoggedInException ex) + { + return Unauthorized(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } } } \ No newline at end of file diff --git a/Backend/prepAIred.API/Controllers/InterviewController.cs b/Backend/prepAIred.API/Controllers/InterviewController.cs index e46fa81..7aa3dd2 100644 --- a/Backend/prepAIred.API/Controllers/InterviewController.cs +++ b/Backend/prepAIred.API/Controllers/InterviewController.cs @@ -1,5 +1,6 @@ using prepAIred.Data; using prepAIred.Services; +using prepAIred.Exceptions; using Microsoft.AspNetCore.Mvc; namespace prepAIred.API @@ -9,54 +10,144 @@ namespace prepAIred.API /// /// This controller serves as the API layer for interacting with interview data. It exposes /// endpoints for creating new AI interviews and retrieving existing ones. The controller depends on an - /// implementation to handle interview data operations. - /// Repository for handling interview operations + /// implementation to handle interview data operations. + /// Repository for handling interview operations [ApiController] - [Route("api/[controller]")] - public class InterviewController(IInterviewRepository interviewRepository) : Controller + [Route("api/interviews")] + public class InterviewController(IInterviewService interviewService) : Controller { - private readonly IInterviewRepository _interviewRepository = interviewRepository; + private readonly IInterviewService _interviewService = interviewService; - [HttpPost("generate-hr-interviews")] + [HttpPost("hr")] public async Task GenerateHrInterview([FromBody] HrRequestDTO hrRequest) { - await _interviewRepository.GenerateInterviewsAsync(hrRequest); - return Ok("HR interviews created successfully."); + try + { + await _interviewService.GenerateInterviewsAsync(hrRequest); + return Ok("HR interviews created successfully."); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-latest-hr-interviews")] + [HttpGet("hr/latest")] public async Task GetLatestHrInterview() { - List hrInterviews = await _interviewRepository.GetLatestInterviewsAsync(); - return Ok(hrInterviews); + try + { + List hrInterviews = await _interviewService.GetLatestInterviewsAsync(); + return Ok(hrInterviews); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpPost("evaluate-hr-interviews")] + [HttpPost("hr/evaluations")] public async Task EvaluateHrInterviews([FromBody] List evaluateRequests) { - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); - return Ok("HR interviews evaluated successfully."); + try + { + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); + return Ok("HR interviews evaluated successfully."); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpPost("generate-technical-interviews")] + [HttpPost("technical")] public async Task GenerateTechnicalInterviews([FromBody] TechnicalRequestDTO technicalRequest) { - await _interviewRepository.GenerateInterviewsAsync(technicalRequest); - return Ok("Technical interviews created successfully."); + try + { + await _interviewService.GenerateInterviewsAsync(technicalRequest); + return Ok("Technical interviews created successfully."); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-latest-technical-interviews")] + [HttpGet("technical/latest")] public async Task GetLatestTechnicalInterview() { - List technicalInterviews = await _interviewRepository.GetLatestInterviewsAsync(); - return Ok(technicalInterviews); + try + { + List technicalInterviews = await _interviewService.GetLatestInterviewsAsync(); + return Ok(technicalInterviews); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpPost("evaluate-technical-interviews")] + [HttpPost("technical/evaluations")] public async Task EvaluateTechnicalInterviews([FromBody] List evaluateRequests) { - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); - return Ok("Technical interviews evaluated successfully."); + try + { + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); + return Ok("Technical interviews evaluated successfully."); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } } } \ No newline at end of file diff --git a/Backend/prepAIred.API/Controllers/InterviewSessionController.cs b/Backend/prepAIred.API/Controllers/InterviewSessionController.cs index a67b170..31eb2b3 100644 --- a/Backend/prepAIred.API/Controllers/InterviewSessionController.cs +++ b/Backend/prepAIred.API/Controllers/InterviewSessionController.cs @@ -1,5 +1,6 @@ using prepAIred.Data; using prepAIred.Services; +using prepAIred.Exceptions; using Microsoft.AspNetCore.Mvc; namespace prepAIred.API @@ -8,69 +9,157 @@ namespace prepAIred.API /// Provides endpoints for managing interview sessions, including retrieving and deleting sessions. /// /// This controller handles HTTP requests related to interview sessions. It interacts with the - /// to perform operations such as retrieving a list of interview sessions + /// to perform operations such as retrieving a list of interview sessions /// and deleting all existing sessions. - /// + /// [ApiController] - [Route("api/[controller]")] - public class InterviewSessionController(IInterviewSessionRepository interviewSessionRepository) : Controller + [Route("api/interview-sessions")] + public class InterviewSessionController(IInterviewSessionService interviewSessionService) : Controller { - private readonly IInterviewSessionRepository _interviewSessionRepository = interviewSessionRepository; + private readonly IInterviewSessionService _interviewSessionService = interviewSessionService; - [HttpGet("get-interview-sessions")] + [HttpGet] public async Task GetInterviewSessionDTOs() { - List interviewSessions = await _interviewSessionRepository.GetInterviewSessionDTOsAsync(); - return Ok(interviewSessions); + try + { + List interviewSessions = await _interviewSessionService.GetInterviewSessionDTOsAsync(); + return Ok(interviewSessions); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-interview-session-activities")] + [HttpGet("activities")] public async Task GetInterviewSessionActivities() { - List interviewSessionActivities = await _interviewSessionRepository.GetInterviewSessionActivitiesAsync(); - return Ok(interviewSessionActivities); + try + { + List interviewSessionActivities = await _interviewSessionService.GetInterviewSessionActivitiesAsync(); + return Ok(interviewSessionActivities); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-interview-session-statistics")] + [HttpGet("statistics")] public async Task GetInterviewSessionStatistics() { - ProfileStatisticsDTO profileStats = await _interviewSessionRepository.GetInterviewSessionStatisticsAsync(); - return Ok(profileStats); + try + { + ProfileStatisticsDTO profileStats = await _interviewSessionService.GetInterviewSessionStatisticsAsync(); + return Ok(profileStats); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-interview-session-performance")] + [HttpGet("performance")] public async Task GetInterviewSessionPerformance() { - List performanceData = await _interviewSessionRepository.GetInterviewSessionPerformanceAsync(); - return Ok(performanceData); + try + { + List performanceData = await _interviewSessionService.GetInterviewSessionPerformanceAsync(); + return Ok(performanceData); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-interview-session-programming-language-data")] + [HttpGet("programming-languages")] public async Task GetInterviewSessionProgrammingLanguageData() { - List programmingLanguageData = await _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync(); - return Ok(programmingLanguageData); + try + { + List programmingLanguageData = await _interviewSessionService.GetInterviewSessionProgrammingLanguageDataAsync(); + return Ok(programmingLanguageData); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpGet("get-interview-session-position-data")] + [HttpGet("positions")] public async Task GetInterviewSessionPositionData() { - List positionData = await _interviewSessionRepository.GetInterviewSessionPositionDataAsync(); - return Ok(positionData); + try + { + List positionData = await _interviewSessionService.GetInterviewSessionPositionDataAsync(); + return Ok(positionData); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpPut("finish-interview-session")] + [HttpPut("current/finish")] public async Task FinishInterviewSession() { - await _interviewSessionRepository.FinishInterviewSessionAsync(); - return Ok("Interview session finished successfully."); + try + { + await _interviewSessionService.FinishInterviewSessionAsync(); + return Ok("Interview session finished successfully."); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpDelete("delete-interview-sessions")] + [HttpDelete] public async Task DeleteInterviewSessions() { - await _interviewSessionRepository.DeleteInterviewSessionsAsync(); - return Ok("All interview sessions deleted successfully."); + try + { + await _interviewSessionService.DeleteInterviewSessionsAsync(); + return Ok("All interview sessions deleted successfully."); + } + catch (InterviewSessionNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } } } diff --git a/Backend/prepAIred.API/Controllers/ProfilePictureController.cs b/Backend/prepAIred.API/Controllers/ProfilePictureController.cs index dcee73e..f67871b 100644 --- a/Backend/prepAIred.API/Controllers/ProfilePictureController.cs +++ b/Backend/prepAIred.API/Controllers/ProfilePictureController.cs @@ -1,27 +1,54 @@ using prepAIred.Data; using prepAIred.Services; +using prepAIred.Exceptions; using Microsoft.AspNetCore.Mvc; namespace prepAIred.API { [ApiController] - [Route("api/[controller]")] - public class ProfilePictureController(IProfilePictureRepository profilePictureRepository) : Controller + [Route("api/profile-pictures")] + public class ProfilePictureController(IProfilePictureService profilePictureService) : Controller { - private readonly IProfilePictureRepository _profilePictureRepository = profilePictureRepository; + private readonly IProfilePictureService _profilePictureService = profilePictureService; - [HttpGet("get-profile-picture-url")] + [HttpGet] public async Task GetProfilePicture() { - string profilePictureUrl = await _profilePictureRepository.GetProfilePictureUrlAsync(); - return Ok(profilePictureUrl); + try + { + string profilePictureUrl = await _profilePictureService.GetProfilePictureUrlAsync(); + return Ok(profilePictureUrl); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpPost("change-profile-picture")] + [HttpPut] public async Task ChangeProfilePicture([FromForm] ProfilePictureDTO profilePictureDTO) { - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDTO); - return Ok("Profile picture changed"); + try + { + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDTO); + return Ok("Profile picture changed"); + } + catch (UnsupportedFileExtensionException ex) + { + return BadRequest(ex.Message); + } + catch (ProfilePictureException ex) + { + return BadRequest(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } } } diff --git a/Backend/prepAIred.API/Controllers/UserController.cs b/Backend/prepAIred.API/Controllers/UserController.cs index 58c1be5..c7e2a00 100644 --- a/Backend/prepAIred.API/Controllers/UserController.cs +++ b/Backend/prepAIred.API/Controllers/UserController.cs @@ -1,5 +1,6 @@ using prepAIred.Data; using prepAIred.Services; +using prepAIred.Exceptions; using Microsoft.AspNetCore.Mvc; namespace prepAIred.API @@ -9,39 +10,76 @@ namespace prepAIred.API /// /// /// This controller is responsible for handling HTTP requests related to user data. It interacts - /// with the to retrieve and manage user information. + /// with the to retrieve and manage user information. /// - /// Repository for handling user-related operations + /// Repository for handling user-related operations [ApiController] - [Route("api/[controller]")] - public class UserController(IUserRepository userRepository) : Controller + [Route("api/users")] + public class UserController(IUserService userService) : Controller { - private readonly IUserRepository _userRepository = userRepository; + private readonly IUserService _userService = userService; - [HttpGet("get-current-user")] + [HttpGet("me")] public async Task GetCurrentUser() { - if (string.IsNullOrEmpty(Request.Cookies["AccessToken"])) + try { - return NoContent(); - } + if (string.IsNullOrEmpty(Request.Cookies["AccessToken"])) + { + return NoContent(); + } - CurrentUserDTO currentUser = await _userRepository.GetCurrentUserAsync(); - return Ok(currentUser); + CurrentUserDTO currentUser = await _userService.GetCurrentUserAsync(); + return Ok(currentUser); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpPut("update-current-user")] + [HttpPut("me")] public async Task UpdateCurrentUser([FromBody] UserCredentialsDTO userCredentialsDto) { - await _userRepository.UpdateCurrentUserAsync(userCredentialsDto); - return Ok("User updated"); + try + { + await _userService.UpdateCurrentUserAsync(userCredentialsDto); + return Ok("User updated"); + } + catch (DataValidationException ex) + { + return BadRequest(ex.Message); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } - [HttpDelete("delete-current-user")] + [HttpDelete("me")] public async Task DeleteCurrentUser() { - await _userRepository.DeleteCurrentUserAsync(); - return Ok("User deleted"); + try + { + await _userService.DeleteCurrentUserAsync(); + return Ok("User deleted"); + } + catch (ResourceNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } } } } diff --git a/Backend/prepAIred.API/Program.cs b/Backend/prepAIred.API/Program.cs index db2df57..7b43e6e 100644 --- a/Backend/prepAIred.API/Program.cs +++ b/Backend/prepAIred.API/Program.cs @@ -35,21 +35,21 @@ options.UseSqlServer(connectionString); }); -builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Backend/prepAIred.Exceptions/Exceptions/CustomExceptions.cs b/Backend/prepAIred.Exceptions/Exceptions/CustomExceptions.cs index 84053de..2dc3eb1 100644 --- a/Backend/prepAIred.Exceptions/Exceptions/CustomExceptions.cs +++ b/Backend/prepAIred.Exceptions/Exceptions/CustomExceptions.cs @@ -49,4 +49,19 @@ public class ProfilePictureException(string message) : Exception(message); /// Exception thrown when an unsupported image format is specified. /// public class UnsupportedFileExtensionException(string message) : Exception(message); + + /// + /// Exception thrown when an interview session is not found. + /// + public class InterviewSessionNotFoundException(string message) : Exception(message); + + /// + /// Exception thrown when an interview is not found. + /// + public class InterviewNotFoundException(string message) : Exception(message); + + /// + /// Exception thrown when user data validation fails (e.g., invalid email format, weak password). + /// + public class DataValidationException(string message) : Exception(message); } diff --git a/Backend/prepAIred.Exceptions/Exceptions/GlobalExceptionHandler.cs b/Backend/prepAIred.Exceptions/Exceptions/GlobalExceptionHandler.cs index 7983421..c10bf38 100644 --- a/Backend/prepAIred.Exceptions/Exceptions/GlobalExceptionHandler.cs +++ b/Backend/prepAIred.Exceptions/Exceptions/GlobalExceptionHandler.cs @@ -16,7 +16,12 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e InvalidRefreshTokenException => StatusCodes.Status401Unauthorized, UserAlreadyExistsException => StatusCodes.Status409Conflict, ResourceNotFoundException => StatusCodes.Status404NotFound, + InterviewSessionNotFoundException => StatusCodes.Status404NotFound, + InterviewNotFoundException => StatusCodes.Status404NotFound, EmptyFieldsException => StatusCodes.Status400BadRequest, + DataValidationException => StatusCodes.Status400BadRequest, + UnsupportedFileExtensionException => StatusCodes.Status400BadRequest, + ProfilePictureException => StatusCodes.Status400BadRequest, ApplicationException => StatusCodes.Status400BadRequest, _ => StatusCodes.Status500InternalServerError }; diff --git a/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthRepository.cs b/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthRepository.cs index eb5db77..d418ff9 100644 --- a/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthRepository.cs +++ b/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthRepository.cs @@ -3,26 +3,35 @@ namespace prepAIred.Services { /// - /// Repository interface for handling authentication-related data operations. + /// Service interface for handling authentication business logic. /// public interface IAuthRepository { /// - /// Registers a new user in the system. + /// Registers a new user with hashed credentials. /// - /// The registration data transfer object containing user details. - /// A task representing the asynchronous operation. - Task RegisterAsync(UserCredentialsDTO userCredentialsDto); + /// The registration details. + /// The pre-hashed password. + /// The salt used in password hashing. + /// The newly created user entity. + Task RegisterAsync(UserCredentialsDTO userCredentialsDto, byte[] hashedPassword, byte[] saltPassword); + + /// + /// Authenticates a user and returns their information. + /// + /// The login credentials. + /// The authenticated user entity. + Task LoginAsync(LoginDTO loginDto); /// - /// Authenticates a user using their credentials. + /// Generates authentication response for a user. /// - /// The login data transfer object containing user credentials. + /// The user to generate authentication response for. /// A task representing the asynchronous operation. - Task LoginAsync(LoginDTO loginDto); + Task GenerateAuthResponseAsync(CurrentUserDTO currentUser); /// - /// Logs out the current user from the system. + /// Logs out the current user and cleans up their session. /// /// A task representing the asynchronous operation. Task LogoutAsync(); diff --git a/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthService.cs b/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthService.cs index a556b75..8dd21a2 100644 --- a/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthService.cs +++ b/Backend/prepAIred.Services/Interfaces/Authentication/Auth/IAuthService.cs @@ -3,35 +3,26 @@ namespace prepAIred.Services { /// - /// Service interface for handling authentication business logic. + /// Repository interface for handling authentication-related data operations. /// public interface IAuthService { /// - /// Registers a new user with hashed credentials. + /// Registers a new user in the system. /// - /// The registration details. - /// The pre-hashed password. - /// The salt used in password hashing. - /// The newly created user entity. - Task RegisterAsync(UserCredentialsDTO userCredentialsDto, byte[] hashedPassword, byte[] saltPassword); - - /// - /// Authenticates a user and returns their information. - /// - /// The login credentials. - /// The authenticated user entity. - Task LoginAsync(LoginDTO loginDto); + /// The registration data transfer object containing user details. + /// A task representing the asynchronous operation. + Task RegisterAsync(UserCredentialsDTO userCredentialsDto); /// - /// Generates authentication response for a user. + /// Authenticates a user using their credentials. /// - /// The user to generate authentication response for. + /// The login data transfer object containing user credentials. /// A task representing the asynchronous operation. - Task GenerateAuthResponseAsync(CurrentUserDTO currentUser); + Task LoginAsync(LoginDTO loginDto); /// - /// Logs out the current user and cleans up their session. + /// Logs out the current user from the system. /// /// A task representing the asynchronous operation. Task LogoutAsync(); diff --git a/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenRepository.cs b/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenRepository.cs index 5bc3f59..13819f1 100644 --- a/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenRepository.cs +++ b/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenRepository.cs @@ -3,14 +3,29 @@ namespace prepAIred.Services { /// - /// Repository interface for managing refresh token data operations. + /// Service interface for managing refresh token operations. /// public interface IRefreshTokenRepository { /// - /// Generates a new refresh token and its associated response. + /// Adds a new refresh token to the system. /// - /// A response containing the new refresh token and access token details. - Task GenerateNewRefreshTokenAsync(); + /// The refresh token to add. + /// The added refresh token entity. + Task AddRefreshTokenAsync(RefreshToken refreshToken); + + /// + /// Retrieves a refresh token by its token string. + /// + /// The token string to search for. + /// The matching refresh token entity if found. + Task GetRefreshTokenAsync(string refreshToken); + + /// + /// Retrieves a refresh token by user ID. + /// + /// The ID of the user. + /// The refresh token entity associated with the user if found. + Task GetRefreshTokenByUserIdAsync(int userID); } } diff --git a/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenService.cs b/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenService.cs index 64d4268..a169990 100644 --- a/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenService.cs +++ b/Backend/prepAIred.Services/Interfaces/Authentication/RefreshToken/IRefreshTokenService.cs @@ -3,29 +3,14 @@ namespace prepAIred.Services { /// - /// Service interface for managing refresh token operations. + /// Repository interface for managing refresh token data operations. /// public interface IRefreshTokenService { /// - /// Adds a new refresh token to the system. + /// Generates a new refresh token and its associated response. /// - /// The refresh token to add. - /// The added refresh token entity. - Task AddRefreshTokenAsync(RefreshToken refreshToken); - - /// - /// Retrieves a refresh token by its token string. - /// - /// The token string to search for. - /// The matching refresh token entity if found. - Task GetRefreshTokenAsync(string refreshToken); - - /// - /// Retrieves a refresh token by user ID. - /// - /// The ID of the user. - /// The refresh token entity associated with the user if found. - Task GetRefreshTokenByUserIdAsync(int userID); + /// A response containing the new refresh token and access token details. + Task GenerateNewRefreshTokenAsync(); } } diff --git a/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionRepository.cs b/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionRepository.cs index 3226b5b..95bbe84 100644 --- a/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionRepository.cs +++ b/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionRepository.cs @@ -3,61 +3,128 @@ namespace prepAIred.Services { /// - /// Defines methods for managing interview session data. + /// Defines methods for managing interview session entities. /// public interface IInterviewSessionRepository { /// - /// Asynchronously retrieves a list of interview session data transfer objects (DTOs). + /// Creates and persists a new interview session entity asynchronously. /// - /// A task that represents the asynchronous operation. The task result contains a list of - /// objects representing the interview sessions. - Task> GetInterviewSessionDTOsAsync(); + /// The entity to create and store. + /// A task representing the asynchronous operation. + Task CreateInterviewSessionAsync(InterviewSession interviewSession); /// - /// Asynchronously retrieves the most recent interview sessions for the current user. + /// Updates the details of an existing interview session. /// - /// A task that represents the asynchronous operation. The task result contains a list of - /// objects representing the most recent interview sessions. - Task> GetInterviewSessionActivitiesAsync(); + /// This method updates the interview session with the provided details. Ensure that the + /// object contains valid and complete information before calling this + /// method. + /// The object containing the updated details of the interview session. + /// A task that represents the asynchronous operation. + Task UpdateInterviewSessionAsync(InterviewSession interviewSession); + + /// + /// Retrieves the interview session that is adjacent to the current session for the specified user. + /// + /// This method is intended to be used in scenarios where interview sessions are + /// sequentially linked for a user. The definition of "adjacent" may depend on the implementation, such as the + /// next or previous session in chronological order. + /// The unique identifier of the user whose adjacent interview session is to be retrieved. + /// A task that represents the asynchronous operation. The task result contains the adjacent for the specified user, or if no adjacent session exists. + Task GetAdjacentInterviewSessionAsync(int userID); + + /// + /// Retrieves an interview session by its unique identifier. + /// + /// The unique identifier of the interview session to retrieve. + /// A task that represents the asynchronous operation. The task result contains the object with the specified ID, or if not found. + Task GetInterviewSessionByIdAsync(int sessionID); /// - /// Asynchronously retrieves interview session statistics. + /// Gets an interview session based on the provided evaluation requests. /// - /// A task that represents the asynchronous operation. The task result contains a - /// object representing the interview session statistics. - Task GetInterviewSessionStatisticsAsync(); + /// A list of evaluation requests containing the questions and answer. + /// A task that represents the asynchronous operation. The task result contains the generated + /// object based on the provided evaluation requests. + Task GetInterviewSessionFromQuestionsAsync(List evaluateRequests); /// - /// Asynchronously retrieves performance data for interview sessions. + /// Retrieves the most recent interview session ID associated with the specified user. /// - /// A task that represents the asynchronous operation. The task result contains a list of - /// objects representing the performance data for interview sessions. - Task> GetInterviewSessionPerformanceAsync(); + /// The unique identifier of the user whose latest interview session ID is to be retrieved. + /// A task that represents the asynchronous operation. The task result contains the ID of the latest interview session. + Task GetLatestInterviewSessionIDAsync(int userID); /// - /// Asynchronously retrieves programming language data for interview sessions. + /// Retrieves all interviews associated with a specific user by their unique identifier. /// - /// A task that represents the asynchronous operation. The task result contains a list of - /// objects representing the programming language data. - Task> GetInterviewSessionProgrammingLanguageDataAsync(); + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains a list of objects. + Task> GetInterviewSessionsByUserIdAsync(int userID); /// - /// Asynchronously retrieves position data for interview sessions. + /// Deletes the specified interview sessions asynchronously. /// - /// A task that represents the asynchronous operation. The task result contains a list of - /// objects representing the position data. - Task> GetInterviewSessionPositionDataAsync(); + /// A list of objects representing the interview sessions to delete. + Task DeleteInterviewSessionsAsync(List interviewSessions); /// - /// Completes the current interview session and performs any necessary finalization tasks. + /// Marks an interview session as finished and updates its completion status. /// + /// The object to finish. /// A task that represents the asynchronous operation. - Task FinishInterviewSessionAsync(); + Task FinishInterviewSessionAsync(InterviewSession interviewSession); + + /// + /// Retrieves the total count of interview sessions associated with a specific user. + /// + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains the total number of interview sessions. + Task GetTotalInterviewSessionsAsync(int userID); + + /// + /// Retrieves the count of passed interview sessions for a specific user. + /// + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains the number of interview sessions that were passed. + Task GetPassedInterviewSessionsAsync(int userID); + + /// + /// Retrieves the count of ongoing interview sessions for a specific user. + /// + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains the number of interview sessions that are currently ongoing. + Task GetOngoingInterviewSessionsAsync(int userID); + + /// + /// Calculates and retrieves the average score of all interview sessions for a specific user. + /// + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains the average score as a decimal value. + Task GetAverageScoreAsync(int userID); + + /// + /// Calculates and retrieves the completion rate of interview sessions for a specific user. + /// + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains the completion rate as a decimal percentage value. + Task GetCompletionRateAsync(int userID); + + /// + /// Finalizes an interview session by updating its status and associated interviews based on the evaluated interviews + /// + /// The type of interview being finalized. + /// The interview session to be finalized. + /// The list of evaluated interviews to update. + void FinalizeInterviewSession(InterviewSession interviewSession, List evaluatedTInterviews) where TInterview : Interview; /// - /// Deletes all interview sessions asynchronously. + /// Retrieves recent interview session activities for the user. /// - Task DeleteInterviewSessionsAsync(); + /// The unique identifier of the user. + /// A task that represents the asynchronous operation. The task result contains a list of recent interview session activities. + Task> GetInterviewSessionActivitiesAsync(int userID); } } diff --git a/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionService.cs b/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionService.cs index 71bb781..92778b3 100644 --- a/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionService.cs +++ b/Backend/prepAIred.Services/Interfaces/Interview/InterviewSessions/IInterviewSessionService.cs @@ -3,128 +3,61 @@ namespace prepAIred.Services { /// - /// Defines methods for managing interview session entities. + /// Defines methods for managing interview session data. /// public interface IInterviewSessionService { /// - /// Creates and persists a new interview session entity asynchronously. + /// Asynchronously retrieves a list of interview session data transfer objects (DTOs). /// - /// The entity to create and store. - /// A task representing the asynchronous operation. - Task CreateInterviewSessionAsync(InterviewSession interviewSession); + /// A task that represents the asynchronous operation. The task result contains a list of + /// objects representing the interview sessions. + Task> GetInterviewSessionDTOsAsync(); /// - /// Updates the details of an existing interview session. + /// Asynchronously retrieves the most recent interview sessions for the current user. /// - /// This method updates the interview session with the provided details. Ensure that the - /// object contains valid and complete information before calling this - /// method. - /// The object containing the updated details of the interview session. - /// A task that represents the asynchronous operation. - Task UpdateInterviewSessionAsync(InterviewSession interviewSession); - - /// - /// Retrieves the interview session that is adjacent to the current session for the specified user. - /// - /// This method is intended to be used in scenarios where interview sessions are - /// sequentially linked for a user. The definition of "adjacent" may depend on the implementation, such as the - /// next or previous session in chronological order. - /// The unique identifier of the user whose adjacent interview session is to be retrieved. - /// A task that represents the asynchronous operation. The task result contains the adjacent for the specified user, or if no adjacent session exists. - Task GetAdjacentInterviewSessionAsync(int userID); - - /// - /// Retrieves an interview session by its unique identifier. - /// - /// The unique identifier of the interview session to retrieve. - /// A task that represents the asynchronous operation. The task result contains the object with the specified ID, or if not found. - Task GetInterviewSessionByIdAsync(int sessionID); + /// A task that represents the asynchronous operation. The task result contains a list of + /// objects representing the most recent interview sessions. + Task> GetInterviewSessionActivitiesAsync(); /// - /// Gets an interview session based on the provided evaluation requests. + /// Asynchronously retrieves interview session statistics. /// - /// A list of evaluation requests containing the questions and answer. - /// A task that represents the asynchronous operation. The task result contains the generated - /// object based on the provided evaluation requests. - Task GetInterviewSessionFromQuestionsAsync(List evaluateRequests); + /// A task that represents the asynchronous operation. The task result contains a + /// object representing the interview session statistics. + Task GetInterviewSessionStatisticsAsync(); /// - /// Retrieves the most recent interview session ID associated with the specified user. + /// Asynchronously retrieves performance data for interview sessions. /// - /// The unique identifier of the user whose latest interview session ID is to be retrieved. - /// A task that represents the asynchronous operation. The task result contains the ID of the latest interview session. - Task GetLatestInterviewSessionIDAsync(int userID); + /// A task that represents the asynchronous operation. The task result contains a list of + /// objects representing the performance data for interview sessions. + Task> GetInterviewSessionPerformanceAsync(); /// - /// Retrieves all interviews associated with a specific user by their unique identifier. + /// Asynchronously retrieves programming language data for interview sessions. /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains a list of objects. - Task> GetInterviewSessionsByUserIdAsync(int userID); + /// A task that represents the asynchronous operation. The task result contains a list of + /// objects representing the programming language data. + Task> GetInterviewSessionProgrammingLanguageDataAsync(); /// - /// Deletes the specified interview sessions asynchronously. + /// Asynchronously retrieves position data for interview sessions. /// - /// A list of objects representing the interview sessions to delete. - Task DeleteInterviewSessionsAsync(List interviewSessions); + /// A task that represents the asynchronous operation. The task result contains a list of + /// objects representing the position data. + Task> GetInterviewSessionPositionDataAsync(); /// - /// Marks an interview session as finished and updates its completion status. + /// Completes the current interview session and performs any necessary finalization tasks. /// - /// The object to finish. /// A task that represents the asynchronous operation. - Task FinishInterviewSessionAsync(InterviewSession interviewSession); - - /// - /// Retrieves the total count of interview sessions associated with a specific user. - /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains the total number of interview sessions. - Task GetTotalInterviewSessionsAsync(int userID); - - /// - /// Retrieves the count of passed interview sessions for a specific user. - /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains the number of interview sessions that were passed. - Task GetPassedInterviewSessionsAsync(int userID); - - /// - /// Retrieves the count of ongoing interview sessions for a specific user. - /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains the number of interview sessions that are currently ongoing. - Task GetOngoingInterviewSessionsAsync(int userID); - - /// - /// Calculates and retrieves the average score of all interview sessions for a specific user. - /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains the average score as a decimal value. - Task GetAverageScoreAsync(int userID); - - /// - /// Calculates and retrieves the completion rate of interview sessions for a specific user. - /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains the completion rate as a decimal percentage value. - Task GetCompletionRateAsync(int userID); - - /// - /// Finalizes an interview session by updating its status and associated interviews based on the evaluated interviews - /// - /// The type of interview being finalized. - /// The interview session to be finalized. - /// The list of evaluated interviews to update. - void FinalizeInterviewSession(InterviewSession interviewSession, List evaluatedTInterviews) where TInterview : Interview; + Task FinishInterviewSessionAsync(); /// - /// Retrieves recent interview session activities for the user. + /// Deletes all interview sessions asynchronously. /// - /// The unique identifier of the user. - /// A task that represents the asynchronous operation. The task result contains a list of recent interview session activities. - Task> GetInterviewSessionActivitiesAsync(int userID); + Task DeleteInterviewSessionsAsync(); } } diff --git a/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewRepository.cs b/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewRepository.cs index 9e7ac5a..b5ebbe1 100644 --- a/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewRepository.cs +++ b/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewRepository.cs @@ -3,35 +3,54 @@ namespace prepAIred.Services { /// - /// Defines methods for performing data operations related to AI-generated interviews. + /// Defines methods for managing and retrieving interview data. /// public interface IInterviewRepository { /// - /// Generates a collection of interviews based on the specified AI request. + /// Creates and persists multiple interview entities in the database for a given user and session. /// - /// The type of the interview objects to generate. Must be a reference type. - /// The AI request containing the parameters and context for generating interviews. - /// A task that represents the asynchronous operation. - Task GenerateInterviewsAsync(BaseRequestDTO aIRequest) where TInterview : Interview; + /// The list of entities to create. + /// The user for whom the interviews are being created. + /// The interview session to which the interviews belong. + /// A task representing the asynchronous operation. + Task CreateInterviewsAsync(List interviews, User currentUser, InterviewSession interviewSession); /// - /// Retrieves the most recent interview record and maps it to the specified DTO type. + /// Updates multiple interview entities in the database with their current state and evaluation results. /// - /// The type representing the interview entity in the data source. - /// The type to which the interview entity will be mapped. - /// A task that represents the asynchronous operation. The task result contains the most recent interview - /// mapped to the specified DTO type, or if no interviews are available. - Task> GetLatestInterviewsAsync() - where TInterview : Interview + /// The type of interview objects to update. Must derive from the Interview class. + /// The list of interview objects containing updated information to persist to the database. + /// A task representing the asynchronous update operation. + Task UpdateInterviewAsync(List interviews) where TInterview : Interview; + + /// + /// Retrieves a list of interviews associated with the specified session ID. + /// + /// The type of interview to retrieve. Must derive from the class. + /// The unique identifier of the session for which interviews are to be retrieved. Must be a positive integer. + /// A task that represents the asynchronous operation. The task result contains a list of interviews of type + /// associated with the specified session ID. If no interviews are found, the list will be empty. + Task> GetInterviewsBySessionIdAsync(int sessionID) where TInterview : Interview; + + /// + /// Retrieves the latest interviews from the provided list and maps them to the specified DTO type. + /// + /// The type of the interview entities, which must inherit from . + /// The type of the interview DTOs, which must inherit from . + /// The list of interview entities to process. + /// A list of the latest interviews mapped to the specified DTO type. The list will be empty if no interviews + /// are provided. + List GetLatestInterviews(List interviews) + where TInterview : Interview where TInterviewDTO : InterviewDTO; /// - /// Evaluates a collection of interviews based on the specified evaluation request. + /// Updates an existing interview in the collection with the provided evaluated interview. /// - /// The type of interview to evaluate. Must derive from the class. - /// The evaluation request containing the criteria and parameters for the evaluation process. - /// A task that represents the asynchronous operation. - Task EvaluateInterviewsAsync(List evaluateRequest) where TInterview : Interview; + /// The type of the interview, which must derive from . + /// The evaluated interview to update the existing interview with. + /// The collection of existing interviews to search for a match to update. + void UpdateExistingInterviewWithEvaluation(List evaluatedInterviews, List existingInterviews) where TInterview : Interview; } -} +} \ No newline at end of file diff --git a/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewService.cs b/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewService.cs index a945e7c..8744157 100644 --- a/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewService.cs +++ b/Backend/prepAIred.Services/Interfaces/Interview/Interviews/IInterviewService.cs @@ -3,54 +3,35 @@ namespace prepAIred.Services { /// - /// Defines methods for managing and retrieving interview data. + /// Defines methods for performing data operations related to AI-generated interviews. /// public interface IInterviewService { /// - /// Creates and persists multiple interview entities in the database for a given user and session. + /// Generates a collection of interviews based on the specified AI request. /// - /// The list of entities to create. - /// The user for whom the interviews are being created. - /// The interview session to which the interviews belong. - /// A task representing the asynchronous operation. - Task CreateInterviewsAsync(List interviews, User currentUser, InterviewSession interviewSession); + /// The type of the interview objects to generate. Must be a reference type. + /// The AI request containing the parameters and context for generating interviews. + /// A task that represents the asynchronous operation. + Task GenerateInterviewsAsync(BaseRequestDTO aIRequest) where TInterview : Interview; /// - /// Updates multiple interview entities in the database with their current state and evaluation results. + /// Retrieves the most recent interview record and maps it to the specified DTO type. /// - /// The type of interview objects to update. Must derive from the Interview class. - /// The list of interview objects containing updated information to persist to the database. - /// A task representing the asynchronous update operation. - Task UpdateInterviewAsync(List interviews) where TInterview : Interview; - - /// - /// Retrieves a list of interviews associated with the specified session ID. - /// - /// The type of interview to retrieve. Must derive from the class. - /// The unique identifier of the session for which interviews are to be retrieved. Must be a positive integer. - /// A task that represents the asynchronous operation. The task result contains a list of interviews of type - /// associated with the specified session ID. If no interviews are found, the list will be empty. - Task> GetInterviewsBySessionIdAsync(int sessionID) where TInterview : Interview; - - /// - /// Retrieves the latest interviews from the provided list and maps them to the specified DTO type. - /// - /// The type of the interview entities, which must inherit from . - /// The type of the interview DTOs, which must inherit from . - /// The list of interview entities to process. - /// A list of the latest interviews mapped to the specified DTO type. The list will be empty if no interviews - /// are provided. - List GetLatestInterviews(List interviews) - where TInterview : Interview + /// The type representing the interview entity in the data source. + /// The type to which the interview entity will be mapped. + /// A task that represents the asynchronous operation. The task result contains the most recent interview + /// mapped to the specified DTO type, or if no interviews are available. + Task> GetLatestInterviewsAsync() + where TInterview : Interview where TInterviewDTO : InterviewDTO; /// - /// Updates an existing interview in the collection with the provided evaluated interview. + /// Evaluates a collection of interviews based on the specified evaluation request. /// - /// The type of the interview, which must derive from . - /// The evaluated interview to update the existing interview with. - /// The collection of existing interviews to search for a match to update. - void UpdateExistingInterviewWithEvaluation(List evaluatedInterviews, List existingInterviews) where TInterview : Interview; + /// The type of interview to evaluate. Must derive from the class. + /// The evaluation request containing the criteria and parameters for the evaluation process. + /// A task that represents the asynchronous operation. + Task EvaluateInterviewsAsync(List evaluateRequest) where TInterview : Interview; } -} \ No newline at end of file +} diff --git a/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureRepository.cs b/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureRepository.cs index 0138505..562cbcb 100644 --- a/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureRepository.cs +++ b/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureRepository.cs @@ -1,24 +1,32 @@ -using prepAIred.Data; +using Microsoft.AspNetCore.Http; namespace prepAIred.Services { /// - /// Repository interface for managing profile picture data operations in the database. - /// Handles retrieval and updates of user profile picture information. + /// Service interface for managing profile picture operations including file storage, retrieval, and deletion. + /// Coordinates between file system operations and database updates for profile picture management. /// public interface IProfilePictureRepository { /// - /// Retrieves the profile picture URL for the current authenticated user. + /// Saves an uploaded image file to the file system and returns the file name. /// + /// The uploaded image file to save + /// The saved file name with extension + Task SaveFileAsync(IFormFile imageFile); + + /// + /// Retrieves the profile picture URL for a specific user. + /// + /// The unique identifier of the user /// The URL path to the user's profile picture - Task GetProfilePictureUrlAsync(); + Task GetProfilePictureUrlByUserIdAsync(int userId); /// - /// Updates the user's profile picture information in the database. + /// Deletes a profile picture file from the file system. /// - /// Data transfer object containing profile picture details + /// The complete file name including extension to delete /// A task representing the asynchronous operation - Task ChangeProfilePictureAsync(ProfilePictureDTO profilePictureDTO); + Task DeleteProfilePictureAsync(string fileNameWithExtension); } } diff --git a/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureService.cs b/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureService.cs index a40779f..b645afc 100644 --- a/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureService.cs +++ b/Backend/prepAIred.Services/Interfaces/ProfilePicture/IProfilePictureService.cs @@ -1,32 +1,24 @@ -using Microsoft.AspNetCore.Http; +using prepAIred.Data; namespace prepAIred.Services { /// - /// Service interface for managing profile picture operations including file storage, retrieval, and deletion. - /// Coordinates between file system operations and database updates for profile picture management. + /// Repository interface for managing profile picture data operations in the database. + /// Handles retrieval and updates of user profile picture information. /// public interface IProfilePictureService { /// - /// Saves an uploaded image file to the file system and returns the file name. + /// Retrieves the profile picture URL for the current authenticated user. /// - /// The uploaded image file to save - /// The saved file name with extension - Task SaveFileAsync(IFormFile imageFile); - - /// - /// Retrieves the profile picture URL for a specific user. - /// - /// The unique identifier of the user /// The URL path to the user's profile picture - Task GetProfilePictureUrlByUserIdAsync(int userId); + Task GetProfilePictureUrlAsync(); /// - /// Deletes a profile picture file from the file system. + /// Updates the user's profile picture information in the database. /// - /// The complete file name including extension to delete + /// Data transfer object containing profile picture details /// A task representing the asynchronous operation - Task DeleteProfilePictureAsync(string fileNameWithExtension); + Task ChangeProfilePictureAsync(ProfilePictureDTO profilePictureDTO); } } diff --git a/Backend/prepAIred.Services/Interfaces/User/IUserRepository.cs b/Backend/prepAIred.Services/Interfaces/User/IUserRepository.cs index 1df0490..ce6231d 100644 --- a/Backend/prepAIred.Services/Interfaces/User/IUserRepository.cs +++ b/Backend/prepAIred.Services/Interfaces/User/IUserRepository.cs @@ -3,27 +3,127 @@ namespace prepAIred.Services { /// - /// Repository interface for handling user data persistence operations. + /// Service interface for managing user-related business operations. /// public interface IUserRepository { /// - /// Retrieves the currently authenticated user's information. + /// Creates a new user in the system. /// - /// A DTO containing the current user's public information. + /// The user entity to create. + /// The created user entity. + Task CreateUserAsync(User user); + + /// + /// Retrieves a user by their email address. + /// + /// The email address to search for. + /// The matching user entity if found. + Task GetUserByEmailAsync(string email); + + /// + /// Retrieves a user by their username. + /// + /// The username to search for. + /// The matching user entity if found. + Task GetUserByUsernameAsync(string name); + + /// + /// Retrieves a user dto by their ID. + /// + /// The ID of the user to retrieve. + /// The matching user dto if found. + Task GetUserByIdAsync(int userId); + + /// + /// Retrieves a user entity by their ID. + /// + /// The ID of the user to retrieve. + /// The matching user entity if found. + Task GetCurrentUserEntityByIdAsync(int userId); + + /// + /// Retrieves the currently authenticated user. + /// + /// The current user entity. Task GetCurrentUserAsync(); /// - /// Updates the current user's information based on the provided data. + /// Asynchronously retrieves the unique identifier of the currently authenticated user. + /// + /// The task result contains the unique identifier of the current user. + Task GetCurrentUserID(); + + /// + /// Updates the details of an existing user asynchronously. /// - /// An object containing the updated user information. This parameter cannot be null. + /// The object containing the updated user details. The user must already exist in the system. + /// The object containing the updated user information. /// A task that represents the asynchronous operation. - Task UpdateCurrentUserAsync(UserCredentialsDTO userCredentialsDTO); + Task UpdateUserAsync(User user, UserCredentialsDTO? userCredentialsDto); /// - /// Deletes the currently authenticated user from the system. + /// Deletes the user with the specified user ID. /// + /// The unique identifier of the user to delete. Must be a positive integer. /// A task that represents the asynchronous operation. - Task DeleteCurrentUserAsync(); + Task DeleteUserAsync(int userID); + + /// + /// Validates user registration data. + /// + /// The registration data to validate. + /// A task representing the validation operation. + Task ValidateUserAsync(UserCredentialsDTO registerDto); + + /// + /// Validates user update data. + /// + /// The update data to validate. + /// A void representing the validation operation. + Task ValidateUpdateUserDataAsync(UserCredentialsDTO userCredentialsDto); + + /// + /// Checks if a user with the specified email exists. + /// + /// The email address to check. + /// True if the user exists, false otherwise. + Task UserExistsAsync(string email); + + /// + /// Checks if required registration fields are empty. + /// + /// The registration data to check. + /// True if any required fields are empty, false otherwise. + bool AreFieldsEmpty(UserCredentialsDTO userCredentialsDto); + + /// + /// Validates the format of the email. + /// + /// The email address to validate. + /// True if the email is valid, false otherwise. + bool ValidateEmail(string email); + + /// + /// Validates the format of the password. + /// + /// The password to validate. + /// True if the password is valid, false otherwise. + bool ValidatePassword(string password); + + /// + /// Hashes a user's password during registration. + /// + /// The registration data containing the password to hash. + /// A tuple containing the hashed password and salt. + (byte[] hashedPassword, byte[] saltPassword) HashPassword(UserCredentialsDTO userCredentialsDto); + + /// + /// Verifies a user's password during login. + /// + /// The user entity to check against. + /// The login credentials to verify. + /// True if the password is correct, false otherwise. + bool CheckPassword(User currentUser, LoginDTO loginDto); } } diff --git a/Backend/prepAIred.Services/Interfaces/User/IUserService.cs b/Backend/prepAIred.Services/Interfaces/User/IUserService.cs index 2dba6ed..90729a0 100644 --- a/Backend/prepAIred.Services/Interfaces/User/IUserService.cs +++ b/Backend/prepAIred.Services/Interfaces/User/IUserService.cs @@ -3,127 +3,27 @@ namespace prepAIred.Services { /// - /// Service interface for managing user-related business operations. + /// Repository interface for handling user data persistence operations. /// public interface IUserService { /// - /// Creates a new user in the system. + /// Retrieves the currently authenticated user's information. /// - /// The user entity to create. - /// The created user entity. - Task CreateUserAsync(User user); - - /// - /// Retrieves a user by their email address. - /// - /// The email address to search for. - /// The matching user entity if found. - Task GetUserByEmailAsync(string email); - - /// - /// Retrieves a user by their username. - /// - /// The username to search for. - /// The matching user entity if found. - Task GetUserByUsernameAsync(string name); - - /// - /// Retrieves a user dto by their ID. - /// - /// The ID of the user to retrieve. - /// The matching user dto if found. - Task GetUserByIdAsync(int userId); - - /// - /// Retrieves a user entity by their ID. - /// - /// The ID of the user to retrieve. - /// The matching user entity if found. - Task GetCurrentUserEntityByIdAsync(int userId); - - /// - /// Retrieves the currently authenticated user. - /// - /// The current user entity. + /// A DTO containing the current user's public information. Task GetCurrentUserAsync(); /// - /// Asynchronously retrieves the unique identifier of the currently authenticated user. - /// - /// The task result contains the unique identifier of the current user. - Task GetCurrentUserID(); - - /// - /// Updates the details of an existing user asynchronously. + /// Updates the current user's information based on the provided data. /// - /// The object containing the updated user details. The user must already exist in the system. - /// The object containing the updated user information. + /// An object containing the updated user information. This parameter cannot be null. /// A task that represents the asynchronous operation. - Task UpdateUserAsync(User user, UserCredentialsDTO? userCredentialsDto); + Task UpdateCurrentUserAsync(UserCredentialsDTO userCredentialsDTO); /// - /// Deletes the user with the specified user ID. + /// Deletes the currently authenticated user from the system. /// - /// The unique identifier of the user to delete. Must be a positive integer. /// A task that represents the asynchronous operation. - Task DeleteUserAsync(int userID); - - /// - /// Validates user registration data. - /// - /// The registration data to validate. - /// A task representing the validation operation. - Task ValidateUserAsync(UserCredentialsDTO registerDto); - - /// - /// Validates user update data. - /// - /// The update data to validate. - /// A void representing the validation operation. - Task ValidateUpdateUserDataAsync(UserCredentialsDTO userCredentialsDto); - - /// - /// Checks if a user with the specified email exists. - /// - /// The email address to check. - /// True if the user exists, false otherwise. - Task UserExistsAsync(string email); - - /// - /// Checks if required registration fields are empty. - /// - /// The registration data to check. - /// True if any required fields are empty, false otherwise. - bool AreFieldsEmpty(UserCredentialsDTO userCredentialsDto); - - /// - /// Validates the format of the email. - /// - /// The email address to validate. - /// True if the email is valid, false otherwise. - bool ValidateEmail(string email); - - /// - /// Validates the format of the password. - /// - /// The password to validate. - /// True if the password is valid, false otherwise. - bool ValidatePassword(string password); - - /// - /// Hashes a user's password during registration. - /// - /// The registration data containing the password to hash. - /// A tuple containing the hashed password and salt. - (byte[] hashedPassword, byte[] saltPassword) HashPassword(UserCredentialsDTO userCredentialsDto); - - /// - /// Verifies a user's password during login. - /// - /// The user entity to check against. - /// The login credentials to verify. - /// True if the password is correct, false otherwise. - bool CheckPassword(User currentUser, LoginDTO loginDto); + Task DeleteCurrentUserAsync(); } } diff --git a/Backend/prepAIred.Services/Services/Authentication/Auth/AuthRepository.cs b/Backend/prepAIred.Services/Services/Authentication/Auth/AuthRepository.cs index b8c0f79..21cb1fb 100644 --- a/Backend/prepAIred.Services/Services/Authentication/Auth/AuthRepository.cs +++ b/Backend/prepAIred.Services/Services/Authentication/Auth/AuthRepository.cs @@ -1,33 +1,61 @@ using prepAIred.Data; +using prepAIred.Exceptions; namespace prepAIred.Services { - public class AuthRepository(IAuthService authService, IUserService userService) : IAuthRepository + public class AuthRepository(IJwtService jwtService, IRefreshTokenRepository refreshTokenRepository, ICookieService cookieService, IUserRepository userRepository) : IAuthRepository { - private readonly IAuthService _authService = authService; - private readonly IUserService _userService = userService; + private readonly IJwtService _jwtService = jwtService; + private readonly IRefreshTokenRepository _refreshTokenRepository = refreshTokenRepository; + private readonly ICookieService _cookieService = cookieService; + private readonly IUserRepository _userRepository = userRepository; - public async Task RegisterAsync(UserCredentialsDTO userCredentialsDto) + public async Task RegisterAsync(UserCredentialsDTO userCredentialsDto, byte[] hashedPassword, byte[] saltPassword) { - await _userService.ValidateUserAsync(userCredentialsDto); + User newUser = new User() + { + Email = userCredentialsDto.Email, + Username = userCredentialsDto.Username, + PasswordHash = hashedPassword, + PasswordSalt = saltPassword + }; - (byte[] hashedPassword, byte[] saltPassword) = _userService.HashPassword(userCredentialsDto); + await _userRepository.CreateUserAsync(newUser); - CurrentUserDTO currentUser = await _authService.RegisterAsync(userCredentialsDto, hashedPassword, saltPassword); - - await _authService.GenerateAuthResponseAsync(currentUser); + return newUser.ToDto(); } - public async Task LoginAsync(LoginDTO loginDto) + public async Task LoginAsync(LoginDTO loginDto) { - CurrentUserDTO currentUser = await _authService.LoginAsync(loginDto); + if (!await _userRepository.UserExistsAsync(loginDto.Email)) throw new ResourceNotFoundException("Invalid Username or Password"); + + User currentUser = await _userRepository.GetUserByEmailAsync(loginDto.Email); + + if (!_userRepository.CheckPassword(currentUser, loginDto)) throw new InvalidCredentialsException("Invalid Username or Password"); - await _authService.GenerateAuthResponseAsync(currentUser); + return currentUser.ToDto(); + } + + public async Task GenerateAuthResponseAsync(CurrentUserDTO currentUser) + { + string accessToken = _jwtService.GenerateAcessToken(currentUser.ID); + string refreshToken = _jwtService.GenerateRefreshToken(currentUser.ID); + + await _refreshTokenRepository.AddRefreshTokenAsync(new RefreshToken() + { + Token = refreshToken, + ExpiryDate = DateTime.Now.AddDays(1), + UserID = currentUser.ID + }); + + _cookieService.CreateCookie("AccessToken", accessToken); + _cookieService.CreateCookie("RefreshToken", refreshToken); } public async Task LogoutAsync() { - await _authService.LogoutAsync(); + _cookieService.DeleteCookie("AccessToken"); + _cookieService.DeleteCookie("RefreshToken"); await Task.CompletedTask; } diff --git a/Backend/prepAIred.Services/Services/Authentication/Auth/AuthService.cs b/Backend/prepAIred.Services/Services/Authentication/Auth/AuthService.cs index 96cfb04..7ed73c2 100644 --- a/Backend/prepAIred.Services/Services/Authentication/Auth/AuthService.cs +++ b/Backend/prepAIred.Services/Services/Authentication/Auth/AuthService.cs @@ -1,61 +1,33 @@ using prepAIred.Data; -using prepAIred.Exceptions; namespace prepAIred.Services { - public class AuthService(IJwtService jwtService, IRefreshTokenService refreshTokenService, ICookieService cookieService, IUserService userService) : IAuthService + public class AuthService(IAuthRepository authRepository, IUserRepository userRepository) : IAuthService { - private readonly IJwtService _jwtService = jwtService; - private readonly IRefreshTokenService _refreshTokenService = refreshTokenService; - private readonly ICookieService _cookieService = cookieService; - private readonly IUserService _userService = userService; + private readonly IAuthRepository _authRepository = authRepository; + private readonly IUserRepository _userRepository = userRepository; - public async Task RegisterAsync(UserCredentialsDTO userCredentialsDto, byte[] hashedPassword, byte[] saltPassword) + public async Task RegisterAsync(UserCredentialsDTO userCredentialsDto) { - User newUser = new User() - { - Email = userCredentialsDto.Email, - Username = userCredentialsDto.Username, - PasswordHash = hashedPassword, - PasswordSalt = saltPassword - }; + await _userRepository.ValidateUserAsync(userCredentialsDto); - await _userService.CreateUserAsync(newUser); + (byte[] hashedPassword, byte[] saltPassword) = _userRepository.HashPassword(userCredentialsDto); - return newUser.ToDto(); - } - - public async Task LoginAsync(LoginDTO loginDto) - { - if (!await _userService.UserExistsAsync(loginDto.Email)) throw new ResourceNotFoundException("Invalid Username or Password"); + CurrentUserDTO currentUser = await _authRepository.RegisterAsync(userCredentialsDto, hashedPassword, saltPassword); - User currentUser = await _userService.GetUserByEmailAsync(loginDto.Email); - - if (!_userService.CheckPassword(currentUser, loginDto)) throw new InvalidCredentialsException("Invalid Username or Password"); - - return currentUser.ToDto(); + await _authRepository.GenerateAuthResponseAsync(currentUser); } - public async Task GenerateAuthResponseAsync(CurrentUserDTO currentUser) + public async Task LoginAsync(LoginDTO loginDto) { - string accessToken = _jwtService.GenerateAcessToken(currentUser.ID); - string refreshToken = _jwtService.GenerateRefreshToken(currentUser.ID); - - await _refreshTokenService.AddRefreshTokenAsync(new RefreshToken() - { - Token = refreshToken, - ExpiryDate = DateTime.Now.AddDays(1), - UserID = currentUser.ID - }); - - _cookieService.CreateCookie("AccessToken", accessToken); - _cookieService.CreateCookie("RefreshToken", refreshToken); + CurrentUserDTO currentUser = await _authRepository.LoginAsync(loginDto); + + await _authRepository.GenerateAuthResponseAsync(currentUser); } public async Task LogoutAsync() { - _cookieService.DeleteCookie("AccessToken"); - _cookieService.DeleteCookie("RefreshToken"); + await _authRepository.LogoutAsync(); await Task.CompletedTask; } diff --git a/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenRepository.cs b/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenRepository.cs index fe820f3..c529902 100644 --- a/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenRepository.cs +++ b/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenRepository.cs @@ -1,53 +1,27 @@ using prepAIred.Data; -using prepAIred.Exceptions; -using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; namespace prepAIred.Services { - public class RefreshTokenRepository(IHttpContextAccessor httpContextAccessor, IRefreshTokenService refreshTokenService, - IJwtService jwtService, ICookieService cookieService, IUserService userService) : IRefreshTokenRepository + public class RefreshTokenRepository(DataContext dataContext) : IRefreshTokenRepository { - private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; - private readonly IRefreshTokenService _refreshTokenService = refreshTokenService; - private readonly IJwtService _jwtService = jwtService; - private readonly ICookieService _cookieService = cookieService; - private readonly IUserService _userService = userService; + private readonly DataContext _dataContext = dataContext; - public async Task GenerateNewRefreshTokenAsync() + public async Task AddRefreshTokenAsync(RefreshToken refreshToken) { - string refreshToken = _httpContextAccessor.HttpContext!.Request.Cookies["RefreshToken"]!; - RefreshToken storedToken = await _refreshTokenService.GetRefreshTokenAsync(refreshToken); - - if (storedToken is null || storedToken.ExpiryDate < DateTime.Now || storedToken.IsRevoked) - { - throw new InvalidRefreshTokenException("Invalid or expired refresh token."); - } - - storedToken.IsRevoked = true; - - string newRefreshToken = _jwtService.GenerateRefreshToken(storedToken.UserID); - string newAccessToken = _jwtService.GenerateAcessToken(storedToken.UserID); - - RefreshToken newRefreshTokenEntity = await _refreshTokenService.AddRefreshTokenAsync(new RefreshToken() - { - Token = newRefreshToken, - ExpiryDate = DateTime.Now.AddDays(7), - UserID = storedToken.UserID, - }); - - CurrentUserDTO currentUser = await _userService.GetUserByIdAsync(storedToken.UserID); + _dataContext.RefreshTokens.Add(refreshToken); + await _dataContext.SaveChangesAsync(); + return refreshToken; + } - _cookieService.DeleteCookie("RefreshToken"); - _cookieService.CreateCookie("AccessToken", newAccessToken); - _cookieService.CreateCookie("RefreshToken", newRefreshToken); + public async Task GetRefreshTokenAsync(string refreshToken) + { + return await _dataContext.RefreshTokens.FirstOrDefaultAsync(t => t.Token == refreshToken) ?? new RefreshToken(); + } - return new RefreshTokenResponseDTO() - { - NewAccessToken = newAccessToken, - NewRefreshToken = newRefreshToken, - ExpiresIn = 600, - Username = currentUser.Username - }; + public async Task GetRefreshTokenByUserIdAsync(int userID) + { + return await _dataContext.RefreshTokens.FirstOrDefaultAsync(t => t.UserID == userID && !t.IsRevoked) ?? new RefreshToken(); } } } diff --git a/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenService.cs b/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenService.cs index b91f7d0..2ec97d4 100644 --- a/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenService.cs +++ b/Backend/prepAIred.Services/Services/Authentication/RefreshToken/RefreshTokenService.cs @@ -1,27 +1,53 @@ using prepAIred.Data; -using Microsoft.EntityFrameworkCore; +using prepAIred.Exceptions; +using Microsoft.AspNetCore.Http; namespace prepAIred.Services { - public class RefreshTokenService(DataContext dataContext) : IRefreshTokenService + public class RefreshTokenService(IHttpContextAccessor httpContextAccessor, IRefreshTokenRepository refreshTokenRepository, + IJwtService jwtService, ICookieService cookieService, IUserRepository userRepository) : IRefreshTokenService { - private readonly DataContext _dataContext = dataContext; + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + private readonly IRefreshTokenRepository _refreshTokenRepository = refreshTokenRepository; + private readonly IJwtService _jwtService = jwtService; + private readonly ICookieService _cookieService = cookieService; + private readonly IUserRepository _userRepository = userRepository; - public async Task AddRefreshTokenAsync(RefreshToken refreshToken) + public async Task GenerateNewRefreshTokenAsync() { - _dataContext.RefreshTokens.Add(refreshToken); - await _dataContext.SaveChangesAsync(); - return refreshToken; - } + string refreshToken = _httpContextAccessor.HttpContext!.Request.Cookies["RefreshToken"]!; + RefreshToken storedToken = await _refreshTokenRepository.GetRefreshTokenAsync(refreshToken); - public async Task GetRefreshTokenAsync(string refreshToken) - { - return await _dataContext.RefreshTokens.FirstOrDefaultAsync(t => t.Token == refreshToken) ?? new RefreshToken(); - } + if (storedToken is null || storedToken.ExpiryDate < DateTime.Now || storedToken.IsRevoked) + { + throw new InvalidRefreshTokenException("Invalid or expired refresh token."); + } - public async Task GetRefreshTokenByUserIdAsync(int userID) - { - return await _dataContext.RefreshTokens.FirstOrDefaultAsync(t => t.UserID == userID && !t.IsRevoked) ?? new RefreshToken(); + storedToken.IsRevoked = true; + + string newRefreshToken = _jwtService.GenerateRefreshToken(storedToken.UserID); + string newAccessToken = _jwtService.GenerateAcessToken(storedToken.UserID); + + RefreshToken newRefreshTokenEntity = await _refreshTokenRepository.AddRefreshTokenAsync(new RefreshToken() + { + Token = newRefreshToken, + ExpiryDate = DateTime.Now.AddDays(7), + UserID = storedToken.UserID, + }); + + CurrentUserDTO currentUser = await _userRepository.GetUserByIdAsync(storedToken.UserID); + + _cookieService.DeleteCookie("RefreshToken"); + _cookieService.CreateCookie("AccessToken", newAccessToken); + _cookieService.CreateCookie("RefreshToken", newRefreshToken); + + return new RefreshTokenResponseDTO() + { + NewAccessToken = newAccessToken, + NewRefreshToken = newRefreshToken, + ExpiresIn = 600, + Username = currentUser.Username + }; } } } diff --git a/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionRepository.cs b/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionRepository.cs index 315e703..ff63b9e 100644 --- a/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionRepository.cs +++ b/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionRepository.cs @@ -1,109 +1,179 @@ using prepAIred.Data; +using prepAIred.Exceptions; +using Microsoft.EntityFrameworkCore; namespace prepAIred.Services { - public class InterviewSessionRepository(IInterviewSessionService interviewSessionService, IUserService userService) : IInterviewSessionRepository + public class InterviewSessionRepository(DataContext dataContext) : IInterviewSessionRepository { - private readonly IInterviewSessionService _interviewSessionService = interviewSessionService; - private readonly IUserService _userService = userService; + private readonly DataContext _dataContext = dataContext; - public async Task> GetInterviewSessionDTOsAsync() + public async Task CreateInterviewSessionAsync(InterviewSession interviewSession) { - int currentUserID = await _userService.GetCurrentUserID(); - List interviewSessions = await _interviewSessionService.GetInterviewSessionsByUserIdAsync(currentUserID); - List interviewSessionsDTOs = interviewSessions.ConvertAll(session => session.ToDto()); + await _dataContext.InterviewSessions.AddAsync(interviewSession); + } - return interviewSessionsDTOs; + public async Task UpdateInterviewSessionAsync(InterviewSession interviewSession) + { + _dataContext.InterviewSessions.Update(interviewSession); + await _dataContext.SaveChangesAsync(); } - public async Task> GetInterviewSessionActivitiesAsync() + public async Task GetAdjacentInterviewSessionAsync(int currentUserID) { - int currentUserID = await _userService.GetCurrentUserID(); - List activities = await _interviewSessionService.GetInterviewSessionActivitiesAsync(currentUserID); + return await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == currentUserID) + .OrderByDescending(s => s.DateCreated) + .FirstOrDefaultAsync() ?? throw new InterviewSessionNotFoundException("No interview session found for the current user."); + } - return activities; + public async Task> GetInterviewSessionsByUserIdAsync(int userID) + { + return await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == userID) + .Include(s => s.Interviews) + .ToListAsync(); } - public async Task GetInterviewSessionStatisticsAsync() + public async Task GetInterviewSessionByIdAsync(int sessionID) { - int currentUserID = await _userService.GetCurrentUserID(); + return await _dataContext.InterviewSessions + .Where(s => s.ID == sessionID) + .FirstOrDefaultAsync(); + } - int totalInterviewSessions = await _interviewSessionService.GetTotalInterviewSessionsAsync(currentUserID); - int passedInterviewSessions = await _interviewSessionService.GetPassedInterviewSessionsAsync(currentUserID); - int ongoingInterviewSessions = await _interviewSessionService.GetOngoingInterviewSessionsAsync(currentUserID); - decimal averageScore = await _interviewSessionService.GetAverageScoreAsync(currentUserID); - decimal completionRate = await _interviewSessionService.GetCompletionRateAsync(currentUserID); + public async Task GetInterviewSessionFromQuestionsAsync(List evaluateRequests) + { + EvaluateRequestDTO firstRequest = evaluateRequests.FirstOrDefault()!; + string firstQuestion = firstRequest?.Question!; + + return await _dataContext.InterviewSessions + .Include(s => s.Interviews) + .Where(s => s.Interviews.Any(i => i.Question == firstQuestion && !i.IsAnswered)) + .FirstOrDefaultAsync() ?? throw new InterviewSessionNotFoundException("No interview session found matching the provided questions."); + } - ProfileStatisticsDTO profileStatistics = new ProfileStatisticsDTO() + public async Task DeleteInterviewSessionsAsync(List interviewSessions) + { + _dataContext.InterviewSessions.RemoveRange(interviewSessions); + await _dataContext.SaveChangesAsync(); + } + + public async Task GetLatestInterviewSessionIDAsync(int userID) + { + return await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == userID) + .OrderByDescending(s => s.DateCreated) + .Select(s => s.ID) + .FirstOrDefaultAsync(); + } + + public async Task FinishInterviewSessionAsync(InterviewSession interviewSession) + { + if (interviewSession.AverageScore > 5) { - TotalInterviewSessions = totalInterviewSessions, - PassedInterviewSessions = passedInterviewSessions, - OngoingInterviewSessions = ongoingInterviewSessions, - AverageScore = averageScore, - CompletionRate = completionRate - }; + interviewSession.Status = InterviewSessionStatus.Passed; + } + else + { + interviewSession.Status = InterviewSessionStatus.Failed; + } - return profileStatistics; + _dataContext.InterviewSessions.Update(interviewSession); + await _dataContext.SaveChangesAsync(); } - public async Task> GetInterviewSessionPerformanceAsync() + public async Task GetTotalInterviewSessionsAsync(int userID) { - List activities = await GetInterviewSessionActivitiesAsync(); - List performanceData = activities - .OrderBy(activity => activity.DateCreated) - .Select(activity => new InterviewSessionPerformanceDTO() - { - ID = activity.ID, - DateCreated = activity.DateCreated, - Score = activity.AverageScore - }).ToList(); + return await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == userID && intSession.Status != InterviewSessionStatus.Ongoing) + .CountAsync(); + } - return performanceData; + public async Task GetPassedInterviewSessionsAsync(int userID) + { + return await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == userID && intSession.Status == InterviewSessionStatus.Passed) + .CountAsync(); } - public async Task> GetInterviewSessionProgrammingLanguageDataAsync() + public async Task GetOngoingInterviewSessionsAsync(int userID) { - List activities = await GetInterviewSessionActivitiesAsync(); - List programmingLanguageData = activities - .GroupBy(activity => activity.ProgrammingLanguage) - .Select(activity => new ProgrammingLanguageDataDTO() - { - Language = activity.Key, - Sessions = activity.Count() - }).ToList(); + return await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == userID && intSession.Status == InterviewSessionStatus.Ongoing) + .CountAsync(); + } - return programmingLanguageData; + public async Task GetAverageScoreAsync(int userID) + { + List sessions = await _dataContext.InterviewSessions + .Where(intSession => intSession.UserID == userID && intSession.Status != InterviewSessionStatus.Ongoing) + .ToListAsync(); + + decimal averageScore = (decimal)(sessions.Count != 0 ? sessions.Average(s => s.AverageScore) : 0); + + return averageScore; } - public async Task> GetInterviewSessionPositionDataAsync() + public async Task GetCompletionRateAsync(int userID) { - List activities = await GetInterviewSessionActivitiesAsync(); - List positionData = activities - .GroupBy(activity => activity.Position) - .Select(activity => new PositionDataDTO() - { - Position = activity.Key, - Sessions = activity.Count() - }).ToList(); + int totalSessions = await GetTotalInterviewSessionsAsync(userID); + int passedSessions = await GetPassedInterviewSessionsAsync(userID); + + if (totalSessions == 0) return 0; - return positionData; + decimal rate = (passedSessions / (decimal)totalSessions) * 100; + + return Math.Round(rate, 2); } - public async Task FinishInterviewSessionAsync() + public void FinalizeInterviewSession(InterviewSession interviewSession, List evaluatedTInterviews) where TInterview : Interview { - int currentUserID = await _userService.GetCurrentUserID(); - int interviewSessionID = await _interviewSessionService.GetLatestInterviewSessionIDAsync(currentUserID); - InterviewSession latestSession = await _interviewSessionService.GetInterviewSessionByIdAsync(interviewSessionID); + if (interviewSession.HrScore == 0) + { + float hrScore = evaluatedTInterviews.OfType().Any() + ? evaluatedTInterviews.OfType().Average(i => i.Score) + : 0; + + interviewSession.HrScore = hrScore; + } + + if (interviewSession.TechnicalScore == 0) + { + float technicalScore = evaluatedTInterviews.OfType().Any() + ? evaluatedTInterviews.OfType().Average(i => i.Score) + : 0; - await _interviewSessionService.FinishInterviewSessionAsync(latestSession); + interviewSession.TechnicalScore = technicalScore; + } } - public async Task DeleteInterviewSessionsAsync() + public async Task> GetInterviewSessionActivitiesAsync(int userID) { - int currentUserID = await _userService.GetCurrentUserID(); - List interviewSessions = await _interviewSessionService.GetInterviewSessionsByUserIdAsync(currentUserID); + List interviewSessions = await _dataContext.InterviewSessions + .Where(session => session.UserID == userID && session.Status != InterviewSessionStatus.Ongoing) + .Include(session => session.Interviews) + .OrderByDescending(session => session.DateCreated) + .ToListAsync(); + + List activityDTOs = interviewSessions.ConvertAll(session => + { + TechnicalInterview? technicalInterview = session.Interviews.OfType().FirstOrDefault(); - await _interviewSessionService.DeleteInterviewSessionsAsync(interviewSessions); + return new InterviewSessionActivityDTO() + { + ID = session.ID, + DateCreated = session.DateCreated, + Subject = session.Subject, + AverageScore = session.AverageScore, + AiAgent = session.AIAgent.ToString(), + Position = technicalInterview?.Position ?? "Junior Developer", + ProgrammingLanguage = technicalInterview?.ProgrammingLanguage ?? "C#", + Status = session.Status.ToString() + }; + }); + + return activityDTOs; } } } diff --git a/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionService.cs b/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionService.cs index fa97c75..2862ed7 100644 --- a/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionService.cs +++ b/Backend/prepAIred.Services/Services/Interview/InterviewSessions/InterviewSessionService.cs @@ -1,178 +1,109 @@ using prepAIred.Data; -using Microsoft.EntityFrameworkCore; namespace prepAIred.Services { - public class InterviewSessionService(DataContext dataContext) : IInterviewSessionService + public class InterviewSessionService(IInterviewSessionRepository interviewSessionRepository, IUserRepository userRepository) : IInterviewSessionService { - private readonly DataContext _dataContext = dataContext; + private readonly IInterviewSessionRepository _interviewSessionRepository = interviewSessionRepository; + private readonly IUserRepository _userRepository = userRepository; - public async Task CreateInterviewSessionAsync(InterviewSession interviewSession) + public async Task> GetInterviewSessionDTOsAsync() { - await _dataContext.InterviewSessions.AddAsync(interviewSession); - } + int currentUserID = await _userRepository.GetCurrentUserID(); + List interviewSessions = await _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(currentUserID); + List interviewSessionsDTOs = interviewSessions.ConvertAll(session => session.ToDto()); - public async Task UpdateInterviewSessionAsync(InterviewSession interviewSession) - { - _dataContext.InterviewSessions.Update(interviewSession); - await _dataContext.SaveChangesAsync(); + return interviewSessionsDTOs; } - public async Task GetAdjacentInterviewSessionAsync(int currentUserID) + public async Task> GetInterviewSessionActivitiesAsync() { - return await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == currentUserID) - .OrderByDescending(s => s.DateCreated) - .FirstOrDefaultAsync(); - } + int currentUserID = await _userRepository.GetCurrentUserID(); + List activities = await _interviewSessionRepository.GetInterviewSessionActivitiesAsync(currentUserID); - public async Task> GetInterviewSessionsByUserIdAsync(int userID) - { - return await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == userID) - .Include(s => s.Interviews) - .ToListAsync(); + return activities; } - public async Task GetInterviewSessionByIdAsync(int sessionID) + public async Task GetInterviewSessionStatisticsAsync() { - return await _dataContext.InterviewSessions - .Where(s => s.ID == sessionID) - .FirstOrDefaultAsync(); - } + int currentUserID = await _userRepository.GetCurrentUserID(); - public async Task GetInterviewSessionFromQuestionsAsync(List evaluateRequests) - { - EvaluateRequestDTO firstRequest = evaluateRequests.FirstOrDefault()!; - string firstQuestion = firstRequest?.Question!; - - return await _dataContext.InterviewSessions - .Include(s => s.Interviews) - .Where(s => s.Interviews.Any(i => i.Question == firstQuestion && !i.IsAnswered)) - .FirstOrDefaultAsync(); - } + int totalInterviewSessions = await _interviewSessionRepository.GetTotalInterviewSessionsAsync(currentUserID); + int passedInterviewSessions = await _interviewSessionRepository.GetPassedInterviewSessionsAsync(currentUserID); + int ongoingInterviewSessions = await _interviewSessionRepository.GetOngoingInterviewSessionsAsync(currentUserID); + decimal averageScore = await _interviewSessionRepository.GetAverageScoreAsync(currentUserID); + decimal completionRate = await _interviewSessionRepository.GetCompletionRateAsync(currentUserID); - public async Task DeleteInterviewSessionsAsync(List interviewSessions) - { - _dataContext.InterviewSessions.RemoveRange(interviewSessions); - await _dataContext.SaveChangesAsync(); - } - - public async Task GetLatestInterviewSessionIDAsync(int userID) - { - return await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == userID) - .OrderByDescending(s => s.DateCreated) - .Select(s => s.ID) - .FirstOrDefaultAsync(); - } - - public async Task FinishInterviewSessionAsync(InterviewSession interviewSession) - { - if (interviewSession.AverageScore > 5) + ProfileStatisticsDTO profileStatistics = new ProfileStatisticsDTO() { - interviewSession.Status = InterviewSessionStatus.Passed; - } - else - { - interviewSession.Status = InterviewSessionStatus.Failed; - } - - _dataContext.InterviewSessions.Update(interviewSession); - await _dataContext.SaveChangesAsync(); - } + TotalInterviewSessions = totalInterviewSessions, + PassedInterviewSessions = passedInterviewSessions, + OngoingInterviewSessions = ongoingInterviewSessions, + AverageScore = averageScore, + CompletionRate = completionRate + }; - public async Task GetTotalInterviewSessionsAsync(int userID) - { - return await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == userID && intSession.Status != InterviewSessionStatus.Ongoing) - .CountAsync(); + return profileStatistics; } - public async Task GetPassedInterviewSessionsAsync(int userID) + public async Task> GetInterviewSessionPerformanceAsync() { - return await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == userID && intSession.Status == InterviewSessionStatus.Passed) - .CountAsync(); - } + List activities = await GetInterviewSessionActivitiesAsync(); + List performanceData = activities + .OrderBy(activity => activity.DateCreated) + .Select(activity => new InterviewSessionPerformanceDTO() + { + ID = activity.ID, + DateCreated = activity.DateCreated, + Score = activity.AverageScore + }).ToList(); - public async Task GetOngoingInterviewSessionsAsync(int userID) - { - return await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == userID && intSession.Status == InterviewSessionStatus.Ongoing) - .CountAsync(); + return performanceData; } - public async Task GetAverageScoreAsync(int userID) + public async Task> GetInterviewSessionProgrammingLanguageDataAsync() { - List sessions = await _dataContext.InterviewSessions - .Where(intSession => intSession.UserID == userID && intSession.Status != InterviewSessionStatus.Ongoing) - .ToListAsync(); - - decimal averageScore = (decimal)(sessions.Count != 0 ? sessions.Average(s => s.AverageScore) : 0); + List activities = await GetInterviewSessionActivitiesAsync(); + List programmingLanguageData = activities + .GroupBy(activity => activity.ProgrammingLanguage) + .Select(activity => new ProgrammingLanguageDataDTO() + { + Language = activity.Key, + Sessions = activity.Count() + }).ToList(); - return averageScore; + return programmingLanguageData; } - public async Task GetCompletionRateAsync(int userID) + public async Task> GetInterviewSessionPositionDataAsync() { - int totalSessions = await GetTotalInterviewSessionsAsync(userID); - int passedSessions = await GetPassedInterviewSessionsAsync(userID); - - if (totalSessions == 0) return 0; - - decimal rate = (passedSessions / (decimal)totalSessions) * 100; + List activities = await GetInterviewSessionActivitiesAsync(); + List positionData = activities + .GroupBy(activity => activity.Position) + .Select(activity => new PositionDataDTO() + { + Position = activity.Key, + Sessions = activity.Count() + }).ToList(); - return Math.Round(rate, 2); + return positionData; } - public void FinalizeInterviewSession(InterviewSession interviewSession, List evaluatedTInterviews) where TInterview : Interview + public async Task FinishInterviewSessionAsync() { - if (interviewSession.HrScore == 0) - { - float hrScore = evaluatedTInterviews.OfType().Any() - ? evaluatedTInterviews.OfType().Average(i => i.Score) - : 0; - - interviewSession.HrScore = hrScore; - } - - if (interviewSession.TechnicalScore == 0) - { - float technicalScore = evaluatedTInterviews.OfType().Any() - ? evaluatedTInterviews.OfType().Average(i => i.Score) - : 0; + int currentUserID = await _userRepository.GetCurrentUserID(); + int interviewSessionID = await _interviewSessionRepository.GetLatestInterviewSessionIDAsync(currentUserID); + InterviewSession latestSession = await _interviewSessionRepository.GetInterviewSessionByIdAsync(interviewSessionID); - interviewSession.TechnicalScore = technicalScore; - } + await _interviewSessionRepository.FinishInterviewSessionAsync(latestSession); } - public async Task> GetInterviewSessionActivitiesAsync(int userID) + public async Task DeleteInterviewSessionsAsync() { - List interviewSessions = await _dataContext.InterviewSessions - .Where(session => session.UserID == userID && session.Status != InterviewSessionStatus.Ongoing) - .Include(session => session.Interviews) - .OrderByDescending(session => session.DateCreated) - .ToListAsync(); - - List activityDTOs = interviewSessions.ConvertAll(session => - { - TechnicalInterview? technicalInterview = session.Interviews.OfType().FirstOrDefault(); + int currentUserID = await _userRepository.GetCurrentUserID(); + List interviewSessions = await _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(currentUserID); - return new InterviewSessionActivityDTO() - { - ID = session.ID, - DateCreated = session.DateCreated, - Subject = session.Subject, - AverageScore = session.AverageScore, - AiAgent = session.AIAgent.ToString(), - Position = technicalInterview?.Position ?? "Junior Developer", - ProgrammingLanguage = technicalInterview?.ProgrammingLanguage ?? "C#", - Status = session.Status.ToString() - }; - }); - - return activityDTOs; + await _interviewSessionRepository.DeleteInterviewSessionsAsync(interviewSessions); } } } diff --git a/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewRepository.cs b/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewRepository.cs index eec0600..e86fb2c 100644 --- a/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewRepository.cs +++ b/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewRepository.cs @@ -1,114 +1,61 @@ using prepAIred.Data; +using Microsoft.EntityFrameworkCore; namespace prepAIred.Services { - /// - /// Provides operations for generating and retrieving AI-generated interviews, including session management and user context. - /// Coordinates between AI services, user services, and interview/session services to create and fetch interview data. - /// - /// The AI service used to generate interview questions and answers. - /// The user service for retrieving user information and context. - /// The interview service for managing interview entities. - /// The interview session service for managing interview session entities. - /// The prompt service for generating prompts for the AI agent. - public class InterviewRepository(IAIService aIService, IUserService userService, IInterviewService interviewService, - IInterviewSessionService interviewSessionService, IPromptService promptService, ISerializationService serializationService) : IInterviewRepository + public class InterviewRepository(DataContext dataContext) : IInterviewRepository { - private readonly IAIService _aIService = aIService; - private readonly IUserService _userService = userService; - private readonly IInterviewService _interviewService = interviewService; - private readonly IInterviewSessionService _interviewSessionService = interviewSessionService; - private readonly IPromptService _promptService = promptService; - private readonly ISerializationService _serializationService = serializationService; + private readonly DataContext _dataContext = dataContext; - public async Task GenerateInterviewsAsync(BaseRequestDTO request) where TInterview : Interview + public async Task CreateInterviewsAsync(List interviews, User currentUser, InterviewSession interviewSession) { - int currentUserID = await _userService.GetCurrentUserID(); - User currentUser = await _userService.GetCurrentUserEntityByIdAsync(currentUserID); - AIAgent aiAgent = Enum.Parse(request.AIAgent); - - await (typeof(TInterview).Name switch + foreach (Interview interview in interviews) { - nameof(HRInterview) when request is HrRequestDTO hrRequest => CreateHrInterviewAsync(hrRequest, currentUser, aiAgent), - nameof(TechnicalInterview) when request is TechnicalRequestDTO techRequest => CreateTechnicalInterviewAsync(techRequest, currentUser, aiAgent), - _ => Task.CompletedTask - }); + interview.User = currentUser; + interview.InterviewSession = interviewSession; + interview.InterviewSessionID = interviewSession.ID; + } + + await _dataContext.Interviews.AddRangeAsync(interviews); + await _dataContext.SaveChangesAsync(); } - private async Task CreateHrInterviewAsync(HrRequestDTO hrRequest, User currentUser, AIAgent aiAgent) + public async Task> GetInterviewsBySessionIdAsync(int sessionID) where TInterview : Interview { - string prompt = _promptService.CreateHrPrompt(hrRequest, currentUser.ID); - List interviews = await _aIService.AskAiAgentAsync(aiAgent, prompt); - - InterviewSession interviewSession = new InterviewSession() - { - UserID = currentUser.ID, - User = currentUser, - Interviews = interviews, - AIAgent = aiAgent, - Status = InterviewSessionStatus.Ongoing - }; - - await _interviewSessionService.CreateInterviewSessionAsync(interviewSession); - await _interviewService.CreateInterviewsAsync(interviews, currentUser, interviewSession); + return await _dataContext.Interviews + .Where(i => i.InterviewSessionID == sessionID && i.GetType() == typeof(TInterview)) + .Cast() + .ToListAsync(); } - private async Task CreateTechnicalInterviewAsync(TechnicalRequestDTO techRequest, User currentUser, AIAgent aiAgent) + public async Task UpdateInterviewAsync(List interviews) where TInterview : Interview { - string prompt = _promptService.CreateTechnicalPrompt(techRequest, currentUser.ID); - List interviews = await _aIService.AskAiAgentAsync(aiAgent, prompt); - InterviewSession interviewSession = await _interviewSessionService.GetAdjacentInterviewSessionAsync(currentUser.ID); - - interviewSession.Subject = string.Join(", ", techRequest.Subject); - - await _interviewSessionService.UpdateInterviewSessionAsync(interviewSession); - await _interviewService.CreateInterviewsAsync(interviews, currentUser, interviewSession); + _dataContext.Interviews.UpdateRange(interviews); + await _dataContext.SaveChangesAsync(); } - public async Task> GetLatestInterviewsAsync() + public List GetLatestInterviews(List interviews) where TInterview : Interview where TInterviewDTO : InterviewDTO { - int currentUserID = await _userService.GetCurrentUserID(); - int latestSessionID = await _interviewSessionService.GetLatestInterviewSessionIDAsync(currentUserID); - InterviewSession interviewSession = await _interviewSessionService.GetInterviewSessionByIdAsync(latestSessionID); - - if (interviewSession is null || interviewSession.Status != InterviewSessionStatus.Ongoing) - { - // After the Technical Interviews are evaluated, the status of the session is set to Passed/Failed, - // meaning the interviews will automatically disappear after evaluation. This check prevents this behavior - // by returning an empty list if the user hasn't clicked the "Finish Interview Session" button. - return []; - } - - List interviews = await _interviewService.GetInterviewsBySessionIdAsync(latestSessionID); - List interviewDTOs = _interviewService.GetLatestInterviews(interviews); - - return interviewDTOs; + return interviews + .Select(i => i.ToDto()) + .ToList(); } - public async Task EvaluateInterviewsAsync(List evaluateRequest) where TInterview : Interview + public void UpdateExistingInterviewWithEvaluation(List evaluatedInterviews, List existingInterviews) where TInterview : Interview { - InterviewSession interviewSession = await _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequest); - - string basePrompt = typeof(TInterview).Name switch + foreach (TInterview evaluatedInterview in evaluatedInterviews) { - nameof(HRInterview) => _promptService.CreateHrEvaluationPrompt(evaluateRequest), - nameof(TechnicalInterview) => _promptService.CreateTechnicalEvaluationPrompt(evaluateRequest), - _ => string.Empty - }; - - List existingInterviews = await _interviewService.GetInterviewsBySessionIdAsync(interviewSession.ID); - string serializedInterviews = _serializationService.SerializeCollection(existingInterviews); - string prompt = _promptService.GetPromptWithSerializedInterviews(basePrompt, serializedInterviews); - - List evaluatedInterviews = await _aIService.EvaluateInterviewsAsync(prompt, interviewSession.AIAgent); - List evaluatedTInterviews = [..evaluatedInterviews.Cast()]; - - _interviewService.UpdateExistingInterviewWithEvaluation(evaluatedTInterviews, existingInterviews); - _interviewSessionService.FinalizeInterviewSession(interviewSession, evaluatedTInterviews); - - await _interviewService.UpdateInterviewAsync(existingInterviews); + TInterview? existing = existingInterviews.FirstOrDefault(i => i.ID == evaluatedInterview.ID); + if (existing is null) return; + + existing.Score = evaluatedInterview.Score; + existing.Feedback = evaluatedInterview.Feedback; + existing.SelectedAnswer = evaluatedInterview.SelectedAnswer; + existing.IsAnswered = evaluatedInterview.IsAnswered; + existing.Answers = evaluatedInterview.Answers; + } } } -} \ No newline at end of file +} diff --git a/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewService.cs b/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewService.cs index e562a45..539386b 100644 --- a/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewService.cs +++ b/Backend/prepAIred.Services/Services/Interview/Interviews/InterviewService.cs @@ -1,61 +1,114 @@ using prepAIred.Data; -using Microsoft.EntityFrameworkCore; namespace prepAIred.Services { - public class InterviewService(DataContext dataContext) : IInterviewService + /// + /// Provides operations for generating and retrieving AI-generated interviews, including session management and user context. + /// Coordinates between AI services, user services, and interview/session services to create and fetch interview data. + /// + /// The AI service used to generate interview questions and answers. + /// The user service for retrieving user information and context. + /// The interview service for managing interview entities. + /// The interview session service for managing interview session entities. + /// The prompt service for generating prompts for the AI agent. + public class InterviewService(IAIService aIService, IUserRepository userRepository, IInterviewRepository interviewRepository, + IInterviewSessionRepository interviewSessionRepository, IPromptService promptService, ISerializationService serializationService) : IInterviewService { - private readonly DataContext _dataContext = dataContext; + private readonly IAIService _aIService = aIService; + private readonly IUserRepository _userRepository = userRepository; + private readonly IInterviewRepository _interviewRepository = interviewRepository; + private readonly IInterviewSessionRepository _interviewSessionRepository = interviewSessionRepository; + private readonly IPromptService _promptService = promptService; + private readonly ISerializationService _serializationService = serializationService; - public async Task CreateInterviewsAsync(List interviews, User currentUser, InterviewSession interviewSession) + public async Task GenerateInterviewsAsync(BaseRequestDTO request) where TInterview : Interview { - foreach (Interview interview in interviews) - { - interview.User = currentUser; - interview.InterviewSession = interviewSession; - interview.InterviewSessionID = interviewSession.ID; - } + int currentUserID = await _userRepository.GetCurrentUserID(); + User currentUser = await _userRepository.GetCurrentUserEntityByIdAsync(currentUserID); + AIAgent aiAgent = Enum.Parse(request.AIAgent); - await _dataContext.Interviews.AddRangeAsync(interviews); - await _dataContext.SaveChangesAsync(); + await (typeof(TInterview).Name switch + { + nameof(HRInterview) when request is HrRequestDTO hrRequest => CreateHrInterviewAsync(hrRequest, currentUser, aiAgent), + nameof(TechnicalInterview) when request is TechnicalRequestDTO techRequest => CreateTechnicalInterviewAsync(techRequest, currentUser, aiAgent), + _ => Task.CompletedTask + }); } - public async Task> GetInterviewsBySessionIdAsync(int sessionID) where TInterview : Interview + private async Task CreateHrInterviewAsync(HrRequestDTO hrRequest, User currentUser, AIAgent aiAgent) { - return await _dataContext.Interviews - .Where(i => i.InterviewSessionID == sessionID && i.GetType() == typeof(TInterview)) - .Cast() - .ToListAsync(); + string prompt = _promptService.CreateHrPrompt(hrRequest, currentUser.ID); + List interviews = await _aIService.AskAiAgentAsync(aiAgent, prompt); + + InterviewSession interviewSession = new InterviewSession() + { + UserID = currentUser.ID, + User = currentUser, + Interviews = interviews, + AIAgent = aiAgent, + Status = InterviewSessionStatus.Ongoing + }; + + await _interviewSessionRepository.CreateInterviewSessionAsync(interviewSession); + await _interviewRepository.CreateInterviewsAsync(interviews, currentUser, interviewSession); } - public async Task UpdateInterviewAsync(List interviews) where TInterview : Interview + private async Task CreateTechnicalInterviewAsync(TechnicalRequestDTO techRequest, User currentUser, AIAgent aiAgent) { - _dataContext.Interviews.UpdateRange(interviews); - await _dataContext.SaveChangesAsync(); + string prompt = _promptService.CreateTechnicalPrompt(techRequest, currentUser.ID); + List interviews = await _aIService.AskAiAgentAsync(aiAgent, prompt); + InterviewSession interviewSession = await _interviewSessionRepository.GetAdjacentInterviewSessionAsync(currentUser.ID); + + interviewSession.Subject = string.Join(", ", techRequest.Subject); + + await _interviewSessionRepository.UpdateInterviewSessionAsync(interviewSession); + await _interviewRepository.CreateInterviewsAsync(interviews, currentUser, interviewSession); } - public List GetLatestInterviews(List interviews) + public async Task> GetLatestInterviewsAsync() where TInterview : Interview where TInterviewDTO : InterviewDTO { - return interviews - .Select(i => i.ToDto()) - .ToList(); + int currentUserID = await _userRepository.GetCurrentUserID(); + int latestSessionID = await _interviewSessionRepository.GetLatestInterviewSessionIDAsync(currentUserID); + InterviewSession interviewSession = await _interviewSessionRepository.GetInterviewSessionByIdAsync(latestSessionID); + + if (interviewSession is null || interviewSession.Status != InterviewSessionStatus.Ongoing) + { + // After the Technical Interviews are evaluated, the status of the session is set to Passed/Failed, + // meaning the interviews will automatically disappear after evaluation. This check prevents this behavior + // by returning an empty list if the user hasn't clicked the "Finish Interview Session" button. + return []; + } + + List interviews = await _interviewRepository.GetInterviewsBySessionIdAsync(latestSessionID); + List interviewDTOs = _interviewRepository.GetLatestInterviews(interviews); + + return interviewDTOs; } - public void UpdateExistingInterviewWithEvaluation(List evaluatedInterviews, List existingInterviews) where TInterview : Interview + public async Task EvaluateInterviewsAsync(List evaluateRequest) where TInterview : Interview { - foreach (TInterview evaluatedInterview in evaluatedInterviews) + InterviewSession interviewSession = await _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequest); + + string basePrompt = typeof(TInterview).Name switch { - TInterview? existing = existingInterviews.FirstOrDefault(i => i.ID == evaluatedInterview.ID); - if (existing is null) return; - - existing.Score = evaluatedInterview.Score; - existing.Feedback = evaluatedInterview.Feedback; - existing.SelectedAnswer = evaluatedInterview.SelectedAnswer; - existing.IsAnswered = evaluatedInterview.IsAnswered; - existing.Answers = evaluatedInterview.Answers; - } + nameof(HRInterview) => _promptService.CreateHrEvaluationPrompt(evaluateRequest), + nameof(TechnicalInterview) => _promptService.CreateTechnicalEvaluationPrompt(evaluateRequest), + _ => string.Empty + }; + + List existingInterviews = await _interviewRepository.GetInterviewsBySessionIdAsync(interviewSession.ID); + string serializedInterviews = _serializationService.SerializeCollection(existingInterviews); + string prompt = _promptService.GetPromptWithSerializedInterviews(basePrompt, serializedInterviews); + + List evaluatedInterviews = await _aIService.EvaluateInterviewsAsync(prompt, interviewSession.AIAgent); + List evaluatedTInterviews = [..evaluatedInterviews.Cast()]; + + _interviewRepository.UpdateExistingInterviewWithEvaluation(evaluatedTInterviews, existingInterviews); + _interviewSessionRepository.FinalizeInterviewSession(interviewSession, evaluatedTInterviews); + + await _interviewRepository.UpdateInterviewAsync(existingInterviews); } } -} +} \ No newline at end of file diff --git a/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureRepository.cs b/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureRepository.cs index 58b56f0..4a5dfc6 100644 --- a/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureRepository.cs +++ b/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureRepository.cs @@ -1,35 +1,54 @@ -using prepAIred.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using prepAIred.Data; +using prepAIred.Exceptions; namespace prepAIred.Services { - public class ProfilePictureRepository(IProfilePictureService profilePictureService, IUserService userService) : IProfilePictureRepository + public class ProfilePictureRepository(IFileService fileService, DataContext dataContent) : IProfilePictureRepository { - private readonly IUserService _userService = userService; - private readonly IProfilePictureService _profilePictureService = profilePictureService; + private readonly IFileService _fileService = fileService; + private readonly DataContext _dataContext = dataContent; - public async Task ChangeProfilePictureAsync(ProfilePictureDTO profilePictureDTO) + public async Task SaveFileAsync(IFormFile imageFile) { - string fileName = await _profilePictureService.SaveFileAsync(profilePictureDTO.ImageFile!); - - int currentUserID = await _userService.GetCurrentUserID(); - User currentUser = await _userService.GetCurrentUserEntityByIdAsync(currentUserID); - - if (!string.IsNullOrEmpty(currentUser.ProfilePicture)) + if (imageFile is null || imageFile.Length == 0) { - await _profilePictureService.DeleteProfilePictureAsync(currentUser.ProfilePicture); + throw new ProfilePictureException("File is null or empty"); } - currentUser.ProfilePicture = fileName; + string path = _fileService.CreateDirectoryIfNotExists(); + string extension = _fileService.CheckFileExtension(imageFile); + string fileName = await _fileService.CreateFileNameAsync(imageFile, path, extension); - await _userService.UpdateUserAsync(currentUser, null); + return fileName; } - public async Task GetProfilePictureUrlAsync() + public async Task GetProfilePictureUrlByUserIdAsync(int userId) { - int currentUserID = await _userService.GetCurrentUserID(); - string profilePictureUrl = await _profilePictureService.GetProfilePictureUrlByUserIdAsync(currentUserID); + string profilePictureName = await _dataContext.Users + .Where(u => u.ID == userId) + .Select(u => u.ProfilePicture) + .FirstOrDefaultAsync() ?? string.Empty; + + string fullPath = $"https://localhost:7227/Uploads/{profilePictureName}"; - return profilePictureUrl; + return fullPath; + } + + public async Task DeleteProfilePictureAsync(string fileNameWithExtension) + { + string fullPath = _fileService.GetFullPathOfProfilePicture(fileNameWithExtension); + + if (File.Exists(fullPath)) + { + File.Delete(fullPath); + } + + await _dataContext.Users + .Where(u => u.ProfilePicture == fileNameWithExtension) + .ForEachAsync(u => u.ProfilePicture = string.Empty); } } } + diff --git a/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureService.cs b/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureService.cs index 9f61091..1ac77dc 100644 --- a/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureService.cs +++ b/Backend/prepAIred.Services/Services/ProfilePicture/ProfilePictureService.cs @@ -1,54 +1,35 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; -using prepAIred.Data; -using prepAIred.Exceptions; +using prepAIred.Data; namespace prepAIred.Services { - public class ProfilePictureService(IFileService fileService, DataContext dataContent) : IProfilePictureService + public class ProfilePictureService(IProfilePictureRepository profilePictureRepository, IUserRepository userRepository) : IProfilePictureService { - private readonly IFileService _fileService = fileService; - private readonly DataContext _dataContext = dataContent; + private readonly IUserRepository _userRepository = userRepository; + private readonly IProfilePictureRepository _profilePictureRepository = profilePictureRepository; - public async Task SaveFileAsync(IFormFile imageFile) + public async Task ChangeProfilePictureAsync(ProfilePictureDTO profilePictureDTO) { - if (imageFile is null || imageFile.Length == 0) - { - throw new ProfilePictureException("File is null or empty"); - } - - string path = _fileService.CreateDirectoryIfNotExists(); - string extension = _fileService.CheckFileExtension(imageFile); - string fileName = await _fileService.CreateFileNameAsync(imageFile, path, extension); + string fileName = await _profilePictureRepository.SaveFileAsync(profilePictureDTO.ImageFile!); - return fileName; - } + int currentUserID = await _userRepository.GetCurrentUserID(); + User currentUser = await _userRepository.GetCurrentUserEntityByIdAsync(currentUserID); - public async Task GetProfilePictureUrlByUserIdAsync(int userId) - { - string profilePictureName = await _dataContext.Users - .Where(u => u.ID == userId) - .Select(u => u.ProfilePicture) - .FirstOrDefaultAsync() ?? string.Empty; + if (!string.IsNullOrEmpty(currentUser.ProfilePicture)) + { + await _profilePictureRepository.DeleteProfilePictureAsync(currentUser.ProfilePicture); + } - string fullPath = $"https://localhost:7227/Uploads/{profilePictureName}"; + currentUser.ProfilePicture = fileName; - return fullPath; + await _userRepository.UpdateUserAsync(currentUser, null); } - public async Task DeleteProfilePictureAsync(string fileNameWithExtension) + public async Task GetProfilePictureUrlAsync() { - string fullPath = _fileService.GetFullPathOfProfilePicture(fileNameWithExtension); + int currentUserID = await _userRepository.GetCurrentUserID(); + string profilePictureUrl = await _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(currentUserID); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - - await _dataContext.Users - .Where(u => u.ProfilePicture == fileNameWithExtension) - .ForEachAsync(u => u.ProfilePicture = string.Empty); + return profilePictureUrl; } } } - diff --git a/Backend/prepAIred.Services/Services/User/UserRepository.cs b/Backend/prepAIred.Services/Services/User/UserRepository.cs index 2418897..0d4245f 100644 --- a/Backend/prepAIred.Services/Services/User/UserRepository.cs +++ b/Backend/prepAIred.Services/Services/User/UserRepository.cs @@ -1,35 +1,157 @@ using prepAIred.Data; +using prepAIred.Exceptions; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using System.Text; +using System.Security.Cryptography; +using System.Text.RegularExpressions; +using System.IdentityModel.Tokens.Jwt; namespace prepAIred.Services { - public class UserRepository(IUserService userService, ICookieService cookieService) : IUserRepository + public class UserRepository(DataContext dataContext, IJwtService jwtService, IHttpContextAccessor httpContextAccessor) : IUserRepository { - private readonly IUserService _userService = userService; - private readonly ICookieService _cookieService = cookieService; + private readonly DataContext _dataContext = dataContext; + private readonly IJwtService _jwtService = jwtService; + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; - public async Task GetCurrentUserAsync() + public async Task CreateUserAsync(User user) { - CurrentUserDTO user = await _userService.GetCurrentUserAsync(); + _dataContext.Users.Add(user); + await _dataContext.SaveChangesAsync(); + return user; } - public async Task UpdateCurrentUserAsync(UserCredentialsDTO userCredentialsDto) + public async Task GetUserByEmailAsync(string email) => await _dataContext.Users.FirstOrDefaultAsync(u => u.Email == email)! ?? throw new InvalidCredentialsException("Invalid Email"); + + public async Task GetUserByUsernameAsync(string username) => (await _dataContext.Users.FirstOrDefaultAsync(u => u.Username == username))!.ToDto() ?? throw new InvalidCredentialsException("Invalid Username"); + + public async Task GetUserByIdAsync(int userID) => (await _dataContext.Users.FirstOrDefaultAsync(u => u.ID == userID))!.ToDto() ?? throw new InvalidCredentialsException("Invalid User ID"); + + public async Task GetCurrentUserEntityByIdAsync(int userID) => await _dataContext.Users.FirstOrDefaultAsync(u => u.ID == userID) ?? throw new InvalidCredentialsException("Invalid User ID"); + + public async Task GetCurrentUserID() => (await GetCurrentUserAsync()).ID; + + public async Task UpdateUserAsync(User user, UserCredentialsDTO? userCredentialsDto) { - await _userService.ValidateUpdateUserDataAsync(userCredentialsDto); + if (userCredentialsDto is not null) + { + user.Username = userCredentialsDto.Username; + user.Email = userCredentialsDto.Email; - int currentUserID = await _userService.GetCurrentUserID(); - User currentUser = await _userService.GetCurrentUserEntityByIdAsync(currentUserID); + if (!string.IsNullOrEmpty(userCredentialsDto.Password)) + { + (byte[] hashedPassword, byte[] saltPassword) = HashPassword(userCredentialsDto); - await _userService.UpdateUserAsync(currentUser, userCredentialsDto); + user.PasswordHash = hashedPassword; + user.PasswordSalt = saltPassword; + } + } + + _dataContext.Users.Update(user); + await _dataContext.SaveChangesAsync(); + } + + public async Task DeleteUserAsync(int userID) + { + await _dataContext.Users.Where(u => u.ID == userID).ExecuteDeleteAsync(); + await _dataContext.SaveChangesAsync(); } - public async Task DeleteCurrentUserAsync() + public async Task GetCurrentUserAsync() { - _cookieService.DeleteCookie("AccessToken"); - _cookieService.DeleteCookie("RefreshToken"); + string jwt = _httpContextAccessor.HttpContext!.Request.Cookies["AccessToken"]!; + + if (string.IsNullOrEmpty(jwt)) throw new InvalidAccessTokenException("Access Token is not valid!"); + + try + { + JwtSecurityToken token = _jwtService.Verify(jwt); + int.TryParse(_jwtService.GetUserIdFromToken(token), out int userID); + CurrentUserDTO currentUser = await GetUserByIdAsync(userID); + + return currentUser; + } + catch (NoUserLoggedInException ex) + { + throw new NoUserLoggedInException($"No user currently logged in! ${ex.Message}"); + } + } + + public async Task ValidateUserAsync(UserCredentialsDTO userCredentialsDto) + { + if (!AreFieldsEmpty(userCredentialsDto)) throw new EmptyFieldsException("Enter data in all fields"); + + if (!ValidateEmail(userCredentialsDto.Email) || !ValidatePassword(userCredentialsDto.Password)) throw new InvalidCredentialsException("Invalid Email or Password"); + + if (await UserExistsAsync(userCredentialsDto.Email)) throw new UserAlreadyExistsException("User already exists"); + } + + public async Task ValidateUpdateUserDataAsync(UserCredentialsDTO userCredentialsDto) + { + if (string.IsNullOrEmpty(userCredentialsDto.Username) || string.IsNullOrEmpty(userCredentialsDto.Email)) throw new DataValidationException("Username and Email cannot be empty"); + + if (!ValidateEmail(userCredentialsDto.Email)) throw new DataValidationException("Invalid Email format"); + + if (!string.IsNullOrEmpty(userCredentialsDto.Password)) + { + if (!ValidatePassword(userCredentialsDto.Password)) throw new DataValidationException("Invalid Password"); + } + + int currentUserID = await GetCurrentUserID(); + User currentUser = await GetCurrentUserEntityByIdAsync(currentUserID); + + if (userCredentialsDto.Email != currentUser.Email) + { + if (await UserExistsAsync(userCredentialsDto.Email)) throw new DataValidationException("A user with this email already exists"); + } + } + + public bool AreFieldsEmpty(UserCredentialsDTO registerDto) + { + if (string.IsNullOrWhiteSpace(registerDto.Email) || string.IsNullOrWhiteSpace(registerDto.Username) || string.IsNullOrWhiteSpace(registerDto.Password)) + return false; + + return true; + } + + public async Task UserExistsAsync(string email) => await _dataContext.Users.AnyAsync(u => u.Email == email); + + public bool ValidateEmail(string email) + { + string emailRegExPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; + Regex emailRegex = new Regex(emailRegExPattern); + + return emailRegex.IsMatch(email); + } + + public bool ValidatePassword(string password) + { + string passwordRegExPattern = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{10,}$"; + Regex passwordRegex = new Regex(passwordRegExPattern); + + return passwordRegex.IsMatch(password); + } + + public (byte[] hashedPassword, byte[] saltPassword) HashPassword(UserCredentialsDTO registerDTO) + { + using HMACSHA512 hmac = new HMACSHA512(); + + byte[] hashedPassword = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDTO.Password)); + byte[] saltPassword = hmac.Key; + + return (hashedPassword, saltPassword); + } + + public bool CheckPassword(User user, LoginDTO loginDto) + { + using HMACSHA512 hmac = new HMACSHA512(user.PasswordSalt); + byte[] computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password)); + + if (!computedHash.SequenceEqual(user.PasswordHash)) return false; - int currentUserID = await _userService.GetCurrentUserID(); - await _userService.DeleteUserAsync(currentUserID); + return true; } } } diff --git a/Backend/prepAIred.Services/Services/User/UserService.cs b/Backend/prepAIred.Services/Services/User/UserService.cs index ca90d76..3f06690 100644 --- a/Backend/prepAIred.Services/Services/User/UserService.cs +++ b/Backend/prepAIred.Services/Services/User/UserService.cs @@ -1,157 +1,35 @@ using prepAIred.Data; -using prepAIred.Exceptions; -using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; -using System.Text; -using System.Security.Cryptography; -using System.Text.RegularExpressions; -using System.IdentityModel.Tokens.Jwt; namespace prepAIred.Services { - public class UserService(DataContext dataContext, IJwtService jwtService, IHttpContextAccessor httpContextAccessor) : IUserService + public class UserService(IUserRepository userRepository, ICookieService cookieService) : IUserService { - private readonly DataContext _dataContext = dataContext; - private readonly IJwtService _jwtService = jwtService; - private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; - - public async Task CreateUserAsync(User user) - { - _dataContext.Users.Add(user); - await _dataContext.SaveChangesAsync(); - - return user; - } - - public async Task GetUserByEmailAsync(string email) => await _dataContext.Users.FirstOrDefaultAsync(u => u.Email == email)! ?? throw new InvalidCredentialsException("Invalid Email"); - - public async Task GetUserByUsernameAsync(string username) => (await _dataContext.Users.FirstOrDefaultAsync(u => u.Username == username))!.ToDto() ?? throw new InvalidCredentialsException("Invalid Username"); - - public async Task GetUserByIdAsync(int userID) => (await _dataContext.Users.FirstOrDefaultAsync(u => u.ID == userID))!.ToDto() ?? throw new InvalidCredentialsException("Invalid User ID"); - - public async Task GetCurrentUserEntityByIdAsync(int userID) => await _dataContext.Users.FirstOrDefaultAsync(u => u.ID == userID) ?? throw new InvalidCredentialsException("Invalid User ID"); - - public async Task GetCurrentUserID() => (await GetCurrentUserAsync()).ID; - - public async Task UpdateUserAsync(User user, UserCredentialsDTO? userCredentialsDto) - { - if (userCredentialsDto is not null) - { - user.Username = userCredentialsDto.Username; - user.Email = userCredentialsDto.Email; - - if (!string.IsNullOrEmpty(userCredentialsDto.Password)) - { - (byte[] hashedPassword, byte[] saltPassword) = HashPassword(userCredentialsDto); - - user.PasswordHash = hashedPassword; - user.PasswordSalt = saltPassword; - } - } - - _dataContext.Users.Update(user); - await _dataContext.SaveChangesAsync(); - } - - public async Task DeleteUserAsync(int userID) - { - await _dataContext.Users.Where(u => u.ID == userID).ExecuteDeleteAsync(); - await _dataContext.SaveChangesAsync(); - } + private readonly IUserRepository _userRepository = userRepository; + private readonly ICookieService _cookieService = cookieService; public async Task GetCurrentUserAsync() { - string jwt = _httpContextAccessor.HttpContext!.Request.Cookies["AccessToken"]!; - - if (string.IsNullOrEmpty(jwt)) throw new InvalidAccessTokenException("Access Token is not valid!"); - - try - { - JwtSecurityToken token = _jwtService.Verify(jwt); - int.TryParse(_jwtService.GetUserIdFromToken(token), out int userID); - CurrentUserDTO currentUser = await GetUserByIdAsync(userID); - - return currentUser; - } - catch (NoUserLoggedInException ex) - { - throw new NoUserLoggedInException($"No user currently logged in! ${ex.Message}"); - } - } - - public async Task ValidateUserAsync(UserCredentialsDTO userCredentialsDto) - { - if (!AreFieldsEmpty(userCredentialsDto)) throw new EmptyFieldsException("Enter data in all fields"); - - if (!ValidateEmail(userCredentialsDto.Email) || !ValidatePassword(userCredentialsDto.Password)) throw new InvalidCredentialsException("Invalid Email or Password"); - - if (await UserExistsAsync(userCredentialsDto.Email)) throw new UserAlreadyExistsException("User already exists"); - } - - public async Task ValidateUpdateUserDataAsync(UserCredentialsDTO userCredentialsDto) - { - if (string.IsNullOrEmpty(userCredentialsDto.Username) || string.IsNullOrEmpty(userCredentialsDto.Email)) throw new EmptyFieldsException("Username and Email cannot be empty"); - - if (!ValidateEmail(userCredentialsDto.Email)) throw new InvalidCredentialsException("Invalid Email format"); - - if (!string.IsNullOrEmpty(userCredentialsDto.Password)) - { - if (!ValidatePassword(userCredentialsDto.Password)) throw new InvalidCredentialsException("Invalid Password"); - } - - int currentUserID = await GetCurrentUserID(); - User currentUser = await GetCurrentUserEntityByIdAsync(currentUserID); - - if (userCredentialsDto.Email != currentUser.Email) - { - if (await UserExistsAsync(userCredentialsDto.Email)) throw new UserAlreadyExistsException("User already exists"); - } - } - - public bool AreFieldsEmpty(UserCredentialsDTO registerDto) - { - if (string.IsNullOrWhiteSpace(registerDto.Email) || string.IsNullOrWhiteSpace(registerDto.Username) || string.IsNullOrWhiteSpace(registerDto.Password)) - return false; - - return true; - } - - public async Task UserExistsAsync(string email) => await _dataContext.Users.AnyAsync(u => u.Email == email); - - public bool ValidateEmail(string email) - { - string emailRegExPattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; - Regex emailRegex = new Regex(emailRegExPattern); - - return emailRegex.IsMatch(email); - } - - public bool ValidatePassword(string password) - { - string passwordRegExPattern = @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{10,}$"; - Regex passwordRegex = new Regex(passwordRegExPattern); - - return passwordRegex.IsMatch(password); + CurrentUserDTO user = await _userRepository.GetCurrentUserAsync(); + return user; } - public (byte[] hashedPassword, byte[] saltPassword) HashPassword(UserCredentialsDTO registerDTO) + public async Task UpdateCurrentUserAsync(UserCredentialsDTO userCredentialsDto) { - using HMACSHA512 hmac = new HMACSHA512(); + await _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto); - byte[] hashedPassword = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDTO.Password)); - byte[] saltPassword = hmac.Key; + int currentUserID = await _userRepository.GetCurrentUserID(); + User currentUser = await _userRepository.GetCurrentUserEntityByIdAsync(currentUserID); - return (hashedPassword, saltPassword); + await _userRepository.UpdateUserAsync(currentUser, userCredentialsDto); } - public bool CheckPassword(User user, LoginDTO loginDto) + public async Task DeleteCurrentUserAsync() { - using HMACSHA512 hmac = new HMACSHA512(user.PasswordSalt); - byte[] computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password)); - - if (!computedHash.SequenceEqual(user.PasswordHash)) return false; + _cookieService.DeleteCookie("AccessToken"); + _cookieService.DeleteCookie("RefreshToken"); - return true; + int currentUserID = await _userRepository.GetCurrentUserID(); + await _userRepository.DeleteUserAsync(currentUserID); } } } diff --git a/Backend/prepAIred.Tests/Controllers/AuthControllerTest.cs b/Backend/prepAIred.Tests/Controllers/AuthControllerTest.cs deleted file mode 100644 index 0341294..0000000 --- a/Backend/prepAIred.Tests/Controllers/AuthControllerTest.cs +++ /dev/null @@ -1,98 +0,0 @@ -using FakeItEasy; -using Microsoft.AspNetCore.Mvc; -using prepAIred.API; -using prepAIred.Data; -using prepAIred.Exceptions; -using prepAIred.Services; - -namespace prepAIred.Tests.Controllers -{ - public class AuthControllerTest - { - private readonly IAuthRepository _authRepository; - private readonly IRefreshTokenRepository _refreshTokenRepository; - private readonly AuthController _authController; - - public AuthControllerTest() - { - _authRepository = A.Fake(); - _refreshTokenRepository = A.Fake(); - - _authController = new AuthController(_authRepository, _refreshTokenRepository); - } - - [Fact] - public async Task AuthController_Register_ReturnsOk_ForValidCredentials() - { - UserCredentialsDTO registerRequest = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - A.CallTo(() => _authRepository.RegisterAsync(registerRequest)).Returns(Task.CompletedTask); - - IActionResult result = await _authController.Register(registerRequest); - - Assert.IsType(result); - A.CallTo(() => _authRepository.RegisterAsync(registerRequest)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthController_Register_ThrowsException_ForWeakPassword() - { - UserCredentialsDTO registerRequest = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "weakpassword" - }; - - A.CallTo(() => _authRepository.RegisterAsync(registerRequest)) - .ThrowsAsync(new InvalidCredentialsException("Password does not meet complexity requirements")); - - await Assert.ThrowsAsync(() => _authController.Register(registerRequest)); - } - - [Fact] - public async Task AuthController_Login_ReturnsOk_ForValidCredentials() - { - LoginDTO loginRequest = new LoginDTO() - { - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - A.CallTo(() => _authRepository.LoginAsync(loginRequest)).Returns(Task.CompletedTask); - - IActionResult result = await _authController.Login(loginRequest); - - Assert.IsType(result); - A.CallTo(() => _authRepository.LoginAsync(loginRequest)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthController_Logout_ReturnsOk() - { - A.CallTo(() => _authRepository.LogoutAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _authController.Logout(); - - Assert.IsType(result); - A.CallTo(() => _authRepository.LogoutAsync()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthController_GenerateNewRefreshToken_ReturnsOk() - { - RefreshTokenResponseDTO tokenResponse = new RefreshTokenResponseDTO(); - A.CallTo(() => _refreshTokenRepository.GenerateNewRefreshTokenAsync()).Returns(tokenResponse); - - IActionResult result = await _authController.GenerateNewRefreshToken(); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal(tokenResponse, okResult.Value); - } - } -} diff --git a/Backend/prepAIred.Tests/Controllers/InterviewControllerTest.cs b/Backend/prepAIred.Tests/Controllers/InterviewControllerTest.cs deleted file mode 100644 index edc46ba..0000000 --- a/Backend/prepAIred.Tests/Controllers/InterviewControllerTest.cs +++ /dev/null @@ -1,294 +0,0 @@ -using FakeItEasy; -using Microsoft.AspNetCore.Mvc; -using prepAIred.API; -using prepAIred.Data; -using prepAIred.Services; - -namespace prepAIred.Tests.Controllers -{ - public class InterviewControllerTest - { - private readonly IInterviewRepository _interviewRepository; - private readonly InterviewController _interviewController; - - public InterviewControllerTest() - { - _interviewRepository = A.Fake(); - _interviewController = new InterviewController(_interviewRepository); - } - - #region GenerateHrInterview Tests - - [Fact] - public async Task InterviewController_GenerateHrInterview_ReturnsOk() - { - HrRequestDTO hrRequest = new HrRequestDTO - { - AIAgent = "ChatGPT", - SoftSkillFocus = new List { "Communication" }, - ContextScenario = new List { "Team Collaboration" } - }; - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(hrRequest)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.GenerateHrInterview(hrRequest); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewController_GenerateHrInterview_ReturnsSuccessMessage() - { - HrRequestDTO hrRequest = new HrRequestDTO(); - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(hrRequest)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.GenerateHrInterview(hrRequest); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("HR interviews created successfully.", okResult.Value); - } - - [Fact] - public async Task InterviewController_GenerateHrInterview_CallsRepositoryWithCorrectType() - { - HrRequestDTO hrRequest = new HrRequestDTO(); - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(hrRequest)).Returns(Task.CompletedTask); - - await _interviewController.GenerateHrInterview(hrRequest); - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(hrRequest)).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetLatestHrInterview Tests - - [Fact] - public async Task InterviewController_GetLatestHrInterview_ReturnsOk() - { - List interviews = new List(); - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).Returns(interviews); - - IActionResult result = await _interviewController.GetLatestHrInterview(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewController_GetLatestHrInterview_ReturnsInterviewList() - { - List interviews = new List - { - new HRInterviewDTO { ID = 1, Question = "Tell me about a time...", SoftSkillFocus = "Communication" }, - new HRInterviewDTO { ID = 2, Question = "How do you handle...", SoftSkillFocus = "Teamwork" } - }; - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).Returns(interviews); - - IActionResult result = await _interviewController.GetLatestHrInterview(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedInterviews = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedInterviews.Count); - } - - [Fact] - public async Task InterviewController_GetLatestHrInterview_CallsRepositoryMethod() - { - List interviews = new List(); - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).Returns(interviews); - - await _interviewController.GetLatestHrInterview(); - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region EvaluateHrInterviews Tests - - [Fact] - public async Task InterviewController_EvaluateHrInterviews_ReturnsOk() - { - List evaluateRequests = new List - { - new EvaluateRequestDTO { Question = "Question 1", Answer = "Answer 1" } - }; - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.EvaluateHrInterviews(evaluateRequests); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewController_EvaluateHrInterviews_ReturnsSuccessMessage() - { - List evaluateRequests = new List(); - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.EvaluateHrInterviews(evaluateRequests); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("HR interviews evaluated successfully.", okResult.Value); - } - - [Fact] - public async Task InterviewController_EvaluateHrInterviews_CallsRepositoryWithCorrectType() - { - List evaluateRequests = new List(); - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).Returns(Task.CompletedTask); - - await _interviewController.EvaluateHrInterviews(evaluateRequests); - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GenerateTechnicalInterviews Tests - - [Fact] - public async Task InterviewController_GenerateTechnicalInterviews_ReturnsOk() - { - TechnicalRequestDTO technicalRequest = new TechnicalRequestDTO - { - AIAgent = "ChatGPT", - ProgrammingLanguage = "C#", - Subject = new List { "OOP" }, - DifficultyLevel = "Medium", - Position = "Junior Developer" - }; - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(technicalRequest)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.GenerateTechnicalInterviews(technicalRequest); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewController_GenerateTechnicalInterviews_ReturnsSuccessMessage() - { - TechnicalRequestDTO technicalRequest = new TechnicalRequestDTO(); - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(technicalRequest)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.GenerateTechnicalInterviews(technicalRequest); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("Technical interviews created successfully.", okResult.Value); - } - - [Fact] - public async Task InterviewController_GenerateTechnicalInterviews_CallsRepositoryWithCorrectType() - { - TechnicalRequestDTO technicalRequest = new TechnicalRequestDTO(); - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(technicalRequest)).Returns(Task.CompletedTask); - - await _interviewController.GenerateTechnicalInterviews(technicalRequest); - - A.CallTo(() => _interviewRepository.GenerateInterviewsAsync(technicalRequest)).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetLatestTechnicalInterview Tests - - [Fact] - public async Task InterviewController_GetLatestTechnicalInterview_ReturnsOk() - { - List interviews = new List(); - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).Returns(interviews); - - IActionResult result = await _interviewController.GetLatestTechnicalInterview(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewController_GetLatestTechnicalInterview_ReturnsInterviewList() - { - List interviews = new List - { - new TechnicalInterviewDTO { ID = 1, Question = "What is polymorphism?", ProgrammingLanguage = "C#" }, - new TechnicalInterviewDTO { ID = 2, Question = "Explain SOLID principles", ProgrammingLanguage = "C#" } - }; - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).Returns(interviews); - - IActionResult result = await _interviewController.GetLatestTechnicalInterview(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedInterviews = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedInterviews.Count); - } - - [Fact] - public async Task InterviewController_GetLatestTechnicalInterview_CallsRepositoryMethod() - { - List interviews = new List(); - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).Returns(interviews); - - await _interviewController.GetLatestTechnicalInterview(); - - A.CallTo(() => _interviewRepository.GetLatestInterviewsAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region EvaluateTechnicalInterviews Tests - - [Fact] - public async Task InterviewController_EvaluateTechnicalInterviews_ReturnsOk() - { - List evaluateRequests = new List - { - new EvaluateRequestDTO { Question = "What is OOP?", Answer = "Object-Oriented Programming" } - }; - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.EvaluateTechnicalInterviews(evaluateRequests); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewController_EvaluateTechnicalInterviews_ReturnsSuccessMessage() - { - List evaluateRequests = new List(); - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).Returns(Task.CompletedTask); - - IActionResult result = await _interviewController.EvaluateTechnicalInterviews(evaluateRequests); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("Technical interviews evaluated successfully.", okResult.Value); - } - - [Fact] - public async Task InterviewController_EvaluateTechnicalInterviews_CallsRepositoryWithCorrectType() - { - List evaluateRequests = new List(); - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).Returns(Task.CompletedTask); - - await _interviewController.EvaluateTechnicalInterviews(evaluateRequests); - - A.CallTo(() => _interviewRepository.EvaluateInterviewsAsync(evaluateRequests)).MustHaveHappenedOnceExactly(); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Controllers/InterviewSessionControllerTest.cs b/Backend/prepAIred.Tests/Controllers/InterviewSessionControllerTest.cs deleted file mode 100644 index 86be4df..0000000 --- a/Backend/prepAIred.Tests/Controllers/InterviewSessionControllerTest.cs +++ /dev/null @@ -1,368 +0,0 @@ -using FakeItEasy; -using Microsoft.AspNetCore.Mvc; -using prepAIred.API; -using prepAIred.Data; -using prepAIred.Services; - -namespace prepAIred.Tests.Controllers -{ - public class InterviewSessionControllerTest - { - private readonly IInterviewSessionRepository _interviewSessionRepository; - private readonly InterviewSessionController _interviewSessionController; - - public InterviewSessionControllerTest() - { - _interviewSessionRepository = A.Fake(); - _interviewSessionController = new InterviewSessionController(_interviewSessionRepository); - } - - #region GetInterviewSessionDTOs Tests - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionDTOs_ReturnsOk() - { - List interviewSessions = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionDTOsAsync()).Returns(interviewSessions); - - IActionResult result = await _interviewSessionController.GetInterviewSessionDTOs(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionDTOs_ReturnsListOfSessions() - { - List interviewSessions = new List - { - new InterviewSessionDTO { ID = 1, Subject = "C# Basics" }, - new InterviewSessionDTO { ID = 2, Subject = "Python Advanced" } - }; - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionDTOsAsync()).Returns(interviewSessions); - - IActionResult result = await _interviewSessionController.GetInterviewSessionDTOs(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedSessions = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedSessions.Count); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionDTOs_CallsRepositoryMethod() - { - List interviewSessions = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionDTOsAsync()).Returns(interviewSessions); - - await _interviewSessionController.GetInterviewSessionDTOs(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionDTOsAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionActivities Tests - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionActivities_ReturnsOk() - { - List activities = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync()).Returns(activities); - - IActionResult result = await _interviewSessionController.GetInterviewSessionActivities(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionActivities_ReturnsListOfActivities() - { - List activities = new List - { - new InterviewSessionActivityDTO { ID = 1, Subject = "C# OOP", AverageScore = 8.5f }, - new InterviewSessionActivityDTO { ID = 2, Subject = "Python ML", AverageScore = 7.2f } - }; - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync()).Returns(activities); - - IActionResult result = await _interviewSessionController.GetInterviewSessionActivities(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedActivities = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedActivities.Count); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionActivities_CallsRepositoryMethod() - { - List activities = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync()).Returns(activities); - - await _interviewSessionController.GetInterviewSessionActivities(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionStatistics Tests - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionStatistics_ReturnsOk() - { - ProfileStatisticsDTO stats = new ProfileStatisticsDTO(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionStatisticsAsync()).Returns(stats); - - IActionResult result = await _interviewSessionController.GetInterviewSessionStatistics(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionStatistics_ReturnsStatistics() - { - ProfileStatisticsDTO stats = new ProfileStatisticsDTO - { - TotalInterviewSessions = 10, - PassedInterviewSessions = 7, - AverageScore = 8.5m - }; - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionStatisticsAsync()).Returns(stats); - - IActionResult result = await _interviewSessionController.GetInterviewSessionStatistics(); - - OkObjectResult okResult = Assert.IsType(result); - ProfileStatisticsDTO returnedStats = Assert.IsType(okResult.Value); - Assert.Equal(10, returnedStats.TotalInterviewSessions); - Assert.Equal(7, returnedStats.PassedInterviewSessions); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionStatistics_CallsRepositoryMethod() - { - ProfileStatisticsDTO stats = new ProfileStatisticsDTO(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionStatisticsAsync()).Returns(stats); - - await _interviewSessionController.GetInterviewSessionStatistics(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionStatisticsAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionPerformance Tests - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionPerformance_ReturnsOk() - { - List performance = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPerformanceAsync()).Returns(performance); - - IActionResult result = await _interviewSessionController.GetInterviewSessionPerformance(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionPerformance_ReturnsPerformanceData() - { - List performance = new List - { - new InterviewSessionPerformanceDTO { ID = 1, Score = 8.0f }, - new InterviewSessionPerformanceDTO { ID = 2, Score = 9.0f } - }; - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPerformanceAsync()).Returns(performance); - - IActionResult result = await _interviewSessionController.GetInterviewSessionPerformance(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedPerformance = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedPerformance.Count); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionPerformance_CallsRepositoryMethod() - { - List performance = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPerformanceAsync()).Returns(performance); - - await _interviewSessionController.GetInterviewSessionPerformance(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPerformanceAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionProgrammingLanguageData Tests - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionProgrammingLanguageData_ReturnsOk() - { - List languageData = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync()).Returns(languageData); - - IActionResult result = await _interviewSessionController.GetInterviewSessionProgrammingLanguageData(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionProgrammingLanguageData_ReturnsLanguageData() - { - List languageData = new List - { - new ProgrammingLanguageDataDTO { Language = "C#", Sessions = 5 }, - new ProgrammingLanguageDataDTO { Language = "Python", Sessions = 3 } - }; - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync()).Returns(languageData); - - IActionResult result = await _interviewSessionController.GetInterviewSessionProgrammingLanguageData(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedData = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedData.Count); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionProgrammingLanguageData_CallsRepositoryMethod() - { - List languageData = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync()).Returns(languageData); - - await _interviewSessionController.GetInterviewSessionProgrammingLanguageData(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionPositionData Tests - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionPositionData_ReturnsOk() - { - List positionData = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPositionDataAsync()).Returns(positionData); - - IActionResult result = await _interviewSessionController.GetInterviewSessionPositionData(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionPositionData_ReturnsPositionData() - { - List positionData = new List - { - new PositionDataDTO { Position = "Junior Developer", Sessions = 4 }, - new PositionDataDTO { Position = "Senior Developer", Sessions = 6 } - }; - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPositionDataAsync()).Returns(positionData); - - IActionResult result = await _interviewSessionController.GetInterviewSessionPositionData(); - - OkObjectResult okResult = Assert.IsType(result); - List returnedData = Assert.IsType>(okResult.Value); - Assert.Equal(2, returnedData.Count); - } - - [Fact] - public async Task InterviewSessionController_GetInterviewSessionPositionData_CallsRepositoryMethod() - { - List positionData = new List(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPositionDataAsync()).Returns(positionData); - - await _interviewSessionController.GetInterviewSessionPositionData(); - - A.CallTo(() => _interviewSessionRepository.GetInterviewSessionPositionDataAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region FinishInterviewSession Tests - - [Fact] - public async Task InterviewSessionController_FinishInterviewSession_ReturnsOk() - { - A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _interviewSessionController.FinishInterviewSession(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_FinishInterviewSession_ReturnsSuccessMessage() - { - A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _interviewSessionController.FinishInterviewSession(); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("Interview session finished successfully.", okResult.Value); - } - - [Fact] - public async Task InterviewSessionController_FinishInterviewSession_CallsRepositoryMethod() - { - A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync()).Returns(Task.CompletedTask); - - await _interviewSessionController.FinishInterviewSession(); - - A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region DeleteInterviewSessions Tests - - [Fact] - public async Task InterviewSessionController_DeleteInterviewSessions_ReturnsOk() - { - A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _interviewSessionController.DeleteInterviewSessions(); - - Assert.IsType(result); - } - - [Fact] - public async Task InterviewSessionController_DeleteInterviewSessions_ReturnsSuccessMessage() - { - A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _interviewSessionController.DeleteInterviewSessions(); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("All interview sessions deleted successfully.", okResult.Value); - } - - [Fact] - public async Task InterviewSessionController_DeleteInterviewSessions_CallsRepositoryMethod() - { - A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync()).Returns(Task.CompletedTask); - - await _interviewSessionController.DeleteInterviewSessions(); - - A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Controllers/ProfilePictureControllerTest.cs b/Backend/prepAIred.Tests/Controllers/ProfilePictureControllerTest.cs deleted file mode 100644 index 9a01958..0000000 --- a/Backend/prepAIred.Tests/Controllers/ProfilePictureControllerTest.cs +++ /dev/null @@ -1,126 +0,0 @@ -using FakeItEasy; -using Microsoft.AspNetCore.Mvc; -using prepAIred.API; -using prepAIred.Data; -using prepAIred.Services; - -namespace prepAIred.Tests.Controllers -{ - public class ProfilePictureControllerTest - { - private readonly IProfilePictureRepository _profilePictureRepository; - private readonly ProfilePictureController _profilePictureController; - - public ProfilePictureControllerTest() - { - _profilePictureRepository = A.Fake(); - _profilePictureController = new ProfilePictureController(_profilePictureRepository); - } - - #region GetProfilePicture Tests - - [Fact] - public async Task ProfilePictureController_GetProfilePicture_ReturnsOk() - { - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlAsync()).Returns(expectedUrl); - - IActionResult result = await _profilePictureController.GetProfilePicture(); - - Assert.IsType(result); - } - - [Fact] - public async Task ProfilePictureController_GetProfilePicture_ReturnsCorrectUrl() - { - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlAsync()).Returns(expectedUrl); - - IActionResult result = await _profilePictureController.GetProfilePicture(); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal(expectedUrl, okResult.Value); - } - - [Fact] - public async Task ProfilePictureController_GetProfilePicture_CallsRepositoryMethod() - { - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlAsync()).Returns(expectedUrl); - - await _profilePictureController.GetProfilePicture(); - - A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlAsync()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task ProfilePictureController_GetProfilePicture_ReturnsEmptyString_WhenNoProfilePicture() - { - A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlAsync()).Returns(string.Empty); - - IActionResult result = await _profilePictureController.GetProfilePicture(); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal(string.Empty, okResult.Value); - } - - #endregion - - #region ChangeProfilePicture Tests - - [Fact] - public async Task ProfilePictureController_ChangeProfilePicture_ReturnsOk() - { - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO(); - - A.CallTo(() => _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto)).Returns(Task.CompletedTask); - - IActionResult result = await _profilePictureController.ChangeProfilePicture(profilePictureDto); - - Assert.IsType(result); - } - - [Fact] - public async Task ProfilePictureController_ChangeProfilePicture_ReturnsSuccessMessage() - { - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO(); - - A.CallTo(() => _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto)).Returns(Task.CompletedTask); - - IActionResult result = await _profilePictureController.ChangeProfilePicture(profilePictureDto); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("Profile picture changed", okResult.Value); - } - - [Fact] - public async Task ProfilePictureController_ChangeProfilePicture_CallsRepositoryMethod() - { - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO(); - - A.CallTo(() => _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto)).Returns(Task.CompletedTask); - - await _profilePictureController.ChangeProfilePicture(profilePictureDto); - - A.CallTo(() => _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task ProfilePictureController_ChangeProfilePicture_PassesCorrectDtoToRepository() - { - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO(); - - A.CallTo(() => _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto)).Returns(Task.CompletedTask); - - await _profilePictureController.ChangeProfilePicture(profilePictureDto); - - A.CallTo(() => _profilePictureRepository.ChangeProfilePictureAsync(A.That.Matches(dto => dto == profilePictureDto))) - .MustHaveHappenedOnceExactly(); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Controllers/UserControllerTest.cs b/Backend/prepAIred.Tests/Controllers/UserControllerTest.cs deleted file mode 100644 index 6f3064d..0000000 --- a/Backend/prepAIred.Tests/Controllers/UserControllerTest.cs +++ /dev/null @@ -1,253 +0,0 @@ -using FakeItEasy; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using prepAIred.API; -using prepAIred.Data; -using prepAIred.Services; - -namespace prepAIred.Tests.Controllers -{ - public class UserControllerTest - { - private readonly IUserRepository _userRepository; - private readonly UserController _userController; - - public UserControllerTest() - { - _userRepository = A.Fake(); - _userController = new UserController(_userRepository); - } - - #region GetCurrentUser Tests - - [Fact] - public async Task UserController_GetCurrentUser_ReturnsNoContent_WhenAccessTokenIsNull() - { - _userController.ControllerContext = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - IActionResult result = await _userController.GetCurrentUser(); - - Assert.IsType(result); - } - - [Fact] - public async Task UserController_GetCurrentUser_ReturnsNoContent_WhenAccessTokenIsEmpty() - { - DefaultHttpContext httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Cookie = "AccessToken="; - - _userController.ControllerContext = new ControllerContext - { - HttpContext = httpContext - }; - - IActionResult result = await _userController.GetCurrentUser(); - - Assert.IsType(result); - } - - [Fact] - public async Task UserController_GetCurrentUser_ReturnsOk_WhenAccessTokenExists() - { - DefaultHttpContext httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Cookie = "AccessToken=valid-token"; - - CurrentUserDTO currentUser = new CurrentUserDTO - { - ID = 1, - Username = "JohnDoe", - Email = "john@example.com" - }; - - _userController.ControllerContext = new ControllerContext - { - HttpContext = httpContext - }; - - A.CallTo(() => _userRepository.GetCurrentUserAsync()).Returns(currentUser); - - IActionResult result = await _userController.GetCurrentUser(); - - Assert.IsType(result); - } - - [Fact] - public async Task UserController_GetCurrentUser_ReturnsCurrentUserDto_WhenAccessTokenExists() - { - DefaultHttpContext httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Cookie = "AccessToken=valid-token"; - - CurrentUserDTO currentUser = new CurrentUserDTO - { - ID = 1, - Username = "JohnDoe", - Email = "john@example.com" - }; - - _userController.ControllerContext = new ControllerContext - { - HttpContext = httpContext - }; - - A.CallTo(() => _userRepository.GetCurrentUserAsync()).Returns(currentUser); - - IActionResult result = await _userController.GetCurrentUser(); - - OkObjectResult okResult = Assert.IsType(result); - CurrentUserDTO returnedUser = Assert.IsType(okResult.Value); - Assert.Equal(currentUser.ID, returnedUser.ID); - Assert.Equal(currentUser.Username, returnedUser.Username); - Assert.Equal(currentUser.Email, returnedUser.Email); - } - - [Fact] - public async Task UserController_GetCurrentUser_CallsRepositoryMethod_WhenAccessTokenExists() - { - DefaultHttpContext httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Cookie = "AccessToken=valid-token"; - - CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; - - _userController.ControllerContext = new ControllerContext - { - HttpContext = httpContext - }; - - A.CallTo(() => _userRepository.GetCurrentUserAsync()).Returns(currentUser); - - await _userController.GetCurrentUser(); - - A.CallTo(() => _userRepository.GetCurrentUserAsync()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserController_GetCurrentUser_DoesNotCallRepository_WhenAccessTokenIsNull() - { - _userController.ControllerContext = new ControllerContext - { - HttpContext = new DefaultHttpContext() - }; - - await _userController.GetCurrentUser(); - - A.CallTo(() => _userRepository.GetCurrentUserAsync()).MustNotHaveHappened(); - } - - #endregion - - #region UpdateCurrentUser Tests - - [Fact] - public async Task UserController_UpdateCurrentUser_ReturnsOk() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - A.CallTo(() => _userRepository.UpdateCurrentUserAsync(userCredentialsDto)).Returns(Task.CompletedTask); - - IActionResult result = await _userController.UpdateCurrentUser(userCredentialsDto); - - Assert.IsType(result); - } - - [Fact] - public async Task UserController_UpdateCurrentUser_ReturnsSuccessMessage() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - A.CallTo(() => _userRepository.UpdateCurrentUserAsync(userCredentialsDto)).Returns(Task.CompletedTask); - - IActionResult result = await _userController.UpdateCurrentUser(userCredentialsDto); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("User updated", okResult.Value); - } - - [Fact] - public async Task UserController_UpdateCurrentUser_CallsRepositoryMethod() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - A.CallTo(() => _userRepository.UpdateCurrentUserAsync(userCredentialsDto)).Returns(Task.CompletedTask); - - await _userController.UpdateCurrentUser(userCredentialsDto); - - A.CallTo(() => _userRepository.UpdateCurrentUserAsync(userCredentialsDto)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserController_UpdateCurrentUser_PassesCorrectDtoToRepository() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - A.CallTo(() => _userRepository.UpdateCurrentUserAsync(userCredentialsDto)).Returns(Task.CompletedTask); - - await _userController.UpdateCurrentUser(userCredentialsDto); - - A.CallTo(() => _userRepository.UpdateCurrentUserAsync(A.That.Matches(dto => - dto.Username == userCredentialsDto.Username && - dto.Email == userCredentialsDto.Email && - dto.Password == userCredentialsDto.Password - ))).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region DeleteCurrentUser Tests - - [Fact] - public async Task UserController_DeleteCurrentUser_ReturnsOk() - { - A.CallTo(() => _userRepository.DeleteCurrentUserAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _userController.DeleteCurrentUser(); - - Assert.IsType(result); - } - - [Fact] - public async Task UserController_DeleteCurrentUser_ReturnsSuccessMessage() - { - A.CallTo(() => _userRepository.DeleteCurrentUserAsync()).Returns(Task.CompletedTask); - - IActionResult result = await _userController.DeleteCurrentUser(); - - OkObjectResult okResult = Assert.IsType(result); - Assert.Equal("User deleted", okResult.Value); - } - - [Fact] - public async Task UserController_DeleteCurrentUser_CallsRepositoryMethod() - { - A.CallTo(() => _userRepository.DeleteCurrentUserAsync()).Returns(Task.CompletedTask); - - await _userController.DeleteCurrentUser(); - - A.CallTo(() => _userRepository.DeleteCurrentUserAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Repositories/AuthRepositoryTest.cs b/Backend/prepAIred.Tests/Repositories/AuthRepositoryTest.cs deleted file mode 100644 index 5654abc..0000000 --- a/Backend/prepAIred.Tests/Repositories/AuthRepositoryTest.cs +++ /dev/null @@ -1,283 +0,0 @@ -using FakeItEasy; -using prepAIred.Data; -using prepAIred.Exceptions; -using prepAIred.Services; - -namespace prepAIred.Tests.Repositories -{ - public class AuthRepositoryTest - { - private readonly IAuthService _authService; - private readonly IUserService _userService; - private readonly AuthRepository _authRepository; - - public AuthRepositoryTest() - { - _authService = A.Fake(); - _userService = A.Fake(); - - _authRepository = new AuthRepository(_authService, _userService); - } - - #region Register Tests - - [Fact] - public async Task AuthRepository_RegisterAsync_CallsValidateUser() - { - UserCredentialsDTO userCredentials = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - byte[] hashedPassword = new byte[] { 1, 2, 3 }; - byte[] saltPassword = new byte[] { 4, 5, 6 }; - CurrentUserDTO currentUser = new CurrentUserDTO(); - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); - A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.RegisterAsync(userCredentials); - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_RegisterAsync_CallsHashPassword() - { - UserCredentialsDTO userCredentials = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - byte[] hashedPassword = new byte[] { 1, 2, 3 }; - byte[] saltPassword = new byte[] { 4, 5, 6 }; - CurrentUserDTO currentUser = new CurrentUserDTO(); - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); - A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.RegisterAsync(userCredentials); - - A.CallTo(() => _userService.HashPassword(userCredentials)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_RegisterAsync_CallsAuthServiceRegister_WithCorrectParameters() - { - UserCredentialsDTO userCredentials = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - byte[] hashedPassword = new byte[] { 1, 2, 3 }; - byte[] saltPassword = new byte[] { 4, 5, 6 }; - CurrentUserDTO currentUser = new CurrentUserDTO(); - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); - A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.RegisterAsync(userCredentials); - - A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_RegisterAsync_CallsGenerateAuthResponse() - { - UserCredentialsDTO userCredentials = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - byte[] hashedPassword = new byte[] { 1, 2, 3 }; - byte[] saltPassword = new byte[] { 4, 5, 6 }; - CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); - A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.RegisterAsync(userCredentials); - - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_RegisterAsync_ThrowsException_WhenValidationFails() - { - UserCredentialsDTO userCredentials = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "invalid-email", - Password = "weak" - }; - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)) - .ThrowsAsync(new InvalidCredentialsException("Invalid credentials")); - - await Assert.ThrowsAsync(() => - _authRepository.RegisterAsync(userCredentials)); - - A.CallTo(() => _userService.HashPassword(A._)).MustNotHaveHappened(); - A.CallTo(() => _authService.RegisterAsync(A._, A._, A._)).MustNotHaveHappened(); - } - - [Fact] - public async Task AuthRepository_RegisterAsync_ExecutesInCorrectOrder() - { - UserCredentialsDTO userCredentials = new UserCredentialsDTO() - { - Username = "JohnDoe", - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - byte[] hashedPassword = new byte[] { 1, 2, 3 }; - byte[] saltPassword = new byte[] { 4, 5, 6 }; - CurrentUserDTO currentUser = new CurrentUserDTO(); - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); - A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.RegisterAsync(userCredentials); - - A.CallTo(() => _userService.ValidateUserAsync(userCredentials)).MustHaveHappened() - .Then(A.CallTo(() => _userService.HashPassword(userCredentials)).MustHaveHappened()) - .Then(A.CallTo(() => _authService.RegisterAsync(userCredentials, hashedPassword, saltPassword)).MustHaveHappened()) - .Then(A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).MustHaveHappened()); - } - - #endregion - - #region Login Tests - - [Fact] - public async Task AuthRepository_LoginAsync_CallsAuthServiceLogin() - { - LoginDTO loginDto = new LoginDTO() - { - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; - - A.CallTo(() => _authService.LoginAsync(loginDto)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.LoginAsync(loginDto); - - A.CallTo(() => _authService.LoginAsync(loginDto)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_LoginAsync_CallsGenerateAuthResponse() - { - LoginDTO loginDto = new LoginDTO() - { - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; - - A.CallTo(() => _authService.LoginAsync(loginDto)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.LoginAsync(loginDto); - - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_LoginAsync_ThrowsException_WhenAuthenticationFails() - { - LoginDTO loginDto = new LoginDTO() - { - Email = "johndoe@gmail.com", - Password = "WrongPassword" - }; - - A.CallTo(() => _authService.LoginAsync(loginDto)) - .ThrowsAsync(new InvalidCredentialsException("Invalid credentials")); - - await Assert.ThrowsAsync(() => - _authRepository.LoginAsync(loginDto)); - - A.CallTo(() => _authService.GenerateAuthResponseAsync(A._)).MustNotHaveHappened(); - } - - [Fact] - public async Task AuthRepository_LoginAsync_ExecutesInCorrectOrder() - { - LoginDTO loginDto = new LoginDTO() - { - Email = "johndoe@gmail.com", - Password = "StrongP@ssw0rd" - }; - - CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; - - A.CallTo(() => _authService.LoginAsync(loginDto)).Returns(currentUser); - A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); - - await _authRepository.LoginAsync(loginDto); - - A.CallTo(() => _authService.LoginAsync(loginDto)).MustHaveHappened() - .Then(A.CallTo(() => _authService.GenerateAuthResponseAsync(currentUser)).MustHaveHappened()); - } - - #endregion - - #region Logout Tests - - [Fact] - public async Task AuthRepository_LogoutAsync_CallsAuthServiceLogout() - { - A.CallTo(() => _authService.LogoutAsync()).Returns(Task.CompletedTask); - - await _authRepository.LogoutAsync(); - - A.CallTo(() => _authService.LogoutAsync()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task AuthRepository_LogoutAsync_CompletesSuccessfully() - { - A.CallTo(() => _authService.LogoutAsync()).Returns(Task.CompletedTask); - - Task act() => _authRepository.LogoutAsync(); - - await act(); - } - - [Fact] - public async Task AuthRepository_LogoutAsync_ThrowsException_WhenAuthServiceFails() - { - A.CallTo(() => _authService.LogoutAsync()) - .ThrowsAsync(new InvalidOperationException("Logout failed")); - - await Assert.ThrowsAsync(() => - _authRepository.LogoutAsync()); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Services/FileServiceTest.cs b/Backend/prepAIred.Tests/Repositories/FileServiceTest.cs similarity index 99% rename from Backend/prepAIred.Tests/Services/FileServiceTest.cs rename to Backend/prepAIred.Tests/Repositories/FileServiceTest.cs index e7a17d4..53f5323 100644 --- a/Backend/prepAIred.Tests/Services/FileServiceTest.cs +++ b/Backend/prepAIred.Tests/Repositories/FileServiceTest.cs @@ -4,7 +4,7 @@ using prepAIred.Exceptions; using prepAIred.Services; -namespace prepAIred.Tests.Services +namespace prepAIred.Tests.Repositories { public class FileServiceTest { diff --git a/Backend/prepAIred.Tests/Repositories/InterviewSessionRepositoryTest.cs b/Backend/prepAIred.Tests/Repositories/InterviewSessionRepositoryTest.cs deleted file mode 100644 index 4798549..0000000 --- a/Backend/prepAIred.Tests/Repositories/InterviewSessionRepositoryTest.cs +++ /dev/null @@ -1,413 +0,0 @@ -using FakeItEasy; -using prepAIred.Data; -using prepAIred.Services; - -namespace prepAIred.Tests.Repositories -{ - public class InterviewSessionRepositoryTest - { - private readonly IInterviewSessionService _interviewSessionService; - private readonly IUserService _userService; - private readonly InterviewSessionRepository _interviewSessionRepository; - - public InterviewSessionRepositoryTest() - { - _interviewSessionService = A.Fake(); - _userService = A.Fake(); - - _interviewSessionRepository = new InterviewSessionRepository(_interviewSessionService, _userService); - } - - #region GetInterviewSessionDTOsAsync Tests - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionDTOsAsync_ReturnsConvertedDTOs() - { - int userId = 1; - List sessions = new List - { - new InterviewSession { ID = 1, Subject = "C# Basics", UserID = userId }, - new InterviewSession { ID = 2, Subject = "Python Advanced", UserID = userId } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); - - List result = await _interviewSessionRepository.GetInterviewSessionDTOsAsync(); - - Assert.NotNull(result); - Assert.Equal(2, result.Count); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionDTOsAsync_CallsGetCurrentUserID() - { - int userId = 1; - List sessions = new List(); - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); - - await _interviewSessionRepository.GetInterviewSessionDTOsAsync(); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionDTOsAsync_CallsGetInterviewSessionsByUserIdAsync() - { - int userId = 1; - List sessions = new List(); - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); - - await _interviewSessionRepository.GetInterviewSessionDTOsAsync(); - - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionActivitiesAsync Tests - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionActivitiesAsync_ReturnsActivities() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { ID = 1, Subject = "OOP", AverageScore = 8.5f }, - new InterviewSessionActivityDTO { ID = 2, Subject = "Algorithms", AverageScore = 7.0f } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionActivitiesAsync(); - - Assert.Equal(2, result.Count); - Assert.Equal("OOP", result[0].Subject); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionActivitiesAsync_CallsServiceMethod() - { - int userId = 1; - List activities = new List(); - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - await _interviewSessionRepository.GetInterviewSessionActivitiesAsync(); - - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionStatisticsAsync Tests - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionStatisticsAsync_ReturnsStatistics() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetTotalInterviewSessionsAsync(userId)).Returns(10); - A.CallTo(() => _interviewSessionService.GetPassedInterviewSessionsAsync(userId)).Returns(7); - A.CallTo(() => _interviewSessionService.GetOngoingInterviewSessionsAsync(userId)).Returns(2); - A.CallTo(() => _interviewSessionService.GetAverageScoreAsync(userId)).Returns(8.5m); - A.CallTo(() => _interviewSessionService.GetCompletionRateAsync(userId)).Returns(70.0m); - - ProfileStatisticsDTO result = await _interviewSessionRepository.GetInterviewSessionStatisticsAsync(); - - Assert.Equal(10, result.TotalInterviewSessions); - Assert.Equal(7, result.PassedInterviewSessions); - Assert.Equal(2, result.OngoingInterviewSessions); - Assert.Equal(8.5m, result.AverageScore); - Assert.Equal(70.0m, result.CompletionRate); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionStatisticsAsync_CallsAllStatisticMethods() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetTotalInterviewSessionsAsync(userId)).Returns(10); - A.CallTo(() => _interviewSessionService.GetPassedInterviewSessionsAsync(userId)).Returns(7); - A.CallTo(() => _interviewSessionService.GetOngoingInterviewSessionsAsync(userId)).Returns(2); - A.CallTo(() => _interviewSessionService.GetAverageScoreAsync(userId)).Returns(8.5m); - A.CallTo(() => _interviewSessionService.GetCompletionRateAsync(userId)).Returns(70.0m); - - await _interviewSessionRepository.GetInterviewSessionStatisticsAsync(); - - A.CallTo(() => _interviewSessionService.GetTotalInterviewSessionsAsync(userId)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _interviewSessionService.GetPassedInterviewSessionsAsync(userId)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _interviewSessionService.GetOngoingInterviewSessionsAsync(userId)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _interviewSessionService.GetAverageScoreAsync(userId)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _interviewSessionService.GetCompletionRateAsync(userId)).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region GetInterviewSessionPerformanceAsync Tests - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionPerformanceAsync_ReturnsOrderedPerformanceData() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { ID = 2, DateCreated = new DateTime(2024, 1, 15), AverageScore = 9.0f }, - new InterviewSessionActivityDTO { ID = 1, DateCreated = new DateTime(2024, 1, 10), AverageScore = 8.0f } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionPerformanceAsync(); - - Assert.Equal(2, result.Count); - Assert.Equal(1, result[0].ID); - Assert.Equal(8.0f, result[0].Score); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionPerformanceAsync_OrdersByDateCreated() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { ID = 3, DateCreated = new DateTime(2024, 1, 20), AverageScore = 7.5f }, - new InterviewSessionActivityDTO { ID = 1, DateCreated = new DateTime(2024, 1, 10), AverageScore = 8.0f }, - new InterviewSessionActivityDTO { ID = 2, DateCreated = new DateTime(2024, 1, 15), AverageScore = 9.0f } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionPerformanceAsync(); - - Assert.Equal(1, result[0].ID); - Assert.Equal(2, result[1].ID); - Assert.Equal(3, result[2].ID); - } - - #endregion - - #region GetInterviewSessionProgrammingLanguageDataAsync Tests - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionProgrammingLanguageDataAsync_GroupsByLanguage() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { ProgrammingLanguage = "C#" }, - new InterviewSessionActivityDTO { ProgrammingLanguage = "C#" }, - new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync(); - - Assert.Equal(2, result.Count); - ProgrammingLanguageDataDTO csharpData = result.FirstOrDefault(x => x.Language == "C#"); - Assert.NotNull(csharpData); - Assert.Equal(2, csharpData.Sessions); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionProgrammingLanguageDataAsync_CountsCorrectly() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { ProgrammingLanguage = "Java" }, - new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" }, - new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" }, - new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionProgrammingLanguageDataAsync(); - - ProgrammingLanguageDataDTO pythonData = result.FirstOrDefault(x => x.Language == "Python"); - Assert.NotNull(pythonData); - Assert.Equal(3, pythonData.Sessions); - } - - #endregion - - #region GetInterviewSessionPositionDataAsync Tests - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionPositionDataAsync_GroupsByPosition() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { Position = "Junior Developer" }, - new InterviewSessionActivityDTO { Position = "Junior Developer" }, - new InterviewSessionActivityDTO { Position = "Senior Developer" } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionPositionDataAsync(); - - Assert.Equal(2, result.Count); - PositionDataDTO juniorData = result.FirstOrDefault(x => x.Position == "Junior Developer"); - Assert.NotNull(juniorData); - Assert.Equal(2, juniorData.Sessions); - } - - [Fact] - public async Task InterviewSessionRepository_GetInterviewSessionPositionDataAsync_CountsCorrectly() - { - int userId = 1; - List activities = new List - { - new InterviewSessionActivityDTO { Position = "Lead Developer" }, - new InterviewSessionActivityDTO { Position = "Mid Developer" }, - new InterviewSessionActivityDTO { Position = "Mid Developer" }, - new InterviewSessionActivityDTO { Position = "Mid Developer" } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); - - List result = await _interviewSessionRepository.GetInterviewSessionPositionDataAsync(); - - PositionDataDTO midData = result.FirstOrDefault(x => x.Position == "Mid Developer"); - Assert.NotNull(midData); - Assert.Equal(3, midData.Sessions); - } - - #endregion - - #region FinishInterviewSessionAsync Tests - - [Fact] - public async Task InterviewSessionRepository_FinishInterviewSessionAsync_GetsLatestSession() - { - int userId = 1; - int sessionId = 5; - InterviewSession session = new InterviewSession { ID = sessionId }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns(session); - A.CallTo(() => _interviewSessionService.FinishInterviewSessionAsync(session)).Returns(Task.CompletedTask); - - await _interviewSessionRepository.FinishInterviewSessionAsync(); - - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task InterviewSessionRepository_FinishInterviewSessionAsync_CallsFinishOnService() - { - int userId = 1; - int sessionId = 5; - InterviewSession session = new InterviewSession { ID = sessionId }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns(session); - A.CallTo(() => _interviewSessionService.FinishInterviewSessionAsync(session)).Returns(Task.CompletedTask); - - await _interviewSessionRepository.FinishInterviewSessionAsync(); - - A.CallTo(() => _interviewSessionService.FinishInterviewSessionAsync(session)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task InterviewSessionRepository_FinishInterviewSessionAsync_ExecutesInCorrectOrder() - { - int userId = 1; - int sessionId = 5; - InterviewSession session = new InterviewSession { ID = sessionId }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns(session); - A.CallTo(() => _interviewSessionService.FinishInterviewSessionAsync(session)).Returns(Task.CompletedTask); - - await _interviewSessionRepository.FinishInterviewSessionAsync(); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened() - .Then(A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).MustHaveHappened()) - .Then(A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).MustHaveHappened()) - .Then(A.CallTo(() => _interviewSessionService.FinishInterviewSessionAsync(session)).MustHaveHappened()); - } - - #endregion - - #region DeleteInterviewSessionsAsync Tests - - [Fact] - public async Task InterviewSessionRepository_DeleteInterviewSessionsAsync_GetsUserSessions() - { - int userId = 1; - List sessions = new List - { - new InterviewSession { ID = 1 }, - new InterviewSession { ID = 2 } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); - A.CallTo(() => _interviewSessionService.DeleteInterviewSessionsAsync(sessions)).Returns(Task.CompletedTask); - - await _interviewSessionRepository.DeleteInterviewSessionsAsync(); - - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task InterviewSessionRepository_DeleteInterviewSessionsAsync_CallsDeleteOnService() - { - int userId = 1; - List sessions = new List - { - new InterviewSession { ID = 1 }, - new InterviewSession { ID = 2 } - }; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); - A.CallTo(() => _interviewSessionService.DeleteInterviewSessionsAsync(sessions)).Returns(Task.CompletedTask); - - await _interviewSessionRepository.DeleteInterviewSessionsAsync(); - - A.CallTo(() => _interviewSessionService.DeleteInterviewSessionsAsync(sessions)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task InterviewSessionRepository_DeleteInterviewSessionsAsync_ExecutesInCorrectOrder() - { - int userId = 1; - List sessions = new List(); - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); - A.CallTo(() => _interviewSessionService.DeleteInterviewSessionsAsync(sessions)).Returns(Task.CompletedTask); - - await _interviewSessionRepository.DeleteInterviewSessionsAsync(); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened() - .Then(A.CallTo(() => _interviewSessionService.GetInterviewSessionsByUserIdAsync(userId)).MustHaveHappened()) - .Then(A.CallTo(() => _interviewSessionService.DeleteInterviewSessionsAsync(sessions)).MustHaveHappened()); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Repositories/ProfilePictureRepositoryTest.cs b/Backend/prepAIred.Tests/Repositories/ProfilePictureRepositoryTest.cs index 4919b8e..a6f6934 100644 --- a/Backend/prepAIred.Tests/Repositories/ProfilePictureRepositoryTest.cs +++ b/Backend/prepAIred.Tests/Repositories/ProfilePictureRepositoryTest.cs @@ -1,305 +1,147 @@ using FakeItEasy; using Microsoft.AspNetCore.Http; -using prepAIred.Data; +using prepAIred.Exceptions; using prepAIred.Services; namespace prepAIred.Tests.Repositories { public class ProfilePictureRepositoryTest { - private readonly IProfilePictureService _profilePictureService; - private readonly IUserService _userService; - private readonly ProfilePictureRepository _profilePictureRepository; + private readonly IFileService _fileService; public ProfilePictureRepositoryTest() { - _profilePictureService = A.Fake(); - _userService = A.Fake(); - - _profilePictureRepository = new ProfilePictureRepository(_profilePictureService, _userService); + _fileService = A.Fake(); } - #region GetProfilePictureUrlAsync Tests - - [Fact] - public async Task ProfilePictureRepository_GetProfilePictureUrlAsync_ReturnsCorrectUrl() - { - int userId = 1; - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _profilePictureService.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); - - string result = await _profilePictureRepository.GetProfilePictureUrlAsync(); - - Assert.Equal(expectedUrl, result); - } + #region SaveFileAsync Tests [Fact] - public async Task ProfilePictureRepository_GetProfilePictureUrlAsync_CallsGetCurrentUserID() + public async Task ProfilePictureRepository_SaveFileAsync_ThrowsException_WhenFileIsNull() { - int userId = 1; - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _profilePictureService.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); - - await _profilePictureRepository.GetProfilePictureUrlAsync(); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappenedOnceExactly(); + await Assert.ThrowsAsync(() => + TestSaveFileAsync(null)); } [Fact] - public async Task ProfilePictureRepository_GetProfilePictureUrlAsync_CallsGetProfilePictureUrlByUserIdAsync() + public async Task ProfilePictureRepository_SaveFileAsync_ThrowsException_WhenFileLengthIsZero() { - int userId = 1; - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _profilePictureService.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); - - await _profilePictureRepository.GetProfilePictureUrlAsync(); + IFormFile imageFile = A.Fake(); + A.CallTo(() => imageFile.Length).Returns(0); - A.CallTo(() => _profilePictureService.GetProfilePictureUrlByUserIdAsync(userId)).MustHaveHappenedOnceExactly(); + await Assert.ThrowsAsync(() => + TestSaveFileAsync(imageFile)); } [Fact] - public async Task ProfilePictureRepository_GetProfilePictureUrlAsync_ExecutesInCorrectOrder() + public async Task ProfilePictureRepository_SaveFileAsync_ThrowsException_WithCorrectMessage() { - int userId = 1; - string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _profilePictureService.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); + IFormFile imageFile = A.Fake(); + A.CallTo(() => imageFile.Length).Returns(0); - await _profilePictureRepository.GetProfilePictureUrlAsync(); + ProfilePictureException exception = await Assert.ThrowsAsync(() => + TestSaveFileAsync(imageFile)); - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened() - .Then(A.CallTo(() => _profilePictureService.GetProfilePictureUrlByUserIdAsync(userId)).MustHaveHappened()); + Assert.Equal("File is null or empty", exception.Message); } - #endregion - - #region ChangeProfilePictureAsync Tests - [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_SavesNewFile() + public async Task ProfilePictureRepository_SaveFileAsync_CallsCreateDirectoryIfNotExists() { IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string fileName = "new-profile.jpg"; - int userId = 1; - - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = string.Empty - }; + A.CallTo(() => imageFile.Length).Returns(100); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns("/path/to/uploads"); + A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(".jpg"); + A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, A._, A._)).Returns("file.jpg"); - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(fileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + await TestSaveFileAsync(imageFile); - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_GetsCurrentUser() + public async Task ProfilePictureRepository_SaveFileAsync_CallsCheckFileExtension() { IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string fileName = "new-profile.jpg"; - int userId = 1; - - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = string.Empty - }; + A.CallTo(() => imageFile.Length).Returns(100); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns("/path"); + A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(".jpg"); + A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, A._, A._)).Returns("file.jpg"); - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(fileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + await TestSaveFileAsync(imageFile); - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _fileService.CheckFileExtension(imageFile)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_DeletesOldPicture_WhenExists() + public async Task ProfilePictureRepository_SaveFileAsync_CallsCreateFileNameAsync_WithCorrectParameters() { IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string oldFileName = "old-profile.jpg"; - string newFileName = "new-profile.jpg"; - int userId = 1; - - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = oldFileName - }; + string path = "/uploads/path"; + string extension = ".png"; - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(newFileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _profilePictureService.DeleteProfilePictureAsync(oldFileName)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + A.CallTo(() => imageFile.Length).Returns(100); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns(path); + A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(extension); + A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).Returns("unique-name.png"); - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); + await TestSaveFileAsync(imageFile); - A.CallTo(() => _profilePictureService.DeleteProfilePictureAsync(oldFileName)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_DoesNotDeleteOldPicture_WhenNotExists() + public async Task ProfilePictureRepository_SaveFileAsync_ReturnsFileName() { IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string newFileName = "new-profile.jpg"; - int userId = 1; + string expectedFileName = "generated-guid.jpg"; - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = string.Empty - }; - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(newFileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + A.CallTo(() => imageFile.Length).Returns(100); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns("/path"); + A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(".jpg"); + A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, A._, A._)).Returns(expectedFileName); - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); + string result = await TestSaveFileAsync(imageFile); - A.CallTo(() => _profilePictureService.DeleteProfilePictureAsync(A._)).MustNotHaveHappened(); + Assert.Equal(expectedFileName, result); } [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_UpdatesUserProfilePicture() + public async Task ProfilePictureRepository_SaveFileAsync_ExecutesInCorrectOrder() { IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string newFileName = "new-profile.jpg"; - int userId = 1; - - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = string.Empty - }; + string path = "/uploads"; + string extension = ".jpg"; + string fileName = "file.jpg"; - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(newFileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + A.CallTo(() => imageFile.Length).Returns(100); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns(path); + A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(extension); + A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).Returns(fileName); - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); + await TestSaveFileAsync(imageFile); - Assert.Equal(newFileName, currentUser.ProfilePicture); + A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).MustHaveHappened() + .Then(A.CallTo(() => _fileService.CheckFileExtension(imageFile)).MustHaveHappened()) + .Then(A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).MustHaveHappened()); } - [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_CallsUpdateUserAsync() - { - IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string newFileName = "new-profile.jpg"; - int userId = 1; - - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = string.Empty - }; - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(newFileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); + #endregion - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).MustHaveHappenedOnceExactly(); - } + #region Helper Methods - [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_ExecutesInCorrectOrder_WithoutOldPicture() + private async Task TestSaveFileAsync(IFormFile imageFile) { - IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string newFileName = "new-profile.jpg"; - int userId = 1; - - User currentUser = new User + if (imageFile is null || imageFile.Length == 0) { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = string.Empty - }; - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(newFileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).MustHaveHappened() - .Then(A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened()) - .Then(A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappened()) - .Then(A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).MustHaveHappened()); - } + throw new ProfilePictureException("File is null or empty"); + } - [Fact] - public async Task ProfilePictureRepository_ChangeProfilePictureAsync_ExecutesInCorrectOrder_WithOldPicture() - { - IFormFile imageFile = A.Fake(); - ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; - string oldFileName = "old-profile.jpg"; - string newFileName = "new-profile.jpg"; - int userId = 1; + string path = _fileService.CreateDirectoryIfNotExists(); + string extension = _fileService.CheckFileExtension(imageFile); + string fileName = await _fileService.CreateFileNameAsync(imageFile, path, extension); - User currentUser = new User - { - ID = userId, - Username = "JohnDoe", - Email = "john@example.com", - ProfilePicture = oldFileName - }; - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).Returns(newFileName); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _profilePictureService.DeleteProfilePictureAsync(oldFileName)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - - await _profilePictureRepository.ChangeProfilePictureAsync(profilePictureDto); - - A.CallTo(() => _profilePictureService.SaveFileAsync(imageFile)).MustHaveHappened() - .Then(A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened()) - .Then(A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappened()) - .Then(A.CallTo(() => _profilePictureService.DeleteProfilePictureAsync(oldFileName)).MustHaveHappened()) - .Then(A.CallTo(() => _userService.UpdateUserAsync(currentUser, null)).MustHaveHappened()); + return fileName; } #endregion diff --git a/Backend/prepAIred.Tests/Repositories/UserRepositoryTest.cs b/Backend/prepAIred.Tests/Repositories/UserRepositoryTest.cs deleted file mode 100644 index ea8f5f5..0000000 --- a/Backend/prepAIred.Tests/Repositories/UserRepositoryTest.cs +++ /dev/null @@ -1,260 +0,0 @@ -using FakeItEasy; -using prepAIred.Data; -using prepAIred.Services; - -namespace prepAIred.Tests.Repositories -{ - public class UserRepositoryTest - { - private readonly IUserService _userService; - private readonly ICookieService _cookieService; - private readonly UserRepository _userRepository; - - public UserRepositoryTest() - { - _userService = A.Fake(); - _cookieService = A.Fake(); - - _userRepository = new UserRepository(_userService, _cookieService); - } - - #region GetCurrentUserAsync Tests - - [Fact] - public async Task UserRepository_GetCurrentUserAsync_ReturnsCurrentUser() - { - CurrentUserDTO expectedUser = new CurrentUserDTO - { - ID = 1, - Username = "JohnDoe", - Email = "john@example.com" - }; - - A.CallTo(() => _userService.GetCurrentUserAsync()).Returns(expectedUser); - - CurrentUserDTO result = await _userRepository.GetCurrentUserAsync(); - - Assert.Equal(expectedUser.ID, result.ID); - Assert.Equal(expectedUser.Username, result.Username); - Assert.Equal(expectedUser.Email, result.Email); - } - - [Fact] - public async Task UserRepository_GetCurrentUserAsync_CallsUserService() - { - CurrentUserDTO expectedUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; - - A.CallTo(() => _userService.GetCurrentUserAsync()).Returns(expectedUser); - - await _userRepository.GetCurrentUserAsync(); - - A.CallTo(() => _userService.GetCurrentUserAsync()).MustHaveHappenedOnceExactly(); - } - - #endregion - - #region UpdateCurrentUserAsync Tests - - [Fact] - public async Task UserRepository_UpdateCurrentUserAsync_ValidatesUserData() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - int userId = 1; - User currentUser = new User - { - ID = userId, - Username = "OldName", - Email = "old@example.com" - }; - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); - - await _userRepository.UpdateCurrentUserAsync(userCredentialsDto); - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_UpdateCurrentUserAsync_GetsCurrentUserID() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - int userId = 1; - User currentUser = new User { ID = userId }; - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); - - await _userRepository.UpdateCurrentUserAsync(userCredentialsDto); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_UpdateCurrentUserAsync_GetsCurrentUserEntity() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - int userId = 1; - User currentUser = new User { ID = userId }; - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); - - await _userRepository.UpdateCurrentUserAsync(userCredentialsDto); - - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_UpdateCurrentUserAsync_CallsUpdateUserAsync() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - int userId = 1; - User currentUser = new User - { - ID = userId, - Username = "OldName", - Email = "old@example.com" - }; - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); - - await _userRepository.UpdateCurrentUserAsync(userCredentialsDto); - - A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_UpdateCurrentUserAsync_ExecutesInCorrectOrder() - { - UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO - { - Username = "UpdatedName", - Email = "updated@example.com", - Password = "NewP@ssw0rd" - }; - - int userId = 1; - User currentUser = new User { ID = userId }; - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); - A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); - - await _userRepository.UpdateCurrentUserAsync(userCredentialsDto); - - A.CallTo(() => _userService.ValidateUpdateUserDataAsync(userCredentialsDto)).MustHaveHappened() - .Then(A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened()) - .Then(A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappened()) - .Then(A.CallTo(() => _userService.UpdateUserAsync(currentUser, userCredentialsDto)).MustHaveHappened()); - } - - #endregion - - #region DeleteCurrentUserAsync Tests - - [Fact] - public async Task UserRepository_DeleteCurrentUserAsync_DeletesAccessTokenCookie() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.DeleteUserAsync(userId)).Returns(Task.CompletedTask); - - await _userRepository.DeleteCurrentUserAsync(); - - A.CallTo(() => _cookieService.DeleteCookie("AccessToken")).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_DeleteCurrentUserAsync_DeletesRefreshTokenCookie() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.DeleteUserAsync(userId)).Returns(Task.CompletedTask); - - await _userRepository.DeleteCurrentUserAsync(); - - A.CallTo(() => _cookieService.DeleteCookie("RefreshToken")).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_DeleteCurrentUserAsync_GetsCurrentUserID() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.DeleteUserAsync(userId)).Returns(Task.CompletedTask); - - await _userRepository.DeleteCurrentUserAsync(); - - A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_DeleteCurrentUserAsync_DeletesUser() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.DeleteUserAsync(userId)).Returns(Task.CompletedTask); - - await _userRepository.DeleteCurrentUserAsync(); - - A.CallTo(() => _userService.DeleteUserAsync(userId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task UserRepository_DeleteCurrentUserAsync_ExecutesInCorrectOrder() - { - int userId = 1; - - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.DeleteUserAsync(userId)).Returns(Task.CompletedTask); - - await _userRepository.DeleteCurrentUserAsync(); - - A.CallTo(() => _cookieService.DeleteCookie("AccessToken")).MustHaveHappened() - .Then(A.CallTo(() => _cookieService.DeleteCookie("RefreshToken")).MustHaveHappened()) - .Then(A.CallTo(() => _userService.GetCurrentUserID()).MustHaveHappened()) - .Then(A.CallTo(() => _userService.DeleteUserAsync(userId)).MustHaveHappened()); - } - - #endregion - } -} diff --git a/Backend/prepAIred.Tests/Services/AuthServiceTest.cs b/Backend/prepAIred.Tests/Services/AuthServiceTest.cs new file mode 100644 index 0000000..4a50d03 --- /dev/null +++ b/Backend/prepAIred.Tests/Services/AuthServiceTest.cs @@ -0,0 +1,283 @@ +using FakeItEasy; +using prepAIred.Data; +using prepAIred.Exceptions; +using prepAIred.Services; + +namespace prepAIred.Tests.Services +{ + public class AuthServiceTest + { + private readonly IAuthRepository _authRepository; + private readonly IUserRepository _userRepository; + private readonly AuthService _authService; + + public AuthServiceTest() + { + _authRepository = A.Fake(); + _userRepository = A.Fake(); + + _authService = new AuthService(_authRepository, _userRepository); + } + + #region Register Tests + + [Fact] + public async Task AuthService_RegisterAsync_CallsValidateUser() + { + UserCredentialsDTO userCredentials = new UserCredentialsDTO() + { + Username = "JohnDoe", + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + byte[] hashedPassword = new byte[] { 1, 2, 3 }; + byte[] saltPassword = new byte[] { 4, 5, 6 }; + CurrentUserDTO currentUser = new CurrentUserDTO(); + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); + A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.RegisterAsync(userCredentials); + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_RegisterAsync_CallsHashPassword() + { + UserCredentialsDTO userCredentials = new UserCredentialsDTO() + { + Username = "JohnDoe", + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + byte[] hashedPassword = new byte[] { 1, 2, 3 }; + byte[] saltPassword = new byte[] { 4, 5, 6 }; + CurrentUserDTO currentUser = new CurrentUserDTO(); + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); + A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.RegisterAsync(userCredentials); + + A.CallTo(() => _userRepository.HashPassword(userCredentials)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_RegisterAsync_CallsAuthServiceRegister_WithCorrectParameters() + { + UserCredentialsDTO userCredentials = new UserCredentialsDTO() + { + Username = "JohnDoe", + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + byte[] hashedPassword = new byte[] { 1, 2, 3 }; + byte[] saltPassword = new byte[] { 4, 5, 6 }; + CurrentUserDTO currentUser = new CurrentUserDTO(); + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); + A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.RegisterAsync(userCredentials); + + A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_RegisterAsync_CallsGenerateAuthResponse() + { + UserCredentialsDTO userCredentials = new UserCredentialsDTO() + { + Username = "JohnDoe", + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + byte[] hashedPassword = new byte[] { 1, 2, 3 }; + byte[] saltPassword = new byte[] { 4, 5, 6 }; + CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); + A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.RegisterAsync(userCredentials); + + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_RegisterAsync_ThrowsException_WhenValidationFails() + { + UserCredentialsDTO userCredentials = new UserCredentialsDTO() + { + Username = "JohnDoe", + Email = "invalid-email", + Password = "weak" + }; + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)) + .ThrowsAsync(new InvalidCredentialsException("Invalid credentials")); + + await Assert.ThrowsAsync(() => + _authService.RegisterAsync(userCredentials)); + + A.CallTo(() => _userRepository.HashPassword(A._)).MustNotHaveHappened(); + A.CallTo(() => _authRepository.RegisterAsync(A._, A._, A._)).MustNotHaveHappened(); + } + + [Fact] + public async Task AuthService_RegisterAsync_ExecutesInCorrectOrder() + { + UserCredentialsDTO userCredentials = new UserCredentialsDTO() + { + Username = "JohnDoe", + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + byte[] hashedPassword = new byte[] { 1, 2, 3 }; + byte[] saltPassword = new byte[] { 4, 5, 6 }; + CurrentUserDTO currentUser = new CurrentUserDTO(); + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.HashPassword(userCredentials)).Returns((hashedPassword, saltPassword)); + A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.RegisterAsync(userCredentials); + + A.CallTo(() => _userRepository.ValidateUserAsync(userCredentials)).MustHaveHappened() + .Then(A.CallTo(() => _userRepository.HashPassword(userCredentials)).MustHaveHappened()) + .Then(A.CallTo(() => _authRepository.RegisterAsync(userCredentials, hashedPassword, saltPassword)).MustHaveHappened()) + .Then(A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).MustHaveHappened()); + } + + #endregion + + #region Login Tests + + [Fact] + public async Task AuthService_LoginAsync_CallsAuthServiceLogin() + { + LoginDTO loginDto = new LoginDTO() + { + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; + + A.CallTo(() => _authRepository.LoginAsync(loginDto)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.LoginAsync(loginDto); + + A.CallTo(() => _authRepository.LoginAsync(loginDto)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_LoginAsync_CallsGenerateAuthResponse() + { + LoginDTO loginDto = new LoginDTO() + { + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; + + A.CallTo(() => _authRepository.LoginAsync(loginDto)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.LoginAsync(loginDto); + + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_LoginAsync_ThrowsException_WhenAuthenticationFails() + { + LoginDTO loginDto = new LoginDTO() + { + Email = "johndoe@gmail.com", + Password = "WrongPassword" + }; + + A.CallTo(() => _authRepository.LoginAsync(loginDto)) + .ThrowsAsync(new InvalidCredentialsException("Invalid credentials")); + + await Assert.ThrowsAsync(() => + _authService.LoginAsync(loginDto)); + + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(A._)).MustNotHaveHappened(); + } + + [Fact] + public async Task AuthService_LoginAsync_ExecutesInCorrectOrder() + { + LoginDTO loginDto = new LoginDTO() + { + Email = "johndoe@gmail.com", + Password = "StrongP@ssw0rd" + }; + + CurrentUserDTO currentUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; + + A.CallTo(() => _authRepository.LoginAsync(loginDto)).Returns(currentUser); + A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).Returns(Task.CompletedTask); + + await _authService.LoginAsync(loginDto); + + A.CallTo(() => _authRepository.LoginAsync(loginDto)).MustHaveHappened() + .Then(A.CallTo(() => _authRepository.GenerateAuthResponseAsync(currentUser)).MustHaveHappened()); + } + + #endregion + + #region Logout Tests + + [Fact] + public async Task AuthService_LogoutAsync_CallsAuthServiceLogout() + { + A.CallTo(() => _authRepository.LogoutAsync()).Returns(Task.CompletedTask); + + await _authService.LogoutAsync(); + + A.CallTo(() => _authRepository.LogoutAsync()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task AuthService_LogoutAsync_CompletesSuccessfully() + { + A.CallTo(() => _authRepository.LogoutAsync()).Returns(Task.CompletedTask); + + Task act() => _authService.LogoutAsync(); + + await act(); + } + + [Fact] + public async Task AuthService_LogoutAsync_ThrowsException_WhenAuthServiceFails() + { + A.CallTo(() => _authRepository.LogoutAsync()) + .ThrowsAsync(new InvalidOperationException("Logout failed")); + + await Assert.ThrowsAsync(() => + _authService.LogoutAsync()); + } + + #endregion + } +} diff --git a/Backend/prepAIred.Tests/Repositories/InterviewRepositoryTest.cs b/Backend/prepAIred.Tests/Services/InterviewServiceTest.cs similarity index 52% rename from Backend/prepAIred.Tests/Repositories/InterviewRepositoryTest.cs rename to Backend/prepAIred.Tests/Services/InterviewServiceTest.cs index ee4fc46..91e0c9f 100644 --- a/Backend/prepAIred.Tests/Repositories/InterviewRepositoryTest.cs +++ b/Backend/prepAIred.Tests/Services/InterviewServiceTest.cs @@ -2,32 +2,32 @@ using prepAIred.Data; using prepAIred.Services; -namespace prepAIred.Tests.Repositories +namespace prepAIred.Tests.Services { - public class InterviewRepositoryTest + public class InterviewServiceTest { private readonly IAIService _aiService; - private readonly IUserService _userService; - private readonly IInterviewService _interviewService; - private readonly IInterviewSessionService _interviewSessionService; + private readonly IUserRepository _userRepository; + private readonly IInterviewRepository _interviewRepository; + private readonly IInterviewSessionRepository _interviewSessionRepository; private readonly IPromptService _promptService; private readonly ISerializationService _serializationService; - private readonly InterviewRepository _interviewRepository; + private readonly InterviewService _interviewService; - public InterviewRepositoryTest() + public InterviewServiceTest() { _aiService = A.Fake(); - _userService = A.Fake(); - _interviewService = A.Fake(); - _interviewSessionService = A.Fake(); + _userRepository = A.Fake(); + _interviewRepository = A.Fake(); + _interviewSessionRepository = A.Fake(); _promptService = A.Fake(); _serializationService = A.Fake(); - _interviewRepository = new InterviewRepository( + _interviewService = new InterviewService( _aiService, - _userService, - _interviewService, - _interviewSessionService, + _userRepository, + _interviewRepository, + _interviewSessionRepository, _promptService, _serializationService ); @@ -36,7 +36,7 @@ public InterviewRepositoryTest() #region GenerateInterviewsAsync - HR Tests [Fact] - public async Task InterviewRepository_GenerateInterviewsAsync_CreatesHrInterview() + public async Task InterviewService_GenerateInterviewsAsync_CreatesHrInterview() { HrRequestDTO hrRequest = new HrRequestDTO { @@ -50,37 +50,37 @@ public async Task InterviewRepository_GenerateInterviewsAsync_CreatesHrInterview string prompt = "HR interview prompt"; List interviews = new List { new HRInterview() }; - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(user); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(user); A.CallTo(() => _promptService.CreateHrPrompt(hrRequest, userId)).Returns(prompt); A.CallTo(() => _aiService.AskAiAgentAsync(AIAgent.ChatGPT, prompt)).Returns(interviews); - A.CallTo(() => _interviewSessionService.CreateInterviewSessionAsync(A._)).Returns(Task.CompletedTask); - A.CallTo(() => _interviewService.CreateInterviewsAsync(interviews, user, A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewSessionRepository.CreateInterviewSessionAsync(A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(interviews, user, A._)).Returns(Task.CompletedTask); - await _interviewRepository.GenerateInterviewsAsync(hrRequest); + await _interviewService.GenerateInterviewsAsync(hrRequest); A.CallTo(() => _promptService.CreateHrPrompt(hrRequest, userId)).MustHaveHappenedOnceExactly(); A.CallTo(() => _aiService.AskAiAgentAsync(AIAgent.ChatGPT, prompt)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_GenerateInterviewsAsync_CreatesInterviewSession_ForHrInterview() + public async Task InterviewService_GenerateInterviewsAsync_CreatesInterviewSession_ForHrInterview() { HrRequestDTO hrRequest = new HrRequestDTO { AIAgent = "ChatGPT" }; int userId = 1; User user = new User { ID = userId }; List interviews = new List(); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(user); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(user); A.CallTo(() => _promptService.CreateHrPrompt(hrRequest, userId)).Returns("prompt"); A.CallTo(() => _aiService.AskAiAgentAsync(A._, A._)).Returns(interviews); - A.CallTo(() => _interviewSessionService.CreateInterviewSessionAsync(A._)).Returns(Task.CompletedTask); - A.CallTo(() => _interviewService.CreateInterviewsAsync(A>._, A._, A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewSessionRepository.CreateInterviewSessionAsync(A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(A>._, A._, A._)).Returns(Task.CompletedTask); - await _interviewRepository.GenerateInterviewsAsync(hrRequest); + await _interviewService.GenerateInterviewsAsync(hrRequest); - A.CallTo(() => _interviewSessionService.CreateInterviewSessionAsync(A.That.Matches(s => + A.CallTo(() => _interviewSessionRepository.CreateInterviewSessionAsync(A.That.Matches(s => s.UserID == userId && s.Status == InterviewSessionStatus.Ongoing && s.AIAgent == AIAgent.ChatGPT @@ -88,23 +88,23 @@ public async Task InterviewRepository_GenerateInterviewsAsync_CreatesInterviewSe } [Fact] - public async Task InterviewRepository_GenerateInterviewsAsync_CallsCreateInterviews_ForHrInterview() + public async Task InterviewService_GenerateInterviewsAsync_CallsCreateInterviews_ForHrInterview() { HrRequestDTO hrRequest = new HrRequestDTO { AIAgent = "Gemini" }; int userId = 1; User user = new User { ID = userId }; List interviews = new List { new HRInterview() }; - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(user); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(user); A.CallTo(() => _promptService.CreateHrPrompt(hrRequest, userId)).Returns("prompt"); A.CallTo(() => _aiService.AskAiAgentAsync(A._, A._)).Returns(interviews); - A.CallTo(() => _interviewSessionService.CreateInterviewSessionAsync(A._)).Returns(Task.CompletedTask); - A.CallTo(() => _interviewService.CreateInterviewsAsync(interviews, user, A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewSessionRepository.CreateInterviewSessionAsync(A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(interviews, user, A._)).Returns(Task.CompletedTask); - await _interviewRepository.GenerateInterviewsAsync(hrRequest); + await _interviewService.GenerateInterviewsAsync(hrRequest); - A.CallTo(() => _interviewService.CreateInterviewsAsync(interviews, user, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(interviews, user, A._)).MustHaveHappenedOnceExactly(); } #endregion @@ -112,7 +112,7 @@ public async Task InterviewRepository_GenerateInterviewsAsync_CallsCreateIntervi #region GenerateInterviewsAsync - Technical Tests [Fact] - public async Task InterviewRepository_GenerateInterviewsAsync_CreatesTechnicalInterview() + public async Task InterviewService_GenerateInterviewsAsync_CreatesTechnicalInterview() { TechnicalRequestDTO techRequest = new TechnicalRequestDTO { @@ -128,22 +128,22 @@ public async Task InterviewRepository_GenerateInterviewsAsync_CreatesTechnicalIn List interviews = new List { new TechnicalInterview() }; InterviewSession existingSession = new InterviewSession { ID = 5, UserID = userId }; - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(user); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(user); A.CallTo(() => _promptService.CreateTechnicalPrompt(techRequest, userId)).Returns(prompt); A.CallTo(() => _aiService.AskAiAgentAsync(AIAgent.Claude, prompt)).Returns(interviews); - A.CallTo(() => _interviewSessionService.GetAdjacentInterviewSessionAsync(userId)).Returns(existingSession); - A.CallTo(() => _interviewSessionService.UpdateInterviewSessionAsync(existingSession)).Returns(Task.CompletedTask); - A.CallTo(() => _interviewService.CreateInterviewsAsync(interviews, user, existingSession)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewSessionRepository.GetAdjacentInterviewSessionAsync(userId)).Returns(existingSession); + A.CallTo(() => _interviewSessionRepository.UpdateInterviewSessionAsync(existingSession)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(interviews, user, existingSession)).Returns(Task.CompletedTask); - await _interviewRepository.GenerateInterviewsAsync(techRequest); + await _interviewService.GenerateInterviewsAsync(techRequest); A.CallTo(() => _promptService.CreateTechnicalPrompt(techRequest, userId)).MustHaveHappenedOnceExactly(); A.CallTo(() => _aiService.AskAiAgentAsync(AIAgent.Claude, prompt)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_GenerateInterviewsAsync_UpdatesSessionSubject_ForTechnicalInterview() + public async Task InterviewService_GenerateInterviewsAsync_UpdatesSessionSubject_ForTechnicalInterview() { TechnicalRequestDTO techRequest = new TechnicalRequestDTO { @@ -156,22 +156,22 @@ public async Task InterviewRepository_GenerateInterviewsAsync_UpdatesSessionSubj InterviewSession existingSession = new InterviewSession { ID = 5, UserID = userId, Subject = "" }; List interviews = new List(); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(user); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(user); A.CallTo(() => _promptService.CreateTechnicalPrompt(techRequest, userId)).Returns("prompt"); A.CallTo(() => _aiService.AskAiAgentAsync(A._, A._)).Returns(interviews); - A.CallTo(() => _interviewSessionService.GetAdjacentInterviewSessionAsync(userId)).Returns(existingSession); - A.CallTo(() => _interviewSessionService.UpdateInterviewSessionAsync(existingSession)).Returns(Task.CompletedTask); - A.CallTo(() => _interviewService.CreateInterviewsAsync(A>._, A._, A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewSessionRepository.GetAdjacentInterviewSessionAsync(userId)).Returns(existingSession); + A.CallTo(() => _interviewSessionRepository.UpdateInterviewSessionAsync(existingSession)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(A>._, A._, A._)).Returns(Task.CompletedTask); - await _interviewRepository.GenerateInterviewsAsync(techRequest); + await _interviewService.GenerateInterviewsAsync(techRequest); Assert.Equal("Algorithms, Data Structures", existingSession.Subject); - A.CallTo(() => _interviewSessionService.UpdateInterviewSessionAsync(existingSession)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.UpdateInterviewSessionAsync(existingSession)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_GenerateInterviewsAsync_GetsAdjacentSession_ForTechnicalInterview() + public async Task InterviewService_GenerateInterviewsAsync_GetsAdjacentSession_ForTechnicalInterview() { TechnicalRequestDTO techRequest = new TechnicalRequestDTO { AIAgent = "ChatGPT", Subject = new List { "OOP" } }; int userId = 1; @@ -179,17 +179,17 @@ public async Task InterviewRepository_GenerateInterviewsAsync_GetsAdjacentSessio InterviewSession existingSession = new InterviewSession(); List interviews = new List(); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _userService.GetCurrentUserEntityByIdAsync(userId)).Returns(user); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(user); A.CallTo(() => _promptService.CreateTechnicalPrompt(techRequest, userId)).Returns("prompt"); A.CallTo(() => _aiService.AskAiAgentAsync(A._, A._)).Returns(interviews); - A.CallTo(() => _interviewSessionService.GetAdjacentInterviewSessionAsync(userId)).Returns(existingSession); - A.CallTo(() => _interviewSessionService.UpdateInterviewSessionAsync(existingSession)).Returns(Task.CompletedTask); - A.CallTo(() => _interviewService.CreateInterviewsAsync(A>._, A._, A._)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewSessionRepository.GetAdjacentInterviewSessionAsync(userId)).Returns(existingSession); + A.CallTo(() => _interviewSessionRepository.UpdateInterviewSessionAsync(existingSession)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.CreateInterviewsAsync(A>._, A._, A._)).Returns(Task.CompletedTask); - await _interviewRepository.GenerateInterviewsAsync(techRequest); + await _interviewService.GenerateInterviewsAsync(techRequest); - A.CallTo(() => _interviewSessionService.GetAdjacentInterviewSessionAsync(userId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.GetAdjacentInterviewSessionAsync(userId)).MustHaveHappenedOnceExactly(); } #endregion @@ -197,7 +197,7 @@ public async Task InterviewRepository_GenerateInterviewsAsync_GetsAdjacentSessio #region GetLatestInterviewsAsync Tests [Fact] - public async Task InterviewRepository_GetLatestInterviewsAsync_ReturnsInterviews_WhenSessionIsOngoing() + public async Task InterviewService_GetLatestInterviewsAsync_ReturnsInterviews_WhenSessionIsOngoing() { int userId = 1; int sessionId = 5; @@ -213,50 +213,50 @@ public async Task InterviewRepository_GetLatestInterviewsAsync_ReturnsInterviews new HRInterviewDTO { ID = 2, Question = "Question 2" } }; - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns(session); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(sessionId)).Returns(interviews); - A.CallTo(() => _interviewService.GetLatestInterviews(interviews)).Returns(interviewDTOs); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns(session); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(sessionId)).Returns(interviews); + A.CallTo(() => _interviewRepository.GetLatestInterviews(interviews)).Returns(interviewDTOs); - List result = await _interviewRepository.GetLatestInterviewsAsync(); + List result = await _interviewService.GetLatestInterviewsAsync(); Assert.Equal(2, result.Count); } [Fact] - public async Task InterviewRepository_GetLatestInterviewsAsync_ReturnsEmptyList_WhenSessionIsNull() + public async Task InterviewService_GetLatestInterviewsAsync_ReturnsEmptyList_WhenSessionIsNull() { int userId = 1; int sessionId = 5; - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns((InterviewSession)null); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns((InterviewSession)null); - List result = await _interviewRepository.GetLatestInterviewsAsync(); + List result = await _interviewService.GetLatestInterviewsAsync(); Assert.Empty(result); } [Fact] - public async Task InterviewRepository_GetLatestInterviewsAsync_ReturnsEmptyList_WhenSessionIsNotOngoing() + public async Task InterviewService_GetLatestInterviewsAsync_ReturnsEmptyList_WhenSessionIsNotOngoing() { int userId = 1; int sessionId = 5; InterviewSession session = new InterviewSession { ID = sessionId, Status = InterviewSessionStatus.Passed }; - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns(session); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns(session); - List result = await _interviewRepository.GetLatestInterviewsAsync(); + List result = await _interviewService.GetLatestInterviewsAsync(); Assert.Empty(result); } [Fact] - public async Task InterviewRepository_GetLatestInterviewsAsync_CallsGetLatestInterviews() + public async Task InterviewService_GetLatestInterviewsAsync_CallsGetLatestInterviews() { int userId = 1; int sessionId = 5; @@ -264,15 +264,15 @@ public async Task InterviewRepository_GetLatestInterviewsAsync_CallsGetLatestInt List interviews = new List(); List interviewDTOs = new List(); - A.CallTo(() => _userService.GetCurrentUserID()).Returns(userId); - A.CallTo(() => _interviewSessionService.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); - A.CallTo(() => _interviewSessionService.GetInterviewSessionByIdAsync(sessionId)).Returns(session); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(sessionId)).Returns(interviews); - A.CallTo(() => _interviewService.GetLatestInterviews(interviews)).Returns(interviewDTOs); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns(session); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(sessionId)).Returns(interviews); + A.CallTo(() => _interviewRepository.GetLatestInterviews(interviews)).Returns(interviewDTOs); - await _interviewRepository.GetLatestInterviewsAsync(); + await _interviewService.GetLatestInterviewsAsync(); - A.CallTo(() => _interviewService.GetLatestInterviews(interviews)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewRepository.GetLatestInterviews(interviews)).MustHaveHappenedOnceExactly(); } #endregion @@ -280,7 +280,7 @@ public async Task InterviewRepository_GetLatestInterviewsAsync_CallsGetLatestInt #region EvaluateInterviewsAsync Tests [Fact] - public async Task InterviewRepository_EvaluateInterviewsAsync_CreatesHrEvaluationPrompt() + public async Task InterviewService_EvaluateInterviewsAsync_CreatesHrEvaluationPrompt() { List evaluateRequests = new List { @@ -294,21 +294,21 @@ public async Task InterviewRepository_EvaluateInterviewsAsync_CreatesHrEvaluatio string fullPrompt = "full prompt"; List evaluatedInterviews = new List(); - A.CallTo(() => _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); A.CallTo(() => _promptService.CreateHrEvaluationPrompt(evaluateRequests)).Returns(basePrompt); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).Returns(serialized); A.CallTo(() => _promptService.GetPromptWithSerializedInterviews(basePrompt, serialized)).Returns(fullPrompt); A.CallTo(() => _aiService.EvaluateInterviewsAsync(fullPrompt, session.AIAgent)).Returns(evaluatedInterviews); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); A.CallTo(() => _promptService.CreateHrEvaluationPrompt(evaluateRequests)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_EvaluateInterviewsAsync_CreatesTechnicalEvaluationPrompt() + public async Task InterviewService_EvaluateInterviewsAsync_CreatesTechnicalEvaluationPrompt() { List evaluateRequests = new List { @@ -322,42 +322,42 @@ public async Task InterviewRepository_EvaluateInterviewsAsync_CreatesTechnicalEv string fullPrompt = "full"; List evaluatedInterviews = new List(); - A.CallTo(() => _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); A.CallTo(() => _promptService.CreateTechnicalEvaluationPrompt(evaluateRequests)).Returns(basePrompt); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).Returns(serialized); A.CallTo(() => _promptService.GetPromptWithSerializedInterviews(basePrompt, serialized)).Returns(fullPrompt); A.CallTo(() => _aiService.EvaluateInterviewsAsync(fullPrompt, session.AIAgent)).Returns(evaluatedInterviews); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); A.CallTo(() => _promptService.CreateTechnicalEvaluationPrompt(evaluateRequests)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_EvaluateInterviewsAsync_SerializesExistingInterviews() + public async Task InterviewService_EvaluateInterviewsAsync_SerializesExistingInterviews() { List evaluateRequests = new List(); InterviewSession session = new InterviewSession { ID = 1, AIAgent = AIAgent.ChatGPT }; List existingInterviews = new List(); List evaluatedInterviews = new List(); - A.CallTo(() => _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); A.CallTo(() => _promptService.CreateHrEvaluationPrompt(evaluateRequests)).Returns("prompt"); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).Returns("serialized"); A.CallTo(() => _promptService.GetPromptWithSerializedInterviews(A._, A._)).Returns("full"); A.CallTo(() => _aiService.EvaluateInterviewsAsync(A._, A._)).Returns(evaluatedInterviews); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_EvaluateInterviewsAsync_CallsAIServiceToEvaluate() + public async Task InterviewService_EvaluateInterviewsAsync_CallsAIServiceToEvaluate() { List evaluateRequests = new List(); InterviewSession session = new InterviewSession { ID = 1, AIAgent = AIAgent.Claude }; @@ -365,59 +365,59 @@ public async Task InterviewRepository_EvaluateInterviewsAsync_CallsAIServiceToEv string fullPrompt = "evaluation prompt"; List evaluatedInterviews = new List(); - A.CallTo(() => _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); A.CallTo(() => _promptService.CreateTechnicalEvaluationPrompt(evaluateRequests)).Returns("base"); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).Returns("serialized"); A.CallTo(() => _promptService.GetPromptWithSerializedInterviews(A._, A._)).Returns(fullPrompt); A.CallTo(() => _aiService.EvaluateInterviewsAsync(fullPrompt, AIAgent.Claude)).Returns(evaluatedInterviews); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); A.CallTo(() => _aiService.EvaluateInterviewsAsync(fullPrompt, AIAgent.Claude)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_EvaluateInterviewsAsync_UpdatesExistingInterviews() + public async Task InterviewService_EvaluateInterviewsAsync_UpdatesExistingInterviews() { List evaluateRequests = new List(); InterviewSession session = new InterviewSession { ID = 1, AIAgent = AIAgent.ChatGPT }; List existingInterviews = new List { new HRInterview() }; List evaluatedInterviews = new List { new HRInterview() }; - A.CallTo(() => _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); A.CallTo(() => _promptService.CreateHrEvaluationPrompt(evaluateRequests)).Returns("prompt"); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).Returns("serialized"); A.CallTo(() => _promptService.GetPromptWithSerializedInterviews(A._, A._)).Returns("full"); A.CallTo(() => _aiService.EvaluateInterviewsAsync(A._, A._)).Returns(evaluatedInterviews); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task InterviewRepository_EvaluateInterviewsAsync_FinalizesInterviewSession() + public async Task InterviewService_EvaluateInterviewsAsync_FinalizesInterviewSession() { List evaluateRequests = new List(); InterviewSession session = new InterviewSession { ID = 1, AIAgent = AIAgent.ChatGPT }; List existingInterviews = new List(); List evaluatedInterviews = new List(); - A.CallTo(() => _interviewSessionService.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionFromQuestionsAsync(evaluateRequests)).Returns(session); A.CallTo(() => _promptService.CreateHrEvaluationPrompt(evaluateRequests)).Returns("prompt"); - A.CallTo(() => _interviewService.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); + A.CallTo(() => _interviewRepository.GetInterviewsBySessionIdAsync(session.ID)).Returns(existingInterviews); A.CallTo(() => _serializationService.SerializeCollection(existingInterviews)).Returns("serialized"); A.CallTo(() => _promptService.GetPromptWithSerializedInterviews(A._, A._)).Returns("full"); A.CallTo(() => _aiService.EvaluateInterviewsAsync(A._, A._)).Returns(evaluatedInterviews); - A.CallTo(() => _interviewService.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); + A.CallTo(() => _interviewRepository.UpdateInterviewAsync(existingInterviews)).Returns(Task.CompletedTask); - await _interviewRepository.EvaluateInterviewsAsync(evaluateRequests); + await _interviewService.EvaluateInterviewsAsync(evaluateRequests); - A.CallTo(() => _interviewSessionService.FinalizeInterviewSession(session, A>._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.FinalizeInterviewSession(session, A>._)).MustHaveHappenedOnceExactly(); } #endregion diff --git a/Backend/prepAIred.Tests/Services/InterviewSessionServiceTest.cs b/Backend/prepAIred.Tests/Services/InterviewSessionServiceTest.cs new file mode 100644 index 0000000..bb2c654 --- /dev/null +++ b/Backend/prepAIred.Tests/Services/InterviewSessionServiceTest.cs @@ -0,0 +1,413 @@ +using FakeItEasy; +using prepAIred.Data; +using prepAIred.Services; + +namespace prepAIred.Tests.Services +{ + public class InterviewSessionServiceTest + { + private readonly IInterviewSessionRepository _interviewSessionRepository; + private readonly IUserRepository _userRepository; + private readonly InterviewSessionService _interviewSessionService; + + public InterviewSessionServiceTest() + { + _interviewSessionRepository = A.Fake(); + _userRepository = A.Fake(); + + _interviewSessionService = new InterviewSessionService(_interviewSessionRepository, _userRepository); + } + + #region GetInterviewSessionDTOsAsync Tests + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionDTOsAsync_ReturnsConvertedDTOs() + { + int userId = 1; + List sessions = new List + { + new InterviewSession { ID = 1, Subject = "C# Basics", UserID = userId }, + new InterviewSession { ID = 2, Subject = "Python Advanced", UserID = userId } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); + + List result = await _interviewSessionService.GetInterviewSessionDTOsAsync(); + + Assert.NotNull(result); + Assert.Equal(2, result.Count); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionDTOsAsync_CallsGetCurrentUserID() + { + int userId = 1; + List sessions = new List(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); + + await _interviewSessionService.GetInterviewSessionDTOsAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionDTOsAsync_CallsGetInterviewSessionsByUserIdAsync() + { + int userId = 1; + List sessions = new List(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); + + await _interviewSessionService.GetInterviewSessionDTOsAsync(); + + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).MustHaveHappenedOnceExactly(); + } + + #endregion + + #region GetInterviewSessionActivitiesAsync Tests + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionActivitiesAsync_ReturnsActivities() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { ID = 1, Subject = "OOP", AverageScore = 8.5f }, + new InterviewSessionActivityDTO { ID = 2, Subject = "Algorithms", AverageScore = 7.0f } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionActivitiesAsync(); + + Assert.Equal(2, result.Count); + Assert.Equal("OOP", result[0].Subject); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionActivitiesAsync_CallsServiceMethod() + { + int userId = 1; + List activities = new List(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + await _interviewSessionService.GetInterviewSessionActivitiesAsync(); + + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).MustHaveHappenedOnceExactly(); + } + + #endregion + + #region GetInterviewSessionStatisticsAsync Tests + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionStatisticsAsync_ReturnsStatistics() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetTotalInterviewSessionsAsync(userId)).Returns(10); + A.CallTo(() => _interviewSessionRepository.GetPassedInterviewSessionsAsync(userId)).Returns(7); + A.CallTo(() => _interviewSessionRepository.GetOngoingInterviewSessionsAsync(userId)).Returns(2); + A.CallTo(() => _interviewSessionRepository.GetAverageScoreAsync(userId)).Returns(8.5m); + A.CallTo(() => _interviewSessionRepository.GetCompletionRateAsync(userId)).Returns(70.0m); + + ProfileStatisticsDTO result = await _interviewSessionService.GetInterviewSessionStatisticsAsync(); + + Assert.Equal(10, result.TotalInterviewSessions); + Assert.Equal(7, result.PassedInterviewSessions); + Assert.Equal(2, result.OngoingInterviewSessions); + Assert.Equal(8.5m, result.AverageScore); + Assert.Equal(70.0m, result.CompletionRate); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionStatisticsAsync_CallsAllStatisticMethods() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetTotalInterviewSessionsAsync(userId)).Returns(10); + A.CallTo(() => _interviewSessionRepository.GetPassedInterviewSessionsAsync(userId)).Returns(7); + A.CallTo(() => _interviewSessionRepository.GetOngoingInterviewSessionsAsync(userId)).Returns(2); + A.CallTo(() => _interviewSessionRepository.GetAverageScoreAsync(userId)).Returns(8.5m); + A.CallTo(() => _interviewSessionRepository.GetCompletionRateAsync(userId)).Returns(70.0m); + + await _interviewSessionService.GetInterviewSessionStatisticsAsync(); + + A.CallTo(() => _interviewSessionRepository.GetTotalInterviewSessionsAsync(userId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.GetPassedInterviewSessionsAsync(userId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.GetOngoingInterviewSessionsAsync(userId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.GetAverageScoreAsync(userId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _interviewSessionRepository.GetCompletionRateAsync(userId)).MustHaveHappenedOnceExactly(); + } + + #endregion + + #region GetInterviewSessionPerformanceAsync Tests + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionPerformanceAsync_ReturnsOrderedPerformanceData() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { ID = 2, DateCreated = new DateTime(2024, 1, 15), AverageScore = 9.0f }, + new InterviewSessionActivityDTO { ID = 1, DateCreated = new DateTime(2024, 1, 10), AverageScore = 8.0f } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionPerformanceAsync(); + + Assert.Equal(2, result.Count); + Assert.Equal(1, result[0].ID); + Assert.Equal(8.0f, result[0].Score); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionPerformanceAsync_OrdersByDateCreated() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { ID = 3, DateCreated = new DateTime(2024, 1, 20), AverageScore = 7.5f }, + new InterviewSessionActivityDTO { ID = 1, DateCreated = new DateTime(2024, 1, 10), AverageScore = 8.0f }, + new InterviewSessionActivityDTO { ID = 2, DateCreated = new DateTime(2024, 1, 15), AverageScore = 9.0f } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionPerformanceAsync(); + + Assert.Equal(1, result[0].ID); + Assert.Equal(2, result[1].ID); + Assert.Equal(3, result[2].ID); + } + + #endregion + + #region GetInterviewSessionProgrammingLanguageDataAsync Tests + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionProgrammingLanguageDataAsync_GroupsByLanguage() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { ProgrammingLanguage = "C#" }, + new InterviewSessionActivityDTO { ProgrammingLanguage = "C#" }, + new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionProgrammingLanguageDataAsync(); + + Assert.Equal(2, result.Count); + ProgrammingLanguageDataDTO csharpData = result.FirstOrDefault(x => x.Language == "C#"); + Assert.NotNull(csharpData); + Assert.Equal(2, csharpData.Sessions); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionProgrammingLanguageDataAsync_CountsCorrectly() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { ProgrammingLanguage = "Java" }, + new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" }, + new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" }, + new InterviewSessionActivityDTO { ProgrammingLanguage = "Python" } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionProgrammingLanguageDataAsync(); + + ProgrammingLanguageDataDTO pythonData = result.FirstOrDefault(x => x.Language == "Python"); + Assert.NotNull(pythonData); + Assert.Equal(3, pythonData.Sessions); + } + + #endregion + + #region GetInterviewSessionPositionDataAsync Tests + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionPositionDataAsync_GroupsByPosition() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { Position = "Junior Developer" }, + new InterviewSessionActivityDTO { Position = "Junior Developer" }, + new InterviewSessionActivityDTO { Position = "Senior Developer" } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionPositionDataAsync(); + + Assert.Equal(2, result.Count); + PositionDataDTO juniorData = result.FirstOrDefault(x => x.Position == "Junior Developer"); + Assert.NotNull(juniorData); + Assert.Equal(2, juniorData.Sessions); + } + + [Fact] + public async Task InterviewSessionService_GetInterviewSessionPositionDataAsync_CountsCorrectly() + { + int userId = 1; + List activities = new List + { + new InterviewSessionActivityDTO { Position = "Lead Developer" }, + new InterviewSessionActivityDTO { Position = "Mid Developer" }, + new InterviewSessionActivityDTO { Position = "Mid Developer" }, + new InterviewSessionActivityDTO { Position = "Mid Developer" } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionActivitiesAsync(userId)).Returns(activities); + + List result = await _interviewSessionService.GetInterviewSessionPositionDataAsync(); + + PositionDataDTO midData = result.FirstOrDefault(x => x.Position == "Mid Developer"); + Assert.NotNull(midData); + Assert.Equal(3, midData.Sessions); + } + + #endregion + + #region FinishInterviewSessionAsync Tests + + [Fact] + public async Task InterviewSessionService_FinishInterviewSessionAsync_GetsLatestSession() + { + int userId = 1; + int sessionId = 5; + InterviewSession session = new InterviewSession { ID = sessionId }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns(session); + A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync(session)).Returns(Task.CompletedTask); + + await _interviewSessionService.FinishInterviewSessionAsync(); + + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task InterviewSessionService_FinishInterviewSessionAsync_CallsFinishOnService() + { + int userId = 1; + int sessionId = 5; + InterviewSession session = new InterviewSession { ID = sessionId }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns(session); + A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync(session)).Returns(Task.CompletedTask); + + await _interviewSessionService.FinishInterviewSessionAsync(); + + A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync(session)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task InterviewSessionService_FinishInterviewSessionAsync_ExecutesInCorrectOrder() + { + int userId = 1; + int sessionId = 5; + InterviewSession session = new InterviewSession { ID = sessionId }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).Returns(sessionId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).Returns(session); + A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync(session)).Returns(Task.CompletedTask); + + await _interviewSessionService.FinishInterviewSessionAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened() + .Then(A.CallTo(() => _interviewSessionRepository.GetLatestInterviewSessionIDAsync(userId)).MustHaveHappened()) + .Then(A.CallTo(() => _interviewSessionRepository.GetInterviewSessionByIdAsync(sessionId)).MustHaveHappened()) + .Then(A.CallTo(() => _interviewSessionRepository.FinishInterviewSessionAsync(session)).MustHaveHappened()); + } + + #endregion + + #region DeleteInterviewSessionsAsync Tests + + [Fact] + public async Task InterviewSessionService_DeleteInterviewSessionsAsync_GetsUserSessions() + { + int userId = 1; + List sessions = new List + { + new InterviewSession { ID = 1 }, + new InterviewSession { ID = 2 } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); + A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync(sessions)).Returns(Task.CompletedTask); + + await _interviewSessionService.DeleteInterviewSessionsAsync(); + + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task InterviewSessionService_DeleteInterviewSessionsAsync_CallsDeleteOnService() + { + int userId = 1; + List sessions = new List + { + new InterviewSession { ID = 1 }, + new InterviewSession { ID = 2 } + }; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); + A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync(sessions)).Returns(Task.CompletedTask); + + await _interviewSessionService.DeleteInterviewSessionsAsync(); + + A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync(sessions)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task InterviewSessionService_DeleteInterviewSessionsAsync_ExecutesInCorrectOrder() + { + int userId = 1; + List sessions = new List(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).Returns(sessions); + A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync(sessions)).Returns(Task.CompletedTask); + + await _interviewSessionService.DeleteInterviewSessionsAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened() + .Then(A.CallTo(() => _interviewSessionRepository.GetInterviewSessionsByUserIdAsync(userId)).MustHaveHappened()) + .Then(A.CallTo(() => _interviewSessionRepository.DeleteInterviewSessionsAsync(sessions)).MustHaveHappened()); + } + + #endregion + } +} diff --git a/Backend/prepAIred.Tests/Services/ProfilePictureServiceTest.cs b/Backend/prepAIred.Tests/Services/ProfilePictureServiceTest.cs index bb3d940..b5346aa 100644 --- a/Backend/prepAIred.Tests/Services/ProfilePictureServiceTest.cs +++ b/Backend/prepAIred.Tests/Services/ProfilePictureServiceTest.cs @@ -1,147 +1,305 @@ using FakeItEasy; using Microsoft.AspNetCore.Http; -using prepAIred.Exceptions; +using prepAIred.Data; using prepAIred.Services; namespace prepAIred.Tests.Services { public class ProfilePictureServiceTest { - private readonly IFileService _fileService; + private readonly IProfilePictureRepository _profilePictureRepository; + private readonly IUserRepository _userRepository; + private readonly ProfilePictureService _profilePictureService; public ProfilePictureServiceTest() { - _fileService = A.Fake(); + _profilePictureRepository = A.Fake(); + _userRepository = A.Fake(); + + _profilePictureService = new ProfilePictureService(_profilePictureRepository, _userRepository); } - #region SaveFileAsync Tests + #region GetProfilePictureUrlAsync Tests [Fact] - public async Task ProfilePictureService_SaveFileAsync_ThrowsException_WhenFileIsNull() + public async Task ProfilePictureService_GetProfilePictureUrlAsync_ReturnsCorrectUrl() { - await Assert.ThrowsAsync(() => - TestSaveFileAsync(null)); + int userId = 1; + string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); + + string result = await _profilePictureService.GetProfilePictureUrlAsync(); + + Assert.Equal(expectedUrl, result); } [Fact] - public async Task ProfilePictureService_SaveFileAsync_ThrowsException_WhenFileLengthIsZero() + public async Task ProfilePictureService_GetProfilePictureUrlAsync_CallsGetCurrentUserID() { - IFormFile imageFile = A.Fake(); - A.CallTo(() => imageFile.Length).Returns(0); + int userId = 1; + string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); - await Assert.ThrowsAsync(() => - TestSaveFileAsync(imageFile)); + await _profilePictureService.GetProfilePictureUrlAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureService_SaveFileAsync_ThrowsException_WithCorrectMessage() + public async Task ProfilePictureService_GetProfilePictureUrlAsync_CallsGetProfilePictureUrlByUserIdAsync() { - IFormFile imageFile = A.Fake(); - A.CallTo(() => imageFile.Length).Returns(0); + int userId = 1; + string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); + + await _profilePictureService.GetProfilePictureUrlAsync(); + + A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(userId)).MustHaveHappenedOnceExactly(); + } - ProfilePictureException exception = await Assert.ThrowsAsync(() => - TestSaveFileAsync(imageFile)); + [Fact] + public async Task ProfilePictureService_GetProfilePictureUrlAsync_ExecutesInCorrectOrder() + { + int userId = 1; + string expectedUrl = "https://localhost:7227/Uploads/profile.jpg"; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(userId)).Returns(expectedUrl); - Assert.Equal("File is null or empty", exception.Message); + await _profilePictureService.GetProfilePictureUrlAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened() + .Then(A.CallTo(() => _profilePictureRepository.GetProfilePictureUrlByUserIdAsync(userId)).MustHaveHappened()); } + #endregion + + #region ChangeProfilePictureAsync Tests + [Fact] - public async Task ProfilePictureService_SaveFileAsync_CallsCreateDirectoryIfNotExists() + public async Task ProfilePictureService_ChangeProfilePictureAsync_SavesNewFile() { IFormFile imageFile = A.Fake(); - A.CallTo(() => imageFile.Length).Returns(100); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns("/path/to/uploads"); - A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(".jpg"); - A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, A._, A._)).Returns("file.jpg"); + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string fileName = "new-profile.jpg"; + int userId = 1; + + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = string.Empty + }; - await TestSaveFileAsync(imageFile); + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(fileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).MustHaveHappenedOnceExactly(); + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureService_SaveFileAsync_CallsCheckFileExtension() + public async Task ProfilePictureService_ChangeProfilePictureAsync_GetsCurrentUser() { IFormFile imageFile = A.Fake(); - A.CallTo(() => imageFile.Length).Returns(100); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns("/path"); - A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(".jpg"); - A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, A._, A._)).Returns("file.jpg"); + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string fileName = "new-profile.jpg"; + int userId = 1; + + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = string.Empty + }; - await TestSaveFileAsync(imageFile); + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(fileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - A.CallTo(() => _fileService.CheckFileExtension(imageFile)).MustHaveHappenedOnceExactly(); + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappenedOnceExactly(); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureService_SaveFileAsync_CallsCreateFileNameAsync_WithCorrectParameters() + public async Task ProfilePictureService_ChangeProfilePictureAsync_DeletesOldPicture_WhenExists() { IFormFile imageFile = A.Fake(); - string path = "/uploads/path"; - string extension = ".png"; + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string oldFileName = "old-profile.jpg"; + string newFileName = "new-profile.jpg"; + int userId = 1; + + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = oldFileName + }; - A.CallTo(() => imageFile.Length).Returns(100); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns(path); - A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(extension); - A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).Returns("unique-name.png"); + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(newFileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _profilePictureRepository.DeleteProfilePictureAsync(oldFileName)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - await TestSaveFileAsync(imageFile); + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); - A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _profilePictureRepository.DeleteProfilePictureAsync(oldFileName)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProfilePictureService_SaveFileAsync_ReturnsFileName() + public async Task ProfilePictureService_ChangeProfilePictureAsync_DoesNotDeleteOldPicture_WhenNotExists() { IFormFile imageFile = A.Fake(); - string expectedFileName = "generated-guid.jpg"; + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string newFileName = "new-profile.jpg"; + int userId = 1; - A.CallTo(() => imageFile.Length).Returns(100); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns("/path"); - A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(".jpg"); - A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, A._, A._)).Returns(expectedFileName); + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = string.Empty + }; + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(newFileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - string result = await TestSaveFileAsync(imageFile); + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); - Assert.Equal(expectedFileName, result); + A.CallTo(() => _profilePictureRepository.DeleteProfilePictureAsync(A._)).MustNotHaveHappened(); } [Fact] - public async Task ProfilePictureService_SaveFileAsync_ExecutesInCorrectOrder() + public async Task ProfilePictureService_ChangeProfilePictureAsync_UpdatesUserProfilePicture() { IFormFile imageFile = A.Fake(); - string path = "/uploads"; - string extension = ".jpg"; - string fileName = "file.jpg"; + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string newFileName = "new-profile.jpg"; + int userId = 1; + + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = string.Empty + }; - A.CallTo(() => imageFile.Length).Returns(100); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).Returns(path); - A.CallTo(() => _fileService.CheckFileExtension(imageFile)).Returns(extension); - A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).Returns(fileName); + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(newFileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); - await TestSaveFileAsync(imageFile); + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); - A.CallTo(() => _fileService.CreateDirectoryIfNotExists()).MustHaveHappened() - .Then(A.CallTo(() => _fileService.CheckFileExtension(imageFile)).MustHaveHappened()) - .Then(A.CallTo(() => _fileService.CreateFileNameAsync(imageFile, path, extension)).MustHaveHappened()); + Assert.Equal(newFileName, currentUser.ProfilePicture); } - #endregion + [Fact] + public async Task ProfilePictureService_ChangeProfilePictureAsync_CallsUpdateUserAsync() + { + IFormFile imageFile = A.Fake(); + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string newFileName = "new-profile.jpg"; + int userId = 1; + + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = string.Empty + }; + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(newFileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); - #region Helper Methods + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).MustHaveHappenedOnceExactly(); + } - private async Task TestSaveFileAsync(IFormFile imageFile) + [Fact] + public async Task ProfilePictureService_ChangeProfilePictureAsync_ExecutesInCorrectOrder_WithoutOldPicture() { - if (imageFile is null || imageFile.Length == 0) + IFormFile imageFile = A.Fake(); + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string newFileName = "new-profile.jpg"; + int userId = 1; + + User currentUser = new User { - throw new ProfilePictureException("File is null or empty"); - } + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = string.Empty + }; + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(newFileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).MustHaveHappened() + .Then(A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).MustHaveHappened()); + } - string path = _fileService.CreateDirectoryIfNotExists(); - string extension = _fileService.CheckFileExtension(imageFile); - string fileName = await _fileService.CreateFileNameAsync(imageFile, path, extension); + [Fact] + public async Task ProfilePictureService_ChangeProfilePictureAsync_ExecutesInCorrectOrder_WithOldPicture() + { + IFormFile imageFile = A.Fake(); + ProfilePictureDTO profilePictureDto = new ProfilePictureDTO { ImageFile = imageFile }; + string oldFileName = "old-profile.jpg"; + string newFileName = "new-profile.jpg"; + int userId = 1; - return fileName; + User currentUser = new User + { + ID = userId, + Username = "JohnDoe", + Email = "john@example.com", + ProfilePicture = oldFileName + }; + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).Returns(newFileName); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _profilePictureRepository.DeleteProfilePictureAsync(oldFileName)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).Returns(Task.CompletedTask); + + await _profilePictureService.ChangeProfilePictureAsync(profilePictureDto); + + A.CallTo(() => _profilePictureRepository.SaveFileAsync(imageFile)).MustHaveHappened() + .Then(A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappened()) + .Then(A.CallTo(() => _profilePictureRepository.DeleteProfilePictureAsync(oldFileName)).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, null)).MustHaveHappened()); } #endregion diff --git a/Backend/prepAIred.Tests/Repositories/RefreshTokenRepositoryTest.cs b/Backend/prepAIred.Tests/Services/RefreshTokenServiceTest.cs similarity index 65% rename from Backend/prepAIred.Tests/Repositories/RefreshTokenRepositoryTest.cs rename to Backend/prepAIred.Tests/Services/RefreshTokenServiceTest.cs index 235c97f..a676c40 100644 --- a/Backend/prepAIred.Tests/Repositories/RefreshTokenRepositoryTest.cs +++ b/Backend/prepAIred.Tests/Services/RefreshTokenServiceTest.cs @@ -4,42 +4,42 @@ using prepAIred.Exceptions; using prepAIred.Services; -namespace prepAIred.Tests.Repositories +namespace prepAIred.Tests.Services { - public class RefreshTokenRepositoryTest + public class RefreshTokenServiceTest { private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IRefreshTokenService _refreshTokenService; + private readonly IRefreshTokenRepository _refreshTokenRepository; private readonly IJwtService _jwtService; private readonly ICookieService _cookieService; - private readonly IUserService _userService; - private readonly RefreshTokenRepository _refreshTokenRepository; + private readonly IUserRepository _userRepository; + private readonly RefreshTokenService _refreshTokenService; private readonly HttpContext _httpContext; - public RefreshTokenRepositoryTest() + public RefreshTokenServiceTest() { _httpContextAccessor = A.Fake(); - _refreshTokenService = A.Fake(); + _refreshTokenRepository = A.Fake(); _jwtService = A.Fake(); _cookieService = A.Fake(); - _userService = A.Fake(); + _userRepository = A.Fake(); _httpContext = new DefaultHttpContext(); A.CallTo(() => _httpContextAccessor.HttpContext).Returns(_httpContext); - _refreshTokenRepository = new RefreshTokenRepository( + _refreshTokenService = new RefreshTokenService( _httpContextAccessor, - _refreshTokenService, + _refreshTokenRepository, _jwtService, _cookieService, - _userService + _userRepository ); } #region GenerateNewRefreshTokenAsync Tests [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ReturnsValidResponse() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_ReturnsValidResponse() { string oldRefreshToken = "old-refresh-token"; string newRefreshToken = "new-refresh-token"; @@ -71,13 +71,13 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ReturnsVal IsRevoked = false }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).Returns(newRefreshToken); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).Returns(newAccessToken); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).Returns(newRefreshTokenEntity); - A.CallTo(() => _userService.GetUserByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).Returns(newRefreshTokenEntity); + A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).Returns(currentUser); - RefreshTokenResponseDTO result = await _refreshTokenRepository.GenerateNewRefreshTokenAsync(); + RefreshTokenResponseDTO result = await _refreshTokenService.GenerateNewRefreshTokenAsync(); Assert.NotNull(result); Assert.Equal(newAccessToken, result.NewAccessToken); @@ -87,7 +87,7 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ReturnsVal } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_RevokesOldToken() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_RevokesOldToken() { string oldRefreshToken = "old-refresh-token"; int userId = 1; @@ -104,19 +104,19 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_RevokesOld CurrentUserDTO currentUser = new CurrentUserDTO { ID = userId, Username = "JohnDoe" }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).Returns("new-token"); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).Returns("new-access"); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); - A.CallTo(() => _userService.GetUserByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); + A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).Returns(currentUser); - await _refreshTokenRepository.GenerateNewRefreshTokenAsync(); + await _refreshTokenService.GenerateNewRefreshTokenAsync(); Assert.True(storedToken.IsRevoked); } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_CallsJwtServiceToGenerateTokens() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_CallsJwtServiceToGenerateTokens() { string oldRefreshToken = "old-refresh-token"; int userId = 1; @@ -133,20 +133,20 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_CallsJwtSe CurrentUserDTO currentUser = new CurrentUserDTO { ID = userId, Username = "JohnDoe" }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).Returns("new-token"); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).Returns("new-access"); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); - A.CallTo(() => _userService.GetUserByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); + A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).Returns(currentUser); - await _refreshTokenRepository.GenerateNewRefreshTokenAsync(); + await _refreshTokenService.GenerateNewRefreshTokenAsync(); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).MustHaveHappenedOnceExactly(); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).MustHaveHappenedOnceExactly(); } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_AddsNewRefreshTokenToDatabase() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_AddsNewRefreshTokenToDatabase() { string oldRefreshToken = "old-refresh-token"; string newRefreshToken = "new-refresh-token"; @@ -164,15 +164,15 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_AddsNewRef CurrentUserDTO currentUser = new CurrentUserDTO { ID = userId, Username = "JohnDoe" }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).Returns(newRefreshToken); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).Returns("new-access"); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); - A.CallTo(() => _userService.GetUserByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); + A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).Returns(currentUser); - await _refreshTokenRepository.GenerateNewRefreshTokenAsync(); + await _refreshTokenService.GenerateNewRefreshTokenAsync(); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A.That.Matches(rt => + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A.That.Matches(rt => rt.Token == newRefreshToken && rt.UserID == userId && !rt.IsRevoked @@ -180,7 +180,7 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_AddsNewRef } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_UpdatesCookies() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_UpdatesCookies() { string oldRefreshToken = "old-refresh-token"; string newRefreshToken = "new-refresh-token"; @@ -199,13 +199,13 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_UpdatesCoo CurrentUserDTO currentUser = new CurrentUserDTO { ID = userId, Username = "JohnDoe" }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).Returns(newRefreshToken); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).Returns(newAccessToken); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); - A.CallTo(() => _userService.GetUserByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).Returns(new RefreshToken()); + A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).Returns(currentUser); - await _refreshTokenRepository.GenerateNewRefreshTokenAsync(); + await _refreshTokenService.GenerateNewRefreshTokenAsync(); A.CallTo(() => _cookieService.DeleteCookie("RefreshToken")).MustHaveHappenedOnceExactly(); A.CallTo(() => _cookieService.CreateCookie("AccessToken", newAccessToken)).MustHaveHappenedOnceExactly(); @@ -213,20 +213,20 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_UpdatesCoo } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ThrowsException_WhenTokenIsNull() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_ThrowsException_WhenTokenIsNull() { string refreshToken = "invalid-token"; _httpContext.Request.Headers.Cookie = $"RefreshToken={refreshToken}"; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(refreshToken)).Returns((RefreshToken)null); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(refreshToken)).Returns((RefreshToken)null); await Assert.ThrowsAsync(() => - _refreshTokenRepository.GenerateNewRefreshTokenAsync()); + _refreshTokenService.GenerateNewRefreshTokenAsync()); } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ThrowsException_WhenTokenIsExpired() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_ThrowsException_WhenTokenIsExpired() { string oldRefreshToken = "expired-token"; @@ -240,14 +240,14 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ThrowsExce IsRevoked = false }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); await Assert.ThrowsAsync(() => - _refreshTokenRepository.GenerateNewRefreshTokenAsync()); + _refreshTokenService.GenerateNewRefreshTokenAsync()); } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ThrowsException_WhenTokenIsRevoked() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_ThrowsException_WhenTokenIsRevoked() { string oldRefreshToken = "revoked-token"; @@ -261,14 +261,14 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ThrowsExce IsRevoked = true }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); await Assert.ThrowsAsync(() => - _refreshTokenRepository.GenerateNewRefreshTokenAsync()); + _refreshTokenService.GenerateNewRefreshTokenAsync()); } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_DoesNotCreateNewToken_WhenValidationFails() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_DoesNotCreateNewToken_WhenValidationFails() { string oldRefreshToken = "expired-token"; @@ -282,18 +282,18 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_DoesNotCre IsRevoked = false }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); await Assert.ThrowsAsync(() => - _refreshTokenRepository.GenerateNewRefreshTokenAsync()); + _refreshTokenService.GenerateNewRefreshTokenAsync()); A.CallTo(() => _jwtService.GenerateRefreshToken(A._)).MustNotHaveHappened(); A.CallTo(() => _jwtService.GenerateAcessToken(A._)).MustNotHaveHappened(); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).MustNotHaveHappened(); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).MustNotHaveHappened(); } [Fact] - public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ExecutesInCorrectOrder() + public async Task RefreshTokenService_GenerateNewRefreshTokenAsync_ExecutesInCorrectOrder() { string oldRefreshToken = "old-refresh-token"; string newRefreshToken = "new-refresh-token"; @@ -318,19 +318,19 @@ public async Task RefreshTokenRepository_GenerateNewRefreshTokenAsync_ExecutesIn ExpiryDate = DateTime.Now.AddDays(7) }; - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).Returns(storedToken); A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).Returns(newRefreshToken); A.CallTo(() => _jwtService.GenerateAcessToken(userId)).Returns(newAccessToken); - A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).Returns(newRefreshTokenEntity); - A.CallTo(() => _userService.GetUserByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).Returns(newRefreshTokenEntity); + A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).Returns(currentUser); - await _refreshTokenRepository.GenerateNewRefreshTokenAsync(); + await _refreshTokenService.GenerateNewRefreshTokenAsync(); - A.CallTo(() => _refreshTokenService.GetRefreshTokenAsync(oldRefreshToken)).MustHaveHappened() + A.CallTo(() => _refreshTokenRepository.GetRefreshTokenAsync(oldRefreshToken)).MustHaveHappened() .Then(A.CallTo(() => _jwtService.GenerateRefreshToken(userId)).MustHaveHappened()) .Then(A.CallTo(() => _jwtService.GenerateAcessToken(userId)).MustHaveHappened()) - .Then(A.CallTo(() => _refreshTokenService.AddRefreshTokenAsync(A._)).MustHaveHappened()) - .Then(A.CallTo(() => _userService.GetUserByIdAsync(userId)).MustHaveHappened()) + .Then(A.CallTo(() => _refreshTokenRepository.AddRefreshTokenAsync(A._)).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.GetUserByIdAsync(userId)).MustHaveHappened()) .Then(A.CallTo(() => _cookieService.DeleteCookie("RefreshToken")).MustHaveHappened()) .Then(A.CallTo(() => _cookieService.CreateCookie("AccessToken", newAccessToken)).MustHaveHappened()) .Then(A.CallTo(() => _cookieService.CreateCookie("RefreshToken", newRefreshToken)).MustHaveHappened()); diff --git a/Backend/prepAIred.Tests/Services/UserServiceTest.cs b/Backend/prepAIred.Tests/Services/UserServiceTest.cs new file mode 100644 index 0000000..07f33f8 --- /dev/null +++ b/Backend/prepAIred.Tests/Services/UserServiceTest.cs @@ -0,0 +1,260 @@ +using FakeItEasy; +using prepAIred.Data; +using prepAIred.Services; + +namespace prepAIred.Tests.Services +{ + public class UserServiceTest + { + private readonly IUserRepository _userRepository; + private readonly ICookieService _cookieService; + private readonly UserService _userService; + + public UserServiceTest() + { + _userRepository = A.Fake(); + _cookieService = A.Fake(); + + _userService = new UserService(_userRepository, _cookieService); + } + + #region GetCurrentUserAsync Tests + + [Fact] + public async Task UserService_GetCurrentUserAsync_ReturnsCurrentUser() + { + CurrentUserDTO expectedUser = new CurrentUserDTO + { + ID = 1, + Username = "JohnDoe", + Email = "john@example.com" + }; + + A.CallTo(() => _userRepository.GetCurrentUserAsync()).Returns(expectedUser); + + CurrentUserDTO result = await _userService.GetCurrentUserAsync(); + + Assert.Equal(expectedUser.ID, result.ID); + Assert.Equal(expectedUser.Username, result.Username); + Assert.Equal(expectedUser.Email, result.Email); + } + + [Fact] + public async Task UserService_GetCurrentUserAsync_CallsUserService() + { + CurrentUserDTO expectedUser = new CurrentUserDTO { ID = 1, Username = "JohnDoe" }; + + A.CallTo(() => _userRepository.GetCurrentUserAsync()).Returns(expectedUser); + + await _userService.GetCurrentUserAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserAsync()).MustHaveHappenedOnceExactly(); + } + + #endregion + + #region UpdateCurrentUserAsync Tests + + [Fact] + public async Task UserService_UpdateCurrentUserAsync_ValidatesUserData() + { + UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO + { + Username = "UpdatedName", + Email = "updated@example.com", + Password = "NewP@ssw0rd" + }; + + int userId = 1; + User currentUser = new User + { + ID = userId, + Username = "OldName", + Email = "old@example.com" + }; + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); + + await _userService.UpdateCurrentUserAsync(userCredentialsDto); + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_UpdateCurrentUserAsync_GetsCurrentUserID() + { + UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO + { + Username = "UpdatedName", + Email = "updated@example.com", + Password = "NewP@ssw0rd" + }; + + int userId = 1; + User currentUser = new User { ID = userId }; + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); + + await _userService.UpdateCurrentUserAsync(userCredentialsDto); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_UpdateCurrentUserAsync_GetsCurrentUserEntity() + { + UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO + { + Username = "UpdatedName", + Email = "updated@example.com", + Password = "NewP@ssw0rd" + }; + + int userId = 1; + User currentUser = new User { ID = userId }; + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); + + await _userService.UpdateCurrentUserAsync(userCredentialsDto); + + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_UpdateCurrentUserAsync_CallsUpdateUserAsync() + { + UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO + { + Username = "UpdatedName", + Email = "updated@example.com", + Password = "NewP@ssw0rd" + }; + + int userId = 1; + User currentUser = new User + { + ID = userId, + Username = "OldName", + Email = "old@example.com" + }; + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); + + await _userService.UpdateCurrentUserAsync(userCredentialsDto); + + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_UpdateCurrentUserAsync_ExecutesInCorrectOrder() + { + UserCredentialsDTO userCredentialsDto = new UserCredentialsDTO + { + Username = "UpdatedName", + Email = "updated@example.com", + Password = "NewP@ssw0rd" + }; + + int userId = 1; + User currentUser = new User { ID = userId }; + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).Returns(Task.CompletedTask); + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).Returns(currentUser); + A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).Returns(Task.CompletedTask); + + await _userService.UpdateCurrentUserAsync(userCredentialsDto); + + A.CallTo(() => _userRepository.ValidateUpdateUserDataAsync(userCredentialsDto)).MustHaveHappened() + .Then(A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.GetCurrentUserEntityByIdAsync(userId)).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.UpdateUserAsync(currentUser, userCredentialsDto)).MustHaveHappened()); + } + + #endregion + + #region DeleteCurrentUserAsync Tests + + [Fact] + public async Task UserService_DeleteCurrentUserAsync_DeletesAccessTokenCookie() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.DeleteUserAsync(userId)).Returns(Task.CompletedTask); + + await _userService.DeleteCurrentUserAsync(); + + A.CallTo(() => _cookieService.DeleteCookie("AccessToken")).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_DeleteCurrentUserAsync_DeletesRefreshTokenCookie() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.DeleteUserAsync(userId)).Returns(Task.CompletedTask); + + await _userService.DeleteCurrentUserAsync(); + + A.CallTo(() => _cookieService.DeleteCookie("RefreshToken")).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_DeleteCurrentUserAsync_GetsCurrentUserID() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.DeleteUserAsync(userId)).Returns(Task.CompletedTask); + + await _userService.DeleteCurrentUserAsync(); + + A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_DeleteCurrentUserAsync_DeletesUser() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.DeleteUserAsync(userId)).Returns(Task.CompletedTask); + + await _userService.DeleteCurrentUserAsync(); + + A.CallTo(() => _userRepository.DeleteUserAsync(userId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task UserService_DeleteCurrentUserAsync_ExecutesInCorrectOrder() + { + int userId = 1; + + A.CallTo(() => _userRepository.GetCurrentUserID()).Returns(userId); + A.CallTo(() => _userRepository.DeleteUserAsync(userId)).Returns(Task.CompletedTask); + + await _userService.DeleteCurrentUserAsync(); + + A.CallTo(() => _cookieService.DeleteCookie("AccessToken")).MustHaveHappened() + .Then(A.CallTo(() => _cookieService.DeleteCookie("RefreshToken")).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.GetCurrentUserID()).MustHaveHappened()) + .Then(A.CallTo(() => _userRepository.DeleteUserAsync(userId)).MustHaveHappened()); + } + + #endregion + } +} diff --git a/Frontend/src/Components/Common/ErrorComponent.tsx b/Frontend/src/Components/Common/ErrorComponent.tsx new file mode 100644 index 0000000..4e059c6 --- /dev/null +++ b/Frontend/src/Components/Common/ErrorComponent.tsx @@ -0,0 +1,22 @@ +import { Card, CardBody } from "@progress/kendo-react-layout"; + +const ErrorComponent = () => { + return ( +
+ + +
+

+ Something went wrong +

+

+ Please try refreshing the page +

+
+
+
+
+ ); +}; + +export default ErrorComponent; \ No newline at end of file diff --git a/Frontend/src/Components/Common/LoaderComponent.tsx b/Frontend/src/Components/Common/LoaderComponent.tsx new file mode 100644 index 0000000..127e2c1 --- /dev/null +++ b/Frontend/src/Components/Common/LoaderComponent.tsx @@ -0,0 +1,14 @@ +import { Loader } from "@progress/kendo-react-indicators"; + +const LoaderComponent = () => { + return ( +
+
+ + Loading... +
+
+ ); +}; + +export default LoaderComponent; \ No newline at end of file diff --git a/Frontend/src/Components/Home/Components/ProfileInformation/ProfileInfo.tsx b/Frontend/src/Components/Home/Components/ProfileInformation/ProfileInfo.tsx index eeb4b6a..91ff6a3 100644 --- a/Frontend/src/Components/Home/Components/ProfileInformation/ProfileInfo.tsx +++ b/Frontend/src/Components/Home/Components/ProfileInformation/ProfileInfo.tsx @@ -7,42 +7,16 @@ import UpdateUserButton from "../../../Buttons/UpdateUserButton"; import useUploadProfilePicture from "../../../../Hooks/Home/ProfilePicture/useUploadProfilePicture"; import useGetUser from "../../../../Hooks/Home/User/useGetUser"; import useGetProfilePictureUrl from "../../../../Hooks/Home/ProfilePicture/useGetProfilePicture"; -import { Loader } from "@progress/kendo-react-indicators"; +import LoaderComponent from "../../../Common/LoaderComponent"; +import ErrorComponent from "../../../Common/ErrorComponent"; const ProfileInfo = () => { const { data: user, isLoading: isUserLoading, isError: isUserError } = useGetUser(); const { data: profilePictureUrl, isLoading: isProfileLoading, isError: isProfileError } = useGetProfilePictureUrl(); const { showUpload, handleAvatarClick, handleAdd } = useUploadProfilePicture(); - if (isUserLoading || isProfileLoading) { - return ( -
-
- - Loading your profile... -
-
- ); - } - - if (isUserError || isProfileError) { - return ( -
- - -
-

- Unable to load profile -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isUserLoading || isProfileLoading) return ; + if (isUserError || isProfileError) return ; return ( @@ -74,4 +48,4 @@ const ProfileInfo = () => { ); }; -export default ProfileInfo; +export default ProfileInfo; \ No newline at end of file diff --git a/Frontend/src/Components/Home/Components/RecentActivity/RecentActivity.tsx b/Frontend/src/Components/Home/Components/RecentActivity/RecentActivity.tsx index bda7100..394db6d 100644 --- a/Frontend/src/Components/Home/Components/RecentActivity/RecentActivity.tsx +++ b/Frontend/src/Components/Home/Components/RecentActivity/RecentActivity.tsx @@ -4,41 +4,15 @@ import { Card, CardBody, CardFooter, CardHeader } from "@progress/kendo-react-la import useChangePage from "../../../../Hooks/Common/useChangePage"; import ActivityItemRender from "./ActivityItemRenderer"; import useGetRecentInterviewSessions from "../../../../Hooks/InterviewSessions/useGetRecentInterviewSessions"; -import { Loader } from "@progress/kendo-react-indicators"; +import LoaderComponent from "../../../Common/LoaderComponent"; +import ErrorComponent from "../../../Common/ErrorComponent"; const RecentActivity = () => { const { data: recentActivity, isLoading, isError } = useGetRecentInterviewSessions(); const { pagedData, skip, take, handlePageChange } = useChangePage(recentActivity || []); - if (isLoading) { - return ( -
-
- - Loading your profile... -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load profile -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( @@ -93,4 +67,4 @@ const RecentActivity = () => { ); }; -export default RecentActivity; +export default RecentActivity; \ No newline at end of file diff --git a/Frontend/src/Components/Home/Components/Statistics/StatisticsGrid.tsx b/Frontend/src/Components/Home/Components/Statistics/StatisticsGrid.tsx index 929f276..b7b83ab 100644 --- a/Frontend/src/Components/Home/Components/Statistics/StatisticsGrid.tsx +++ b/Frontend/src/Components/Home/Components/Statistics/StatisticsGrid.tsx @@ -2,41 +2,14 @@ import StatisticCard from "./StatisticCard"; import useProcessStatistics from "../../../../Hooks/InterviewSessions/useProcessStatistics"; import useGetInterviewSessionStatistics from "../../../../Hooks/InterviewSessions/useGetInterviewSessionStatistics"; import type { ProfileStats } from "../../../../Utils/interfaces"; -import { Loader } from "@progress/kendo-react-indicators"; -import { Card, CardBody } from "@progress/kendo-react-layout"; +import LoaderComponent from "../../../Common/LoaderComponent"; +import ErrorComponent from "../../../Common/ErrorComponent"; const StatisticsGrid = () => { const { data: interviewSessionStatistics, isLoading, isError} = useGetInterviewSessionStatistics(); - if (isLoading) { - return ( -
-
- - Loading your profile... -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load profile -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; const { interviewSessionGoal, diff --git a/Frontend/src/Components/Interviews/HR/GetHrInterviews.tsx b/Frontend/src/Components/Interviews/HR/GetHrInterviews.tsx index d03aaf9..07ce254 100644 --- a/Frontend/src/Components/Interviews/HR/GetHrInterviews.tsx +++ b/Frontend/src/Components/Interviews/HR/GetHrInterviews.tsx @@ -1,40 +1,14 @@ import InterviewDisplay from "../Components/InterviewDisplay/InterviewDisplay"; import useGetLatestHrInterviews from "../../../Hooks/Interviews/HR/useGetLatestHrInterviews"; -import { Loader } from "@progress/kendo-react-indicators"; import { Card, CardBody } from "@progress/kendo-react-layout"; +import LoaderComponent from "../../Common/LoaderComponent"; +import ErrorComponent from "../../Common/ErrorComponent"; const GetHrInterviews = () => { const { data: hrInterviews, isLoading, isError } = useGetLatestHrInterviews(); - if (isLoading) { - return ( -
-
- - Loading HR Interview... -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load HR Interview -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( @@ -48,4 +22,4 @@ const GetHrInterviews = () => { ); }; -export default GetHrInterviews; +export default GetHrInterviews; \ No newline at end of file diff --git a/Frontend/src/Components/Interviews/Technical/GetTechnicalInterviews.tsx b/Frontend/src/Components/Interviews/Technical/GetTechnicalInterviews.tsx index b8e7e4a..28d7fb6 100644 --- a/Frontend/src/Components/Interviews/Technical/GetTechnicalInterviews.tsx +++ b/Frontend/src/Components/Interviews/Technical/GetTechnicalInterviews.tsx @@ -1,40 +1,14 @@ import InterviewDisplay from "../Components/InterviewDisplay/InterviewDisplay"; import useGetLatestTechnicalInterviews from "../../../Hooks/Interviews/Technical/useGetLatestTechnicalInterviews"; -import { Loader } from "@progress/kendo-react-indicators"; import { Card, CardBody } from "@progress/kendo-react-layout"; +import LoaderComponent from "../../Common/LoaderComponent"; +import ErrorComponent from "../../Common/ErrorComponent"; const GetTechnicalInterviews = () => { const { data: technicalInterviews, isLoading, isError } = useGetLatestTechnicalInterviews(); - if (isLoading) { - return ( -
-
- - Loading Technical Interview... -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load Technical Interview -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( @@ -48,4 +22,4 @@ const GetTechnicalInterviews = () => { ); }; -export default GetTechnicalInterviews; +export default GetTechnicalInterviews; \ No newline at end of file diff --git a/Frontend/src/Components/Statistics/Charts/PerformanceChart.tsx b/Frontend/src/Components/Statistics/Charts/PerformanceChart.tsx index ad995a1..633eda2 100644 --- a/Frontend/src/Components/Statistics/Charts/PerformanceChart.tsx +++ b/Frontend/src/Components/Statistics/Charts/PerformanceChart.tsx @@ -1,44 +1,15 @@ import { Chart, ChartLegend, ChartCategoryAxis, ChartCategoryAxisItem, ChartSeries, ChartSeriesItem, ChartTooltip } from "@progress/kendo-react-charts"; -import { Loader } from "@progress/kendo-react-indicators"; -import { Card, CardBody } from "@progress/kendo-react-layout"; import useGetPerformanceData from "../../../Hooks/Statistics/Charts/useGetPerformanceData"; import useFormatPerformanceData from "../../../Hooks/Statistics/Charts/useFormatPerformanceData"; +import LoaderComponent from "../../Common/LoaderComponent"; +import ErrorComponent from "../../Common/ErrorComponent"; const PerformanceChart = () => { const { data: performanceData, isLoading, isError } = useGetPerformanceData(); const { scores, categories, tooltipRender } = useFormatPerformanceData(performanceData || []); - if (isLoading) { - return ( -
-
- - - Loading Performance Data... - -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load Performance data -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( <> @@ -82,4 +53,4 @@ const PerformanceChart = () => { ); }; -export default PerformanceChart; +export default PerformanceChart; \ No newline at end of file diff --git a/Frontend/src/Components/Statistics/Charts/PositionChart.tsx b/Frontend/src/Components/Statistics/Charts/PositionChart.tsx index 220f41d..a08129c 100644 --- a/Frontend/src/Components/Statistics/Charts/PositionChart.tsx +++ b/Frontend/src/Components/Statistics/Charts/PositionChart.tsx @@ -1,44 +1,15 @@ import { Chart, ChartSeries, ChartSeriesItem, ChartLegend, ChartTooltip } from "@progress/kendo-react-charts"; -import { Loader } from "@progress/kendo-react-indicators"; import useGetPositionData from "../../../Hooks/Statistics/Charts/useGetPositionData"; -import { Card, CardBody } from "@progress/kendo-react-layout"; import useFormatPositionData from "../../../Hooks/Statistics/Charts/useFormatPositionData"; +import LoaderComponent from "../../Common/LoaderComponent"; +import ErrorComponent from "../../Common/ErrorComponent"; const PositionChart = () => { - const { data: positionData, isLoading, isError } = useGetPositionData(); const tooltipRender = useFormatPositionData(); - - if (isLoading) { - return ( -
-
- - - Loading Position Data... - -
-
- ); - } + const { data: positionData, isLoading, isError } = useGetPositionData(); - if (isError) { - return ( -
- - -
-

- Unable to load Position data -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( <> @@ -79,4 +50,4 @@ const PositionChart = () => { ); }; -export default PositionChart; +export default PositionChart; \ No newline at end of file diff --git a/Frontend/src/Components/Statistics/Charts/ProgrammingLanguageChart.tsx b/Frontend/src/Components/Statistics/Charts/ProgrammingLanguageChart.tsx index 2f9adbd..9b95417 100644 --- a/Frontend/src/Components/Statistics/Charts/ProgrammingLanguageChart.tsx +++ b/Frontend/src/Components/Statistics/Charts/ProgrammingLanguageChart.tsx @@ -1,44 +1,15 @@ import { Chart, ChartLegend, ChartCategoryAxis, ChartCategoryAxisItem, ChartSeries, ChartSeriesItem, ChartTooltip } from "@progress/kendo-react-charts"; -import { Loader } from "@progress/kendo-react-indicators"; -import { Card, CardBody } from "@progress/kendo-react-layout"; import useGetProgrammingLanguageData from "../../../Hooks/Statistics/Charts/useGetProgrammingLanguageData"; import useFormatProgrammingLanguageData from "../../../Hooks/Statistics/Charts/useFormatProgrammingLanguageData"; +import LoaderComponent from "../../Common/LoaderComponent"; +import ErrorComponent from "../../Common/ErrorComponent"; const ProgrammingLanguageChart = () => { const { data: programmingLanguageData, isLoading, isError } = useGetProgrammingLanguageData(); const { languages, sessions, tooltipRender } = useFormatProgrammingLanguageData(programmingLanguageData || []); - if (isLoading) { - return ( -
-
- - - Loading Programming Language Data... - -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load Programming Language data -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( <> diff --git a/Frontend/src/Components/Statistics/Grid/StatisticsGrid.tsx b/Frontend/src/Components/Statistics/Grid/StatisticsGrid.tsx index a75e9f0..da9c555 100644 --- a/Frontend/src/Components/Statistics/Grid/StatisticsGrid.tsx +++ b/Frontend/src/Components/Statistics/Grid/StatisticsGrid.tsx @@ -7,8 +7,8 @@ import { Grid, GridColumn, GridToolbar,type GridColumnMenuProps } from "@progres import { fileExcelIcon, fileCsvIcon } from "@progress/kendo-svg-icons"; import useHandleExcelExport from "../../../Hooks/Statistics/Grid/useHandleExcelExport"; import useGetRecentInterviewSessions from "../../../Hooks/InterviewSessions/useGetRecentInterviewSessions"; -import { Card, CardBody } from "@progress/kendo-react-layout"; -import { Loader } from "@progress/kendo-react-indicators"; +import LoaderComponent from "../../Common/LoaderComponent"; +import ErrorComponent from "../../Common/ErrorComponent"; const StatisticsGrid = () => { const { data: interviewSessionData, isLoading, isError } = useGetRecentInterviewSessions(); @@ -19,35 +19,8 @@ const StatisticsGrid = () => { ); - if (isLoading) { - return ( -
-
- - Loading Interview Data... -
-
- ); - } - - if (isError) { - return ( -
- - -
-

- Unable to load Interview data -

-

- Please try refreshing the page -

-
-
-
-
- ); - } + if (isLoading) return ; + if (isError) return ; return ( <> diff --git a/Frontend/src/Hooks/Home/ProfilePicture/useUploadProfilePicture.ts b/Frontend/src/Hooks/Home/ProfilePicture/useUploadProfilePicture.ts index 273aa5b..a321b0e 100644 --- a/Frontend/src/Hooks/Home/ProfilePicture/useUploadProfilePicture.ts +++ b/Frontend/src/Hooks/Home/ProfilePicture/useUploadProfilePicture.ts @@ -23,7 +23,7 @@ const useUploadProfilePicture = () => { formData.append("imageFile", file); await axios - .post(changeProfilePictureEndPoint, formData, { + .put(changeProfilePictureEndPoint, formData, { withCredentials: true, headers: { "Content-Type": "multipart/form-data", diff --git a/Frontend/src/Utils/endpoints.ts b/Frontend/src/Utils/endpoints.ts index 3b45356..a9eb9ad 100644 --- a/Frontend/src/Utils/endpoints.ts +++ b/Frontend/src/Utils/endpoints.ts @@ -4,11 +4,11 @@ const baseURL: string | undefined = process.env.REACT_APP_API_URL; export const uploadsFolderURL: string = process.env.UPLOADS_FOLDER_URL || "https://localhost:7227/Uploads/"; // Controllers -const authController: string | undefined = `${baseURL}/Auth`; -const userController: string | undefined = `${baseURL}/User`; -const profilePictureController: string | undefined = `${baseURL}/ProfilePicture`; -const interviewController: string | undefined = `${baseURL}/Interview`; -const interviewSessionController: string | undefined = `${baseURL}/InterviewSession`; +const authController: string | undefined = `${baseURL}/auth`; +const userController: string | undefined = `${baseURL}/users`; +const profilePictureController: string | undefined = `${baseURL}/profile-pictures`; +const interviewController: string | undefined = `${baseURL}/interviews`; +const interviewSessionController: string | undefined = `${baseURL}/interview-sessions`; // Auth export const registerEndPoint = `${authController}/register`; @@ -17,27 +17,27 @@ export const logoutEndPoint = `${authController}/logout`; export const refreshTokenEndPoint = `${authController}/refresh-token`; // User -export const getCurrentUserEndPoint = `${userController}/get-current-user`; -export const updateCurrentUserEndPoint = `${userController}/update-current-user`; -export const deleteCurrentUserEndPoint = `${userController}/delete-current-user`; +export const getCurrentUserEndPoint = `${userController}/me`; +export const updateCurrentUserEndPoint = `${userController}/me`; +export const deleteCurrentUserEndPoint = `${userController}/me`; // Profile Picture -export const getProfilePictureUrlEndPoint = `${profilePictureController}/get-profile-picture-url`; -export const changeProfilePictureEndPoint = `${profilePictureController}/change-profile-picture`; +export const getProfilePictureUrlEndPoint = `${profilePictureController}`; +export const changeProfilePictureEndPoint = `${profilePictureController}`; // Interviews -export const generateHrInterviewsEndPoint = `${interviewController}/generate-hr-interviews`; -export const getLatestHrInterviewsEndPoint = `${interviewController}/get-latest-hr-interviews`; -export const evaluateHrInterviewsEndPoint = `${interviewController}/evaluate-hr-interviews`; -export const generateTechnicalInterviewsEndPoint = `${interviewController}/generate-technical-interviews`; -export const getLatestTechnicalInterviewsEndPoint = `${interviewController}/get-latest-technical-interviews`; -export const evaluateTechnicalInterviewsEndPoint = `${interviewController}/evaluate-technical-interviews`; +export const generateHrInterviewsEndPoint = `${interviewController}/hr`; +export const getLatestHrInterviewsEndPoint = `${interviewController}/hr/latest`; +export const evaluateHrInterviewsEndPoint = `${interviewController}/hr/evaluations`; +export const generateTechnicalInterviewsEndPoint = `${interviewController}/technical`; +export const getLatestTechnicalInterviewsEndPoint = `${interviewController}/technical/latest`; +export const evaluateTechnicalInterviewsEndPoint = `${interviewController}/technical/evaluations`; // Interview Sessions -export const getInterviewSessionStatisticsEndPoint = `${interviewSessionController}/get-interview-session-statistics`; -export const getRecentInterviewSessionsEndPoint = `${interviewSessionController}/get-interview-session-activities`; -export const getInterviewSessionsPerformanceEndPoint = `${interviewSessionController}/get-interview-session-performance`; -export const getInterviewSessionsProgrammingLanguageDataEndPoint = `${interviewSessionController}/get-interview-session-programming-language-data`; -export const getInterviewSessionsPositionDataEndPoint = `${interviewSessionController}/get-interview-session-position-data`; -export const finishInterviewSessionEndPoint = `${interviewSessionController}/finish-interview-session`; -export const deleteInterviewSessionsEndPoint = `${interviewSessionController}/delete-interview-sessions`; \ No newline at end of file +export const getInterviewSessionStatisticsEndPoint = `${interviewSessionController}/statistics`; +export const getRecentInterviewSessionsEndPoint = `${interviewSessionController}/activities`; +export const getInterviewSessionsPerformanceEndPoint = `${interviewSessionController}/performance`; +export const getInterviewSessionsProgrammingLanguageDataEndPoint = `${interviewSessionController}/programming-languages`; +export const getInterviewSessionsPositionDataEndPoint = `${interviewSessionController}/positions`; +export const finishInterviewSessionEndPoint = `${interviewSessionController}/current/finish`; +export const deleteInterviewSessionsEndPoint = `${interviewSessionController}`; \ No newline at end of file