diff --git a/pom.xml b/pom.xml index d5e071f9..cf9d3292 100644 --- a/pom.xml +++ b/pom.xml @@ -1,96 +1,153 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.0.6 - - - com.medeiros - SPRINGProject - 0.0.1-SNAPSHOT - SPRINGProject - Demo project for Spring Boot - - 20 - - 6.0.3 - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - com.mysql - mysql-connector-j - runtime - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-thymeleaf - 3.0.6 - - - - org.springframework.boot - spring-boot-starter-security - - - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - - - - - org.springframework.security - spring-security-core - 6.0.3 - - - - - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - runtime - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.6 + + + + com.medeiros + SPRINGProject + 0.0.1-SNAPSHOT + SPRINGProject + Demo project for Spring Boot + + 20 + + 6.0.3 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.mysql + mysql-connector-j + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-thymeleaf + 3.0.6 + + + org.springframework.boot + spring-boot-starter-security + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + + org.springframework.security + spring-security-core + 6.0.3 + + + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + + + + io.spring.javaformat + spring-javaformat-formatter + 0.0.40 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + + prepare-agent + + + + report + test + + report + + + coverageReport + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.2.5 + + testReport + + + + + org.apache.maven.plugins + maven-site-plugin + 2.1 + + testReport + + + + + io.spring.javaformat + spring-javaformat-maven-plugin + 0.0.40 + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/medeiros/SPRINGProject/Controllers/AuthControllerCreateAuserTest.java b/src/test/java/com/medeiros/SPRINGProject/Controllers/AuthControllerCreateAuserTest.java new file mode 100644 index 00000000..4a78bb84 --- /dev/null +++ b/src/test/java/com/medeiros/SPRINGProject/Controllers/AuthControllerCreateAuserTest.java @@ -0,0 +1,632 @@ + +// ********RoostGPT******** +/* +Test generated by RoostGPT for test maven-music-github using AI Type Azure Open AI and AI Model gpt-5 + +ROOST_METHOD_HASH=createAuser_43c81bbbba +ROOST_METHOD_SIG_HASH=createAuser_2684b4910b + +Scenario 1: Mismatched passwords should return the mismatch message and avoid persistence + +Details: + TestName: returnsMismatchMessageWhenPasswordsDiffer + Description: Verifies that AuthController.createAuser returns "Senhas não batem!" when password and confirmpassword differ, and ensures no user credentials are saved and no log entry is written. + +Execution: + Arrange: Create an instance of AuthController. Provide mocked UserAccRepository and LogRepository. Leave other unused dependencies as mocks or null as appropriate. + Act: Call createAuser with email="user@example.com", password="abc123", confirmpassword="ABC123", username="tester". + Assert: Assert the returned string equals "Senhas não batem!". Verify that UserAccRepo.save is never called and Log.save is never called. + +Validation: + Confirms that the guard clause using Objects.equals enforces exact equality and prevents side effects (no save or log) when passwords do not match. This preserves data integrity by not creating inconsistent user records. + + +Scenario 2: Matching passwords should return success and persist both user and log + +Details: + TestName: returnsSuccessAndPersistsWhenPasswordsMatch + Description: Ensures that when password equals confirmpassword, AuthController.createAuser returns "Sucesso", saves a User_Credentials to UserAccRepo, and saves a corresponding LogModel with the expected values to Log. + +Execution: + Arrange: Create AuthController with mocked UserAccRepository and LogRepository. Replace the Date field (LogModel instance) with a spy or stub that returns a fixed timestamp (e.g., "2026-01-01T00:00:00Z") from getTimeNow (use reflection to set the private field if needed). + Act: Call createAuser with email="user@example.com", password="abc123", confirmpassword="abc123", username="tester". + Assert: Assert the returned string equals "Sucesso". Verify UserAccRepo.save is called exactly once with a User_Credentials constructed from the provided email, password, and username. Capture the argument passed to Log.save and verify it is a LogModel created with action="createUser01", entity="User", and the timestamp returned by Date.getTimeNow. + +Validation: + Confirms the happy path behavior: user creation is attempted and a log entry is written with the exact action and entity labels and the controller’s timestamp source. This validates both the result message and critical side effects. + + +Scenario 3: Both password and confirmpassword null should be treated as equal and succeed + +Details: + TestName: allowsNullPasswordsWhenBothNull + Description: Validates that Objects.equals treats two null values as equal, allowing the method to proceed with saving and logging, returning "Sucesso". + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Spy/stub the Date.getTimeNow to return a fixed value. + Act: Call createAuser with email="user@example.com", password=null, confirmpassword=null, username="tester". + Assert: Assert the returned string equals "Sucesso". Verify UserAccRepo.save and Log.save are each called exactly once. + +Validation: + Ensures the method relies strictly on Objects.equals semantics for equality, and does not block creation when both password fields are null. This documents current behavior for null handling. + + +Scenario 4: One null password and one non-null should return the mismatch message + +Details: + TestName: returnsMismatchWhenOnePasswordIsNull + Description: Checks that when one of the password values is null and the other is not, the method returns "Senhas não batem!" and performs no persistence. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. + Act: Call createAuser with email="user@example.com", password=null, confirmpassword="abc123", username="tester" (and vice versa in a separate assertion if desired). + Assert: Assert the returned string equals "Senhas não batem!". Verify no calls to UserAccRepo.save or Log.save. + +Validation: + Verifies robust mismatch detection for asymmetric null values, preventing unintended user creation. + + +Scenario 5: Equal empty-string passwords should succeed + +Details: + TestName: allowsEmptyPasswordsWhenBothEmpty + Description: Ensures that empty strings are considered equal when both password and confirmpassword are "", resulting in a successful save-and-log and "Sucesso" return. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Stub Date.getTimeNow. + Act: Call createAuser with email="user@example.com", password="", confirmpassword="", username="tester". + Assert: Assert the returned string equals "Sucesso". Verify UserAccRepo.save and Log.save are called exactly once each. + +Validation: + Documents that the controller applies no non-emptiness validation to passwords and proceeds based purely on equality. + + +Scenario 6: Case-sensitive mismatch in passwords should fail + +Details: + TestName: treatsCaseDifferenceAsMismatch + Description: Verifies that password comparison is case-sensitive by returning "Senhas não batem!" when values differ by case only. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. + Act: Call createAuser with password="Password123" and confirmpassword="password123". + Assert: Assert the returned string equals "Senhas não batem!". Verify no calls to UserAccRepo.save or Log.save. + +Validation: + Clarifies that exact string equality (including case) is required, which is important for security expectations. + + +Scenario 7: Null email with matching passwords should still return success and persist + +Details: + TestName: savesEvenWhenEmailIsNullIfPasswordsMatch + Description: Confirms that the controller does not validate the email value; if passwords match, it returns "Sucesso" and performs save and log operations. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Stub Date.getTimeNow. + Act: Call createAuser with email=null, password="abc123", confirmpassword="abc123", username="tester". + Assert: Assert the returned string equals "Sucesso". Verify single calls to UserAccRepo.save and Log.save. + +Validation: + Highlights that input validation for email is not enforced by the controller. This scenario captures the current behavior for upstream/downstream validation considerations. + + +Scenario 8: Null username with matching passwords should still return success and persist + +Details: + TestName: savesEvenWhenUsernameIsNullIfPasswordsMatch + Description: Ensures the controller does not validate username; with matching passwords, it proceeds to save and log, returning "Sucesso". + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Stub Date.getTimeNow. + Act: Call createAuser with email="user@example.com", password="abc123", confirmpassword="abc123", username=null. + Assert: Assert the returned string equals "Sucesso". Verify calls to UserAccRepo.save and Log.save occur exactly once each. + +Validation: + Documents the absence of username validation and that persistence proceeds solely on password equality. + + +Scenario 9: Exception during user save should propagate and prevent logging + +Details: + TestName: propagatesExceptionWhenUserSaveFails + Description: Validates that if UserAccRepo.save throws a runtime exception, the method does not catch it, does not attempt to log, and propagates the exception. + +Execution: + Arrange: Mock UserAccRepository to throw a RuntimeException on save. Mock LogRepository. Stub Date.getTimeNow if needed. + Act: Invoke createAuser with matching passwords and expect an exception. + Assert: Assert that a RuntimeException is thrown. Verify Log.save is never called. + +Validation: + Confirms lack of error-handling within the method and ensures logging is not attempted after a failed persistence operation, preserving consistency. + + +Scenario 10: Exception during log save should propagate after successful user save + +Details: + TestName: propagatesExceptionWhenLogSaveFails + Description: Ensures that if Log.save throws a runtime exception, the exception is propagated and the method does not return "Sucesso", while confirming the user was saved before the log attempt. + +Execution: + Arrange: Mock UserAccRepository to save without error. Mock LogRepository to throw RuntimeException on save. Stub Date.getTimeNow. + Act: Call createAuser with matching passwords and expect an exception. + Assert: Assert that a RuntimeException is thrown. Verify UserAccRepo.save is called once and Log.save is called once. + +Validation: + Verifies operation order and error propagation: user persistence happens before logging, and a logging failure interrupts the response. + + +Scenario 11: Ensure user is saved before log entry is written + +Details: + TestName: savesUserBeforeWritingLog + Description: Confirms the sequencing of operations: save to UserAccRepo occurs before Log.save. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Use an interaction sequence verifier (e.g., in-order verification). + Act: Call createAuser with matching passwords. + Assert: Verify that the call to UserAccRepo.save occurs before the call to Log.save. + +Validation: + Ensures the intended order of side effects, which can be important for audit trails and consistency. + + +Scenario 12: Timestamp is obtained once from the controller’s Date field on success + +Details: + TestName: requestsTimestampFromDateOnceOnSuccess + Description: Validates that Date.getTimeNow is invoked exactly once when the success path is taken. + +Execution: + Arrange: Replace the Date field with a spy/stubbed LogModel whose getTimeNow returns a fixed value and can be verified for invocation count. + Act: Call createAuser with matching passwords. + Assert: Verify getTimeNow is invoked exactly once. + +Validation: + Confirms that the controller fetches the timestamp a single time per operation, aligning with performance and correctness expectations. + + +Scenario 13: Unused dependencies are not interacted with during create + +Details: + TestName: doesNotUseUnusedDependenciesDuringCreate + Description: Ensures that JwtUtil and UserInfoRepository are not used by createAuser. + +Execution: + Arrange: Provide mocks for JwtUtil and UserInfoRepository (if injected or set), alongside necessary mocks for UserAccRepository and LogRepository. + Act: Call createAuser with matching passwords. + Assert: Verify there are no interactions with JwtUtil and UserInfoRepo. + +Validation: + Documents that createAuser is isolated to user credential persistence and logging, helping prevent unintended coupling. + + +Scenario 14: Repeated calls with identical inputs consistently succeed and trigger repeated persistence + +Details: + TestName: returnsSuccessOnRepeatedCallsWithSameInputs + Description: Verifies idempotent response (not idempotent side effects) with repeated identical inputs: each call returns "Sucesso" and triggers its own save and log. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Stub Date.getTimeNow to return deterministic or sequential values if needed. + Act: Call createAuser twice with the same email, password, confirmpassword, and username. + Assert: Assert both calls return "Sucesso". Verify UserAccRepo.save is called twice and Log.save is called twice. + +Validation: + Shows that the controller does not enforce uniqueness or deduplication and that each call results in persistence and logging. + + +Scenario 15: Accepts very long input strings when passwords match + +Details: + TestName: acceptsVeryLongInputsWhenPasswordsMatch + Description: Ensures the method handles large input sizes without additional validation, returning "Sucesso" and performing persistence and logging. + +Execution: + Arrange: Construct very long strings for email, password, and username; ensure password equals confirmpassword. Mock UserAccRepository and LogRepository. + Act: Call createAuser with the long inputs. + Assert: Assert the returned string equals "Sucesso". Verify UserAccRepo.save and Log.save are called exactly once each. + +Validation: + Demonstrates that the controller imposes no length constraints and relies on downstream components for such validation. + + +Scenario 16: Leading/trailing whitespace in equal passwords still succeeds + +Details: + TestName: treatsWhitespaceAsSignificantInPasswordMatching + Description: Confirms that the controller does not trim passwords; equal strings with whitespace pass and result in success. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. + Act: Call createAuser with password=" pass " and confirmpassword=" pass ". + Assert: Assert the returned string equals "Sucesso". Verify UserAccRepo.save and Log.save each invoked once. + +Validation: + Captures current behavior that whitespace is treated as part of the password, important for avoiding silent data transformations. + + +Scenario 17: Special characters in credentials are accepted when passwords match + +Details: + TestName: acceptsSpecialCharactersWhenPasswordsMatch + Description: Ensures the method handles special characters (e.g., symbols, Unicode) in email, password, and username, provided passwords match. + +Execution: + Arrange: Mock UserAccRepository and LogRepository. Stub Date.getTimeNow. + Act: Call createAuser with email="üser+alias@example-domain.com", password="p@$$w0rd!✓", confirmpassword="p@$$w0rd!✓", username="téßt_user-✓". + Assert: Assert the returned string equals "Sucesso". Verify one call to UserAccRepo.save and one call to Log.save. + +Validation: + Demonstrates that the controller does not constrain character sets and properly delegates to persistence and logging when passwords match. + + +Scenario 18: Log content uses exact action and entity constants + +Details: + TestName: writesLogWithExpectedActionAndEntity + Description: Focuses on verifying that the created log entry uses the exact strings "createUser01" for action and "User" for entity, alongside the timestamp from Date.getTimeNow. + +Execution: + Arrange: Mock LogRepository with an argument captor for LogModel. Replace Date with a spy/stub returning a known timestamp. Mock UserAccRepository to accept saves. + Act: Call createAuser with matching passwords. + Assert: Capture the LogModel passed to Log.save and verify it was created with action="createUser01", entity="User", and the stubbed timestamp. + +Validation: + Ensures audit log semantics are precise and unchanged, which is critical for traceability and monitoring. + +*/ + +// ********RoostGPT******** + +package com.medeiros.SPRINGProject.Controllers; + +import com.medeiros.SPRINGProject.Models.*; +import com.medeiros.SPRINGProject.Security.JwtUtil; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.bind.annotation.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.Objects; + +@ExtendWith(MockitoExtension.class) +public class AuthControllerCreateAuserTest { + + private AuthController controller; + + @Mock + private UserAccRepository userAccRepository; + + @Mock + private UserInfoRepository userInfoRepository; + + @Mock + private LogRepository logRepository; + + @Mock + private JwtUtil jwtUtil; + + private static final String FIXED_TIMESTAMP = "2026-01-01T00:00:00Z"; // TODO adjust + // if project + // expectations + // change + + @BeforeEach + public void setUp() throws Exception { + controller = new AuthController(); + setPrivateField(controller, "UserAccRepo", userAccRepository); + setPrivateField(controller, "Log", logRepository); + setPrivateField(controller, "JwtToken", jwtUtil); + setPrivateField(controller, "UserInfoRepo", userInfoRepository); + // Default Date field as a real LogModel; individual tests can override with a spy + setPrivateField(controller, "Date", new LogModel()); + } + + @Test + @Tag("invalid") + public void returnsMismatchMessageWhenPasswordsDiffer() { + String result = controller.createAuser("user@example.com", "abc123", "ABC123", "tester"); + assertEquals((String) "Senhas não batem!", (String) result); + verify(userAccRepository, never()).save(any(User_Credentials.class)); + verify(logRepository, never()).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void returnsSuccessAndPersistsWhenPasswordsMatch() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String email = "user@example.com"; + String password = "abc123"; + String username = "tester"; + String result = controller.createAuser(email, password, password, username); + assertEquals((String) "Sucesso", (String) result); + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User_Credentials.class); + ArgumentCaptor logCaptor = ArgumentCaptor.forClass(LogModel.class); + verify(userAccRepository, times(1)).save(userCaptor.capture()); + verify(logRepository, times(1)).save(logCaptor.capture()); + // Verify user content via reflection-based search of String fields + assertTrue(objectContainsStringValue(userCaptor.getValue(), (String) email)); + assertTrue(objectContainsStringValue(userCaptor.getValue(), (String) password)); + assertTrue(objectContainsStringValue(userCaptor.getValue(), (String) username)); + // Verify log content: action, entity, timestamp + LogModel capturedLog = logCaptor.getValue(); + assertNotNull(capturedLog); + assertTrue(objectContainsStringValue(capturedLog, (String) "createUser01")); + assertTrue(objectContainsStringValue(capturedLog, (String) "User")); + assertTrue(objectContainsStringValue(capturedLog, (String) FIXED_TIMESTAMP)); + } + + @Test + @Tag("valid") + public void allowsNullPasswordsWhenBothNull() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String result = controller.createAuser("user@example.com", null, null, "tester"); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("invalid") + public void returnsMismatchWhenOnePasswordIsNull() { + String result1 = controller.createAuser("user@example.com", null, "abc123", "tester"); + assertEquals((String) "Senhas não batem!", (String) result1); + verify(userAccRepository, never()).save(any(User_Credentials.class)); + verify(logRepository, never()).save(any(LogModel.class)); + String result2 = controller.createAuser("user@example.com", "abc123", null, "tester"); + assertEquals((String) "Senhas não batem!", (String) result2); + verify(userAccRepository, never()).save(any(User_Credentials.class)); + verify(logRepository, never()).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void allowsEmptyPasswordsWhenBothEmpty() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String result = controller.createAuser("user@example.com", "", "", "tester"); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("invalid") + public void treatsCaseDifferenceAsMismatch() { + String result = controller.createAuser("user@example.com", "Password123", "password123", "tester"); + assertEquals((String) "Senhas não batem!", (String) result); + verify(userAccRepository, never()).save(any(User_Credentials.class)); + verify(logRepository, never()).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void savesEvenWhenEmailIsNullIfPasswordsMatch() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String result = controller.createAuser(null, "abc123", "abc123", "tester"); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void savesEvenWhenUsernameIsNullIfPasswordsMatch() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String result = controller.createAuser("user@example.com", "abc123", "abc123", null); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("integration") + public void propagatesExceptionWhenUserSaveFails() throws Exception { + doThrow(new RuntimeException("DB save error")).when(userAccRepository).save(any(User_Credentials.class)); + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + assertThrows(RuntimeException.class, + () -> controller.createAuser("user@example.com", "abc123", "abc123", "tester")); + verify(logRepository, never()).save(any(LogModel.class)); + } + + @Test + @Tag("integration") + public void propagatesExceptionWhenLogSaveFails() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + doThrow(new RuntimeException("Log save error")).when(logRepository).save(any(LogModel.class)); + assertThrows(RuntimeException.class, + () -> controller.createAuser("user@example.com", "abc123", "abc123", "tester")); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("integration") + public void savesUserBeforeWritingLog() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + controller.createAuser("user@example.com", "abc123", "abc123", "tester"); + InOrder inOrder = inOrder(userAccRepository, logRepository); + inOrder.verify(userAccRepository).save(any(User_Credentials.class)); + inOrder.verify(logRepository).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void requestsTimestampFromDateOnceOnSuccess() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + controller.createAuser("user@example.com", "abc123", "abc123", "tester"); + verify(dateSpy, times(1)).getTimeNow(); + } + + @Test + @Tag("valid") + public void doesNotUseUnusedDependenciesDuringCreate() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + controller.createAuser("user@example.com", "abc123", "abc123", "tester"); + verifyNoInteractions(jwtUtil); + verifyNoInteractions(userInfoRepository); + } + + @Test + @Tag("valid") + public void returnsSuccessOnRepeatedCallsWithSameInputs() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn("2026-01-01T00:00:00Z", "2026-01-01T00:00:01Z").when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String res1 = controller.createAuser("user@example.com", "abc123", "abc123", "tester"); + String res2 = controller.createAuser("user@example.com", "abc123", "abc123", "tester"); + assertEquals((String) "Sucesso", (String) res1); + assertEquals((String) "Sucesso", (String) res2); + verify(userAccRepository, times(2)).save(any(User_Credentials.class)); + verify(logRepository, times(2)).save(any(LogModel.class)); + } + + @Test + @Tag("boundary") + public void acceptsVeryLongInputsWhenPasswordsMatch() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String longEmail = generateLongString("user", 1000) + "@example.com"; + String longPassword = generateLongString("p", 2000); + String longUsername = generateLongString("u", 1500); + String result = controller.createAuser(longEmail, longPassword, longPassword, longUsername); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void treatsWhitespaceAsSignificantInPasswordMatching() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String result = controller.createAuser("user@example.com", " pass ", " pass ", "tester"); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void acceptsSpecialCharactersWhenPasswordsMatch() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + String email = "üser+alias@example-domain.com"; + String password = "p@$w0rd!✓"; + String username = "téßt_user-✓"; + String result = controller.createAuser(email, password, password, username); + assertEquals((String) "Sucesso", (String) result); + verify(userAccRepository, times(1)).save(any(User_Credentials.class)); + verify(logRepository, times(1)).save(any(LogModel.class)); + } + + @Test + @Tag("valid") + public void writesLogWithExpectedActionAndEntity() throws Exception { + LogModel dateSpy = Mockito.spy(new LogModel()); + doReturn(FIXED_TIMESTAMP).when(dateSpy).getTimeNow(); + setPrivateField(controller, "Date", dateSpy); + controller.createAuser("user@example.com", "abc123", "abc123", "tester"); + ArgumentCaptor logCaptor = ArgumentCaptor.forClass(LogModel.class); + verify(logRepository, times(1)).save(logCaptor.capture()); + LogModel capturedLog = logCaptor.getValue(); + assertNotNull(capturedLog); + assertTrue(objectContainsStringValue(capturedLog, (String) "createUser01")); + assertTrue(objectContainsStringValue(capturedLog, (String) "User")); + assertTrue(objectContainsStringValue(capturedLog, (String) FIXED_TIMESTAMP)); + } + + // Helper methods + private static void setPrivateField(Object target, String fieldName, Object value) throws Exception { + Field field = getFieldRecursive(target.getClass(), fieldName); + field.setAccessible(true); + field.set(target, value); + } + + private static Field getFieldRecursive(Class cls, String fieldName) throws NoSuchFieldException { + Class current = cls; + while (current != null) { + try { + return current.getDeclaredField(fieldName); + } + catch (NoSuchFieldException ignored) { + current = current.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + private static boolean objectContainsStringValue(Object obj, String expected) { + if (obj == null || expected == null) + return false; + List values = getAllStringFieldValues(obj); + for (String v : values) { + if (expected.equals(v)) { + return true; + } + } + return false; + } + + private static List getAllStringFieldValues(Object obj) { + List values = new ArrayList<>(); + Class current = obj.getClass(); + while (current != null) { + Field[] fields = current.getDeclaredFields(); + for (Field f : fields) { + if (f.getType() == String.class) { + try { + f.setAccessible(true); + Object val = f.get(obj); + if (val instanceof String) { + values.add((String) val); + } + } + catch (IllegalAccessException ignored) { + } + } + } + current = current.getSuperclass(); + } + return values; + } + + private static String generateLongString(String seed, int repeat) { + StringBuilder sb = new StringBuilder(seed.length() * repeat); + for (int i = 0; i < repeat; i++) { + sb.append(seed); + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/medeiros/SPRINGProject/Controllers/AuthControllerLoginUserTest.java b/src/test/java/com/medeiros/SPRINGProject/Controllers/AuthControllerLoginUserTest.java new file mode 100644 index 00000000..fadeebf4 --- /dev/null +++ b/src/test/java/com/medeiros/SPRINGProject/Controllers/AuthControllerLoginUserTest.java @@ -0,0 +1,622 @@ + +// ********RoostGPT******** +/* +Test generated by RoostGPT for test maven-music-github using AI Type Azure Open AI and AI Model gpt-5 + +ROOST_METHOD_HASH=loginUser_d19666d97a +ROOST_METHOD_SIG_HASH=loginUser_8fe76c816e + +Scenario 1: Successful login when email exists and password matches exactly + +Details: + TestName: successfulLoginReturnsLogado + Description: Verifies that when a user is found by email and the provided password matches the stored password exactly, the method returns the success message "Logado". + +Execution: + Arrange: Set up an AuthController instance with UserAccRepo injected as a mock. Configure the mock so that UserAccRepo.findUserByEmail("user@example.com") returns a User_Credentials constructed with email "user@example.com", password "P@ssw0rd", and a username (any valid string). + Act: Invoke loginUser("user@example.com", "P@ssw0rd"). + Assert: Assert that the returned value equals "Logado". + +Validation: + Confirming the return value verifies the happy-path behavior: the method checks the repository result, compares passwords using Objects.equals, and returns the exact success string when credentials match. This ensures the core login success flow behaves as intended. + + +Scenario 2: Incorrect password for existing email + +Details: + TestName: wrongPasswordReturnsSenhaIncorreta + Description: Ensures that when a user is found by email but the provided password does not match the stored password, the method returns "Senha incorreta". + +Execution: + Arrange: Inject a mock UserAccRepo into AuthController. Configure UserAccRepo.findUserByEmail("user@example.com") to return a User_Credentials with password "Correct123". + Act: Call loginUser("user@example.com", "Wrong123"). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + The assertion verifies that the method correctly detects a password mismatch via Objects.equals and responds with the predefined incorrect password message, reflecting expected authentication failure behavior. + + +Scenario 3: Email not found in repository + +Details: + TestName: unknownEmailReturnsEmailNaoEncontrado + Description: Validates that when the repository returns null for a provided email, the method responds with "Email não encontrado!". + +Execution: + Arrange: Mock UserAccRepo in AuthController so that UserAccRepo.findUserByEmail("missing@example.com") returns null. + Act: Call loginUser("missing@example.com", "anyPassword"). + Assert: Assert that the returned value equals "Email não encontrado!". + +Validation: + This confirms the method’s null-check branch and ensures that unknown accounts produce the exact "Email não encontrado!" message, aligning with user feedback expectations. + + +Scenario 4: Stored and provided passwords are both null + +Details: + TestName: nullStoredAndNullProvidedPasswordReturnsLogado + Description: Checks the edge case where the stored password is null and the provided password is also null. Because Objects.equals(null, null) is true, the method should return "Logado". + +Execution: + Arrange: Configure UserAccRepo.findUserByEmail("user@example.com") to return a User_Credentials whose password is null. + Act: Invoke loginUser("user@example.com", null). + Assert: Assert that the returned value equals "Logado". + +Validation: + This tests how Objects.equals handles null values. It verifies that a null stored password is treated as equal to a null provided password, producing a successful login outcome per the current logic. + + +Scenario 5: Stored password is null but provided password is non-null + +Details: + TestName: nullStoredPasswordWithNonNullProvidedReturnsSenhaIncorreta + Description: Ensures that when the stored password is null and the provided password is a non-null value, the method returns "Senha incorreta". + +Execution: + Arrange: Set up UserAccRepo to return a User_Credentials with password null for "user@example.com". + Act: Call loginUser("user@example.com", "NonNullPassword"). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + Confirms that Objects.equals(null, "something") evaluates to false and that the method returns the incorrect password message in this mixed null case. + + +Scenario 6: Stored password is non-null but provided password is null + +Details: + TestName: nonNullStoredPasswordWithNullProvidedReturnsSenhaIncorreta + Description: Validates that when the stored password has a value and the provided password is null, the method returns "Senha incorreta". + +Execution: + Arrange: Configure UserAccRepo to return a User_Credentials with password "abc123" for "user@example.com". + Act: Invoke loginUser("user@example.com", null). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + Ensures that a null provided password does not match a non-null stored password and the expected failure message is returned. + + +Scenario 7: Empty string password matches empty string stored + +Details: + TestName: emptyPasswordMatchingStoredEmptyReturnsLogado + Description: Tests that when the stored password is an empty string and the provided password is also an empty string, the method returns "Logado". + +Execution: + Arrange: Mock UserAccRepo so that it returns a User_Credentials with password "" for "user@example.com". + Act: Call loginUser("user@example.com", ""). + Assert: Assert that the returned value equals "Logado". + +Validation: + Verifies that empty strings are treated as valid values and that equality on empty strings results in a successful login response. + + +Scenario 8: Provided empty string password does not match non-empty stored password + +Details: + TestName: emptyPasswordAgainstNonEmptyStoredReturnsSenhaIncorreta + Description: Ensures that an empty provided password is considered incorrect when the stored password is non-empty. + +Execution: + Arrange: Configure UserAccRepo to return a User_Credentials with password "nonEmpty" for "user@example.com". + Act: Invoke loginUser("user@example.com", ""). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + Confirms that empty input does not falsely authenticate against a non-empty stored password. + + +Scenario 9: Null email leads to not found result + +Details: + TestName: nullEmailReturnsEmailNaoEncontrado + Description: Validates behavior when the provided email is null and the repository does not find a user (returns null), expecting "Email não encontrado!". + +Execution: + Arrange: Mock UserAccRepo so that UserAccRepo.findUserByEmail(null) returns null. + Act: Call loginUser(null, "anyPassword"). + Assert: Assert that the returned value equals "Email não encontrado!". + +Validation: + Ensures the method safely handles null email parameters by relying on repository output and returning the not-found message when no user is found. + + +Scenario 10: Empty or whitespace-only email results in not found + +Details: + TestName: whitespaceEmailReturnsEmailNaoEncontrado + Description: Tests that an empty or whitespace-only email does not match any user and yields "Email não encontrado!". + +Execution: + Arrange: Configure UserAccRepo to return null for UserAccRepo.findUserByEmail(" "). + Act: Invoke loginUser(" ", "password"). + Assert: Assert that the returned value equals "Email não encontrado!". + +Validation: + This scenario confirms that the method does not trim or alter input and depends strictly on the repository to find the user. Without a match, it properly returns the not-found message. + + +Scenario 11: Email case sensitivity results in not found when case differs + +Details: + TestName: emailCaseDifferenceReturnsEmailNaoEncontrado + Description: Ensures that if the repository is case-sensitive and the provided email uses different casing, the user is not found and the method returns "Email não encontrado!". + +Execution: + Arrange: Mock UserAccRepo so that UserAccRepo.findUserByEmail("User@Example.com") returns null, while the stored user would exist for "user@example.com" (but that method is not invoked in this test). + Act: Call loginUser("User@Example.com", "P@ssw0rd"). + Assert: Assert that the returned value equals "Email não encontrado!". + +Validation: + Confirms that no normalization is performed by the controller and that lookups rely on exact matches as provided by the repository outcome. + + +Scenario 12: Password case sensitivity yields incorrect password + +Details: + TestName: passwordCaseDifferenceReturnsSenhaIncorreta + Description: Validates that a password with different letter casing does not match and results in "Senha incorreta". + +Execution: + Arrange: Configure UserAccRepo to return a User_Credentials with password "SecretPass" for "user@example.com". + Act: Invoke loginUser("user@example.com", "secretpass"). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + Demonstrates that password comparisons are case-sensitive and exact, matching Objects.equals behavior. + + +Scenario 13: Password with trailing whitespace does not match + +Details: + TestName: trailingWhitespaceInPasswordReturnsSenhaIncorreta + Description: Checks that a provided password with trailing whitespace is not considered equal to a stored password without whitespace. + +Execution: + Arrange: Mock UserAccRepo to return a User_Credentials with password "pass123" for "user@example.com". + Act: Call loginUser("user@example.com", "pass123 "). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + Ensures no trimming or normalization is applied to the password, preserving strict equality behavior. + + +Scenario 14: International characters in credentials with exact match succeed + +Details: + TestName: unicodeCredentialsMatchingReturnLogado + Description: Verifies that credentials containing non-ASCII characters (e.g., accents, emojis) authenticate successfully when they match exactly. + +Execution: + Arrange: Set UserAccRepo to return a User_Credentials with email "usér@exámple.com" and password "päss🔒". + Act: Invoke loginUser("usér@exámple.com", "päss🔒"). + Assert: Assert that the returned value equals "Logado". + +Validation: + Confirms the method relies on standard Java string equality, which supports Unicode, enabling correct handling of internationalized credentials. + + +Scenario 15: Extremely long password matches successfully + +Details: + TestName: longPasswordMatchingReturnsLogado + Description: Ensures that very long passwords are handled by the method and, when matching exactly, result in "Logado". + +Execution: + Arrange: Configure UserAccRepo to return a User_Credentials with a very long password string (e.g., thousands of characters) for "user@example.com". + Act: Call loginUser("user@example.com", the same long password). + Assert: Assert that the returned value equals "Logado". + +Validation: + Validates correct behavior with large input sizes and that equality checks do not truncate or alter long strings. + + +Scenario 16: SQL injection-like strings do not authenticate if not found + +Details: + TestName: sqlInjectionLikeEmailReturnsEmailNaoEncontrado + Description: Verifies that an email resembling an SQL injection payload does not authenticate when the repository does not find a matching user. + +Execution: + Arrange: Mock UserAccRepo so that UserAccRepo.findUserByEmail("user@example.com' OR '1'='1") returns null. + Act: Invoke loginUser("user@example.com' OR '1'='1", "irrelevant"). + Assert: Assert that the returned value equals "Email não encontrado!". + +Validation: + Demonstrates that input is treated as a literal string, and without a repository match, the not-found message is returned. It highlights that sanitization is not performed at this layer. + + +Scenario 17: Repository throws a runtime exception and method propagates it + +Details: + TestName: repositoryFailurePropagatesException + Description: Ensures that if UserAccRepo.findUserByEmail throws a runtime exception (e.g., due to a database failure), the method does not catch it and the exception propagates. + +Execution: + Arrange: Configure UserAccRepo mock so that calling findUserByEmail("user@example.com") throws a RuntimeException. + Act: Invoke loginUser("user@example.com", "anyPassword") and capture the thrown exception. + Assert: Assert that a RuntimeException is thrown. + +Validation: + Confirms the method contains no exception handling around repository calls and validates the behavior under infrastructure failures, aiding in understanding error propagation. + + +Scenario 18: Uninitialized repository causes a NullPointerException + +Details: + TestName: nullRepositoryReferenceCausesNullPointerException + Description: Verifies that if AuthController.UserAccRepo is not initialized (null), invoking loginUser results in a NullPointerException. + +Execution: + Arrange: Create an AuthController instance without injecting UserAccRepo (leave it null). + Act: Call loginUser("user@example.com", "password") and observe the outcome. + Assert: Assert that a NullPointerException is thrown. + +Validation: + Highlights the importance of dependency injection and proper setup for the controller, reflecting expected failure when required dependencies are missing. + + +Scenario 19: Multiple consecutive successful calls return consistent results + +Details: + TestName: repeatedSuccessfulCallsReturnLogadoConsistently + Description: Ensures that calling loginUser multiple times with the same valid credentials consistently returns "Logado", indicating stateless behavior for this method. + +Execution: + Arrange: Mock UserAccRepo to return a User_Credentials with password "Again123" for "user@example.com". + Act: Call loginUser("user@example.com", "Again123") twice in succession. + Assert: Assert that both calls return "Logado". + +Validation: + Confirms consistent, deterministic behavior across repeated invocations and that no hidden state affects outcomes. + + +Scenario 20: Whitespace-only password matches stored whitespace-only password + +Details: + TestName: whitespaceOnlyPasswordMatchingReturnsLogado + Description: Validates that a password consisting solely of whitespace characters matches if the stored password is identical whitespace. + +Execution: + Arrange: Configure UserAccRepo to return a User_Credentials with password " " (three spaces) for "user@example.com". + Act: Invoke loginUser("user@example.com", " "). + Assert: Assert that the returned value equals "Logado". + +Validation: + Confirms that the method uses literal string equality without trimming and that whitespace-only values are treated as valid matching strings. + + +Scenario 21: Email with leading/trailing spaces does not match if repository expects exact match + +Details: + TestName: emailWithSurroundingSpacesReturnsEmailNaoEncontrado + Description: Checks that an email with leading or trailing spaces will not find a user when the repository expects exact, untrimmed input, resulting in "Email não encontrado!". + +Execution: + Arrange: Mock UserAccRepo so that UserAccRepo.findUserByEmail(" user@example.com ") returns null. + Act: Call loginUser(" user@example.com ", "P@ssw0rd"). + Assert: Assert that the returned value equals "Email não encontrado!". + +Validation: + Verifies no trimming is applied to input and underscores the need for the caller or earlier layers to normalize inputs if desired. + + +Scenario 22: Null email with repository returning a user still respects password check + +Details: + TestName: nullEmailWithUserFoundAndMatchingPasswordReturnsLogado + Description: Tests a corner case where the repository returns a user even when a null email is provided, and the provided password matches the stored password, leading to "Logado". + +Execution: + Arrange: Configure UserAccRepo so that UserAccRepo.findUserByEmail(null) returns a User_Credentials with password "MatchMe". + Act: Invoke loginUser(null, "MatchMe"). + Assert: Assert that the returned value equals "Logado". + +Validation: + Demonstrates that the method’s logic relies solely on the repository output and password equality, irrespective of email nullity once a user object is obtained. + + +Scenario 23: Provided password with internal whitespace differs from stored without whitespace + +Details: + TestName: internalWhitespaceInPasswordReturnsSenhaIncorreta + Description: Ensures that a password containing internal whitespace characters does not match a stored password that lacks those characters, resulting in "Senha incorreta". + +Execution: + Arrange: Mock UserAccRepo to return a User_Credentials with password "pass123" for "user@example.com". + Act: Call loginUser("user@example.com", "pass 123"). + Assert: Assert that the returned value equals "Senha incorreta". + +Validation: + Confirms strict character-by-character comparison, reinforcing that any deviation—even internal spaces—causes authentication to fail. + +*/ + +// ********RoostGPT******** + +package com.medeiros.SPRINGProject.Controllers; + +import com.medeiros.SPRINGProject.Models.*; +import com.medeiros.SPRINGProject.Security.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import java.util.Objects; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.doThrow; +import org.junit.jupiter.api.*; + +@ExtendWith(MockitoExtension.class) +public class AuthControllerLoginUserTest { + + @InjectMocks + private AuthController authController; + + @Mock + private UserAccRepository UserAccRepo; + + @Tag("valid") + @Test + public void successfulLoginReturnsLogado() { + String email = "user@example.com"; + String password = "P@ssw0rd"; + User_Credentials user = new User_Credentials(email, password, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, password); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("invalid") + @Test + public void wrongPasswordReturnsSenhaIncorreta() { + String email = "user@example.com"; + String storedPassword = "Correct123"; + String providedPassword = "Wrong123"; + User_Credentials user = new User_Credentials(email, storedPassword, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, providedPassword); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + + @Tag("invalid") + @Test + public void unknownEmailReturnsEmailNaoEncontrado() { + String email = "missing@example.com"; + when(UserAccRepo.findUserByEmail(email)).thenReturn(null); + String result = authController.loginUser(email, "anyPassword"); + Assertions.assertEquals((String) "Email não encontrado!", (String) result); + } + + @Tag("boundary") + @Test + public void nullStoredAndNullProvidedPasswordReturnsLogado() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, null, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, null); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("invalid") + @Test + public void nullStoredPasswordWithNonNullProvidedReturnsSenhaIncorreta() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, null, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, "NonNullPassword"); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + + @Tag("invalid") + @Test + public void nonNullStoredPasswordWithNullProvidedReturnsSenhaIncorreta() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, "abc123", "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, null); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + + @Tag("boundary") + @Test + public void emptyPasswordMatchingStoredEmptyReturnsLogado() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, "", "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, ""); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("invalid") + @Test + public void emptyPasswordAgainstNonEmptyStoredReturnsSenhaIncorreta() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, "nonEmpty", "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, ""); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + + @Tag("invalid") + @Test + public void nullEmailReturnsEmailNaoEncontrado() { + when(UserAccRepo.findUserByEmail((String) null)).thenReturn(null); + String result = authController.loginUser(null, "anyPassword"); + Assertions.assertEquals((String) "Email não encontrado!", (String) result); + } + + @Tag("invalid") + @Test + public void whitespaceEmailReturnsEmailNaoEncontrado() { + String email = " "; + when(UserAccRepo.findUserByEmail(email)).thenReturn(null); + String result = authController.loginUser(email, "password"); + Assertions.assertEquals((String) "Email não encontrado!", (String) result); + } + + @Tag("invalid") + @Test + public void emailCaseDifferenceReturnsEmailNaoEncontrado() { + String email = "User@Example.com"; + when(UserAccRepo.findUserByEmail(email)).thenReturn(null); + String result = authController.loginUser(email, "P@ssw0rd"); + Assertions.assertEquals((String) "Email não encontrado!", (String) result); + } + + @Tag("invalid") + @Test + public void passwordCaseDifferenceReturnsSenhaIncorreta() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, "SecretPass", "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, "secretpass"); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + + @Tag("invalid") + @Test + public void trailingWhitespaceInPasswordReturnsSenhaIncorreta() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, "pass123", "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, "pass123 "); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + + @Tag("boundary") + @Test + public void unicodeCredentialsMatchingReturnLogado() { + String email = "usér@exámple.com"; + String password = "päss🔒"; + User_Credentials user = new User_Credentials(email, password, "usér"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, password); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("boundary") + @Test + public void longPasswordMatchingReturnsLogado() { + String email = "user@example.com"; + StringBuilder sb = new StringBuilder(); + // TODO adjust length if needed for performance constraints + for (int i = 0; i < 5000; i++) { + sb.append('a'); + } + String longPassword = sb.toString(); + User_Credentials user = new User_Credentials(email, longPassword, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, longPassword); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("invalid") + @Test + public void sqlInjectionLikeEmailReturnsEmailNaoEncontrado() { + String email = "user@example.com' OR '1'='1"; + when(UserAccRepo.findUserByEmail(email)).thenReturn(null); + String result = authController.loginUser(email, "irrelevant"); + Assertions.assertEquals((String) "Email não encontrado!", (String) result); + } + + @Tag("integration") + @Test + public void repositoryFailurePropagatesException() { + String email = "user@example.com"; + when(UserAccRepo.findUserByEmail(email)).thenThrow(new RuntimeException()); + RuntimeException ex = Assertions.assertThrows(RuntimeException.class, () -> { + authController.loginUser(email, "anyPassword"); + }); + Assertions.assertNotNull((Object) ex); + } + + @Tag("integration") + @Test + public void nullRepositoryReferenceCausesNullPointerException() { + AuthController controllerWithoutRepo = new AuthController(); + NullPointerException ex = Assertions.assertThrows(NullPointerException.class, () -> { + controllerWithoutRepo.loginUser("user@example.com", "password"); + }); + Assertions.assertNotNull((Object) ex); + } + + @Tag("valid") + @Test + public void repeatedSuccessfulCallsReturnLogadoConsistently() { + String email = "user@example.com"; + String password = "Again123"; + User_Credentials user = new User_Credentials(email, password, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String first = authController.loginUser(email, password); + String second = authController.loginUser(email, password); + Assertions.assertEquals((String) "Logado", (String) first); + Assertions.assertEquals((String) "Logado", (String) second); + } + + @Tag("boundary") + @Test + public void whitespaceOnlyPasswordMatchingReturnsLogado() { + String email = "user@example.com"; + String password = " "; + User_Credentials user = new User_Credentials(email, password, "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, password); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("invalid") + @Test + public void emailWithSurroundingSpacesReturnsEmailNaoEncontrado() { + String email = " user@example.com "; + when(UserAccRepo.findUserByEmail(email)).thenReturn(null); + String result = authController.loginUser(email, "P@ssw0rd"); + Assertions.assertEquals((String) "Email não encontrado!", (String) result); + } + + @Tag("boundary") + @Test + public void nullEmailWithUserFoundAndMatchingPasswordReturnsLogado() { + String password = "MatchMe"; + User_Credentials user = new User_Credentials(null, password, "username"); + when(UserAccRepo.findUserByEmail((String) null)).thenReturn(user); + String result = authController.loginUser(null, password); + Assertions.assertEquals((String) "Logado", (String) result); + } + + @Tag("invalid") + @Test + public void internalWhitespaceInPasswordReturnsSenhaIncorreta() { + String email = "user@example.com"; + User_Credentials user = new User_Credentials(email, "pass123", "username"); + when(UserAccRepo.findUserByEmail(email)).thenReturn(user); + String result = authController.loginUser(email, "pass 123"); + Assertions.assertEquals((String) "Senha incorreta", (String) result); + } + +} \ No newline at end of file