Refactor: Replace brittle constraint-name string matching with proactive repository checks in AuthService#41
Conversation
Agent-Logs-Url: https://github.com/fred-maina/ChatAppBackend/sessions/8eb2ae0e-0759-4cb6-ac06-ca3501f1c9c6 Co-authored-by: fred-maina <150168105+fred-maina@users.noreply.github.com>
…name Agent-Logs-Url: https://github.com/fred-maina/ChatAppBackend/sessions/8eb2ae0e-0759-4cb6-ac06-ca3501f1c9c6 Co-authored-by: fred-maina <150168105+fred-maina@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors AuthService’s duplicate-user handling to avoid brittle DataIntegrityViolationException message/constraint-name string matching by proactively querying the repository for existing usernames/emails, and updates unit tests accordingly.
Changes:
- In
signUp, replacedfindBy...().isPresent()checks withexistsBy...checks and removed constraint-name parsing from the exception handler. - In
setUsername, removed string-based exception-message interpolation and kept a generic integrity-violation handler. - Updated
AuthServiceTestduplicate-email/username tests to mock proactiveexistsBy...checks and verify nosave(...)occurs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/main/java/com/fredmaina/chatapp/Auth/services/AuthService.java |
Uses proactive repository existsBy... checks and simplifies DataIntegrityViolationException handling. |
src/test/java/com/fredmaina/chatapp/Auth/Services/AuthServiceTest.java |
Adjusts tests to validate early-return duplicate detection and avoid DB writes on duplicates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| log.error("Data integrity violation during sign up: {}", e.getMessage()); | ||
| authResponse.setMessage("Data integrity violation"); | ||
| } | ||
| log.error("Data integrity violation during sign up: {}", e.getMessage()); |
There was a problem hiding this comment.
In the DataIntegrityViolationException handler, the error is logged without the exception object, which drops the stack trace and makes production debugging harder. Consider logging e as the throwable (and optionally using a stable identifier rather than only e.getMessage()).
| log.error("Data integrity violation during sign up: {}", e.getMessage()); | |
| log.error("Data integrity violation during sign up: {}", e.getMessage(), e); |
| authResponse.setMessage("Data integrity violation"); | ||
| } | ||
| log.error("Data integrity violation during sign up: {}", e.getMessage()); | ||
| authResponse.setMessage("Data integrity violation"); |
There was a problem hiding this comment.
After removing constraint-name matching, this catch-all now always returns the generic message "Data integrity violation". Since the PR intent is to keep this as a race-condition safety net, you can still provide a user-friendly duplicate email/username message by re-checking existsByUsernameIgnoreCase/existsByEmail inside the catch and only falling back to the generic message if neither exists. This keeps the logic resilient without depending on driver/constraint strings.
| authResponse.setMessage("Data integrity violation"); | |
| if (request.getUsername() != null && userRepository.existsByUsernameIgnoreCase(request.getUsername())) { | |
| authResponse.setMessage("Username already exists"); | |
| } else if (userRepository.existsByEmail(request.getEmail())) { | |
| authResponse.setMessage("Email already exists"); | |
| } else { | |
| authResponse.setMessage("Data integrity violation"); | |
| } |
| .build(); | ||
| } catch (DataIntegrityViolationException e) { | ||
|
|
||
| log.error("Data integrity violation while setting username for email {}: {}", email, e.getMessage()); |
There was a problem hiding this comment.
Similar to signUp, this DataIntegrityViolationException log line only records e.getMessage() and omits the throwable, which can hide the stack trace and root cause. Consider including e in the log call so the full exception is captured.
| log.error("Data integrity violation while setting username for email {}: {}", email, e.getMessage()); | |
| log.error("Data integrity violation while setting username for email {}: {}", email, e.getMessage(), e); |
| @@ -68,14 +67,13 @@ void testSignUp_duplicateEmail() { | |||
| request.setPassword("mypassword"); | |||
| request.setEmail("fred@example.com"); | |||
|
|
|||
There was a problem hiding this comment.
testSignUp_duplicateEmail only stubs existsByEmail, implicitly relying on Mockito's default false return for the preceding existsByUsernameIgnoreCase call. To make the test intent clearer and avoid coupling to Mockito defaults, consider explicitly stubbing existsByUsernameIgnoreCase("fredmaina123") to false (or verifying it was called).
| when(userRepository.existsByUsernameIgnoreCase("fredmaina123")).thenReturn(false); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (request.getUsername() != null && userRepository.findByUsernameIgnoreCase(request.getUsername()).isPresent()) { | ||
| authResponse.setMessage("Username already exists (case-insensitive)"); | ||
| if (request.getUsername() != null && userRepository.existsByUsernameIgnoreCase(request.getUsername())) { | ||
| authResponse.setMessage("Username already exists"); |
There was a problem hiding this comment.
Changing the duplicate-username message from "Username already exists (case-insensitive)" to "Username already exists" will break any callers that branch on the exact message text. For example, AuthController.register currently returns HTTP 409 only when the message equals the old string, so duplicate usernames will now likely return 400 instead of 409. Consider keeping the message stable or updating the controller to use the new message (or better: return/propagate a dedicated error code/status instead of matching on message strings).
| authResponse.setMessage("Username already exists"); | |
| authResponse.setMessage("Username already exists (case-insensitive)"); |
| @CacheEvict(value = "usernameCheck", key = "#request.username.toLowerCase()") | ||
| public AuthResponse signUp(SignUpRequest request) { | ||
| AuthResponse authResponse = new AuthResponse(); | ||
| if (request.getUsername() != null && userRepository.findByUsernameIgnoreCase(request.getUsername()).isPresent()) { | ||
| authResponse.setMessage("Username already exists (case-insensitive)"); | ||
| if (request.getUsername() != null && userRepository.existsByUsernameIgnoreCase(request.getUsername())) { | ||
| authResponse.setMessage("Username already exists"); |
There was a problem hiding this comment.
@CacheEvict uses key "#request.username.toLowerCase()" which will throw a SpEL evaluation error if signUp is ever called with a null username (and the method body already allows for null via request.getUsername() != null checks). To make this null-safe, add a condition = "#request.username != null" (or otherwise guard the key expression) so the cache eviction doesn't fail before the method executes.
Database constraint violations in
AuthServicewere caught and identified by string-matching againste.getMessage()(e.g.,contains("users_email_key")), making the logic fragile and silently broken if constraints are renamed or the DB driver changes.Changes
signUp: ReplacedfindByUsernameIgnoreCase(...).isPresent()/findByEmail(...).isPresent()withexistsByUsernameIgnoreCase/existsByEmail. Stripped all constraint-name string matching from theDataIntegrityViolationExceptioncatch — a generic fallback is retained for race conditions.setUsername: Already had a proactivefindByUsernameIgnoreCaseguard. Removed the string-based error log interpolation from itsDataIntegrityViolationExceptioncatch, keeping a clean generic handler.Tests: Updated
testSignUp_duplicateEmail/testSignUp_duplicateUsernameto mock the proactive repository checks (existsByEmail,existsByUsernameIgnoreCase) instead of simulating exception throws fromsave. Addedverify(userRepository, never()).save(...)to assert no DB write occurs on duplicate detection.Before:
After: