Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,36 +17,73 @@ public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> 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<ErrorResponse> 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<Map<String, String>> handleValidationErrors(
MethodArgumentNotValidException ex) {

Map<String, String> 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<ErrorResponse> 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<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
public ResponseEntity<ErrorResponse> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,48 +27,52 @@

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class ExpenseIntegrationTest {

@Autowired
private MockMvc mockMvc;

@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(),
Expand All @@ -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);
Expand All @@ -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())
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading