From a742d6f8fc7756375b68d77e17c42c95352cece7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikay=C4=B1l=20Quliyev?= Date: Fri, 15 May 2026 17:10:46 +0400 Subject: [PATCH 1/2] feat: Handled validation error --- .../exception/GlobalExceptionHandler.java | 79 ++++++++++++++----- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/example/expencetrackerapi/exception/GlobalExceptionHandler.java b/src/main/java/com/example/expencetrackerapi/exception/GlobalExceptionHandler.java index 2c4d49a..16615bb 100644 --- a/src/main/java/com/example/expencetrackerapi/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/example/expencetrackerapi/exception/GlobalExceptionHandler.java @@ -2,8 +2,12 @@ import com.example.expencetrackerapi.dto.response.ErrorResponse; import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.WebRequest; @@ -13,36 +17,73 @@ public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity handleResourceNotFound( - ResourceNotFoundException ex, WebRequest request) { + ResourceNotFoundException ex, WebRequest request) { + ErrorResponse error = - new ErrorResponse( - LocalDateTime.now(), - ex.getMessage(), - request.getDescription(false), - HttpStatus.NOT_FOUND.value()); + new ErrorResponse( + LocalDateTime.now(), + ex.getMessage(), + request.getDescription(false), + HttpStatus.NOT_FOUND.value()); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } @ExceptionHandler(InsufficientFundsException.class) public ResponseEntity handleInsufficientFunds( - InsufficientFundsException ex, WebRequest request) { + InsufficientFundsException ex, WebRequest request) { + ErrorResponse error = - new ErrorResponse( - LocalDateTime.now(), - ex.getMessage(), - request.getDescription(false), - HttpStatus.BAD_REQUEST.value()); + new ErrorResponse( + LocalDateTime.now(), + ex.getMessage(), + request.getDescription(false), + HttpStatus.BAD_REQUEST.value()); + + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationErrors( + MethodArgumentNotValidException ex) { + + Map errors = new HashMap<>(); + + ex.getBindingResult() + .getFieldErrors() + .forEach( + (FieldError error) -> { + errors.put(error.getField(), error.getDefaultMessage()); + }); + + return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument( + IllegalArgumentException ex, WebRequest request) { + + ErrorResponse error = + new ErrorResponse( + LocalDateTime.now(), + ex.getMessage(), + request.getDescription(false), + HttpStatus.BAD_REQUEST.value()); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } @ExceptionHandler(Exception.class) - public ResponseEntity handleGlobalException(Exception ex, WebRequest request) { + public ResponseEntity handleGlobalException( + Exception ex, WebRequest request) { + ErrorResponse error = - new ErrorResponse( - LocalDateTime.now(), - "An unexpected error occurred", - ex.getMessage(), - HttpStatus.INTERNAL_SERVER_ERROR.value()); + new ErrorResponse( + LocalDateTime.now(), + "Internal server error occurred", + ex.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR.value()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); } -} +} \ No newline at end of file From 9907df7928b4d6a3e0c213353255cf3368b64db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikay=C4=B1l=20Quliyev?= Date: Fri, 15 May 2026 17:22:23 +0400 Subject: [PATCH 2/2] feat: Handled validation error --- .../AccountIntegrationTest.java | 4 +- .../CategoryIntegrationTest.java | 6 +- .../ExpenseIntegrationTest.java | 55 ++++++++++--------- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/test/java/com/example/expencetrackerapi/AccountIntegrationTest.java b/src/test/java/com/example/expencetrackerapi/AccountIntegrationTest.java index 55d0348..f1fa3e0 100644 --- a/src/test/java/com/example/expencetrackerapi/AccountIntegrationTest.java +++ b/src/test/java/com/example/expencetrackerapi/AccountIntegrationTest.java @@ -79,7 +79,7 @@ void createAccount_DuplicateEmail_Returns500() throws Exception { mockMvc.perform(post("/api/accounts") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(duplicate))) - .andExpect(status().isInternalServerError()); + .andExpect(status().isBadRequest()); } @Test @@ -151,7 +151,7 @@ void updateAccount_WithDuplicateEmail_Returns500() throws Exception { mockMvc.perform(put("/api/accounts/" + bobId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(updateRequest))) - .andExpect(status().isInternalServerError() + .andExpect(status().isBadRequest() ); } diff --git a/src/test/java/com/example/expencetrackerapi/CategoryIntegrationTest.java b/src/test/java/com/example/expencetrackerapi/CategoryIntegrationTest.java index a759b2a..e34e3ae 100644 --- a/src/test/java/com/example/expencetrackerapi/CategoryIntegrationTest.java +++ b/src/test/java/com/example/expencetrackerapi/CategoryIntegrationTest.java @@ -107,7 +107,7 @@ void createCategory_BlankName_Returns500() throws Exception { mockMvc.perform(post("/api/categories") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isInternalServerError()); + .andExpect(status().isBadRequest()); } @Test @@ -120,7 +120,7 @@ void createCategory_NameTooShort_Returns500() throws Exception { mockMvc.perform(post("/api/categories") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isInternalServerError()); + .andExpect(status().isBadRequest()); } @Test @@ -132,7 +132,7 @@ void createCategory_NullAccountId_Returns500() throws Exception { mockMvc.perform(post("/api/categories") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isInternalServerError()); + .andExpect(status().isBadRequest()); } @Test diff --git a/src/test/java/com/example/expencetrackerapi/ExpenseIntegrationTest.java b/src/test/java/com/example/expencetrackerapi/ExpenseIntegrationTest.java index d073a35..7594fb0 100644 --- a/src/test/java/com/example/expencetrackerapi/ExpenseIntegrationTest.java +++ b/src/test/java/com/example/expencetrackerapi/ExpenseIntegrationTest.java @@ -17,7 +17,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.math.BigDecimal; @@ -28,7 +27,6 @@ @SpringBootTest @AutoConfigureMockMvc -@ActiveProfiles("test") class ExpenseIntegrationTest { @Autowired @@ -36,40 +34,45 @@ class ExpenseIntegrationTest { @Autowired private ExpenseRepository expenseRepository; + @Autowired private AccountRepository accountRepository; + @Autowired private CategoryRepository categoryRepository; + @Autowired private ObjectMapper objectMapper; + private Account account; private Category category; + @BeforeEach void setup() { - expenseRepository.deleteAll(); - categoryRepository.deleteAll(); - accountRepository.deleteAll(); - - account = new Account(); - account.setFullName("Test User"); - account.setEmail("test@gmail.com"); - account.setCurrentBalance(new BigDecimal("1000.00")); - account = accountRepository.save(account); - - category = new Category(); - category.setName("Food"); - category.setDescription("Food expenses"); - category.setAccount(account); - category = categoryRepository.save(category); - + expenseRepository.deleteAll(); + categoryRepository.deleteAll(); + accountRepository.deleteAll(); + + account = new Account(); + account.setFullName("Test User"); + account.setEmail("test@gmail.com"); + account.setCurrentBalance(new BigDecimal("1000.00")); + account = accountRepository.save(account); + + category = new Category(); + category.setName("Food"); + category.setDescription("Food expenses"); + category.setAccount(account); + category = categoryRepository.save(category); } + @Test void shouldCreateExpense() throws Exception { CreateExpenseRequest request = new CreateExpenseRequest( "Market Shopping", new BigDecimal("100.00"), - LocalDate.now(), + LocalDate.of(2026, 1, 1), PaymentMethod.CASH, account.getId(), category.getId(), @@ -81,15 +84,16 @@ void shouldCreateExpense() throws Exception { .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.title").value("Market Shopping")) - .andExpect(jsonPath("$.amount").value(100.00)); + .andExpect(jsonPath("$.amount").value(100)); } + @Test void shouldUpdateExpense() throws Exception { Expense expense = new Expense(); expense.setTitle("Old Title"); expense.setAmount(new BigDecimal("100")); - expense.setExpenseDate(LocalDate.now()); + expense.setExpenseDate(LocalDate.of(2026, 1, 1)); expense.setPaymentMethod(PaymentMethod.CASH); expense.setAccount(account); expense.setCategory(category); @@ -99,7 +103,7 @@ void shouldUpdateExpense() throws Exception { UpdateExpenseRequest request = new UpdateExpenseRequest(); request.setTitle("New Title"); request.setAmount(new BigDecimal("200")); - request.setExpenseDate(LocalDate.now()); + request.setExpenseDate(LocalDate.of(2026, 1, 2)); request.setPaymentMethod(PaymentMethod.CASH); mockMvc.perform(put("/api/expenses/" + expense.getId()) @@ -116,7 +120,7 @@ void shouldReturnAllExpenses() throws Exception { Expense expense = new Expense(); expense.setTitle("Test Expense"); expense.setAmount(new BigDecimal("50")); - expense.setExpenseDate(LocalDate.now()); + expense.setExpenseDate(LocalDate.of(2026, 1, 1)); expense.setPaymentMethod(PaymentMethod.CASH); expense.setAccount(account); expense.setCategory(category); @@ -134,7 +138,7 @@ void shouldReturnExpenseById() throws Exception { Expense expense = new Expense(); expense.setTitle("Laptop"); expense.setAmount(new BigDecimal("300")); - expense.setExpenseDate(LocalDate.now()); + expense.setExpenseDate(LocalDate.of(2026, 1, 1)); expense.setPaymentMethod(PaymentMethod.CASH); expense.setAccount(account); expense.setCategory(category); @@ -145,13 +149,14 @@ void shouldReturnExpenseById() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.title").value("Laptop")); } + @Test void shouldDeleteExpense() throws Exception { Expense expense = new Expense(); expense.setTitle("Delete Test"); expense.setAmount(new BigDecimal("20")); - expense.setExpenseDate(LocalDate.now()); + expense.setExpenseDate(LocalDate.of(2026, 1, 1)); expense.setPaymentMethod(PaymentMethod.CASH); expense.setAccount(account); expense.setCategory(category);