diff --git a/src/main/java/fun/trackmoney/account/service/AccountService.java b/src/main/java/fun/trackmoney/account/service/AccountService.java index 8e71eda..5c616ca 100644 --- a/src/main/java/fun/trackmoney/account/service/AccountService.java +++ b/src/main/java/fun/trackmoney/account/service/AccountService.java @@ -7,7 +7,8 @@ import fun.trackmoney.account.exception.AccountNotFoundException; import fun.trackmoney.account.mapper.AccountMapper; import fun.trackmoney.account.repository.AccountRepository; -import fun.trackmoney.user.service.UserService; +import fun.trackmoney.user.exception.UserNotFoundException; +import fun.trackmoney.user.repository.UserRepository; import org.springframework.stereotype.Service; import java.util.List; @@ -17,18 +18,21 @@ public class AccountService { private final AccountRepository accountRepository; private final AccountMapper accountMapper; - private final UserService userService; + private final UserRepository userRepository; - public AccountService(AccountRepository accountRepository, AccountMapper accountMapper, UserService userService) { + public AccountService(AccountRepository accountRepository, + AccountMapper accountMapper, + UserRepository userRepository) { this.accountRepository = accountRepository; this.accountMapper = accountMapper; - this.userService = userService; + this.userRepository = userRepository; } public AccountResponseDTO createAccount(AccountRequestDTO dto) { AccountEntity account = accountMapper.accountRequestToAccountEntity(dto); - account.setUser(userService.findUserById(dto.userId())); + account.setUser(userRepository.findById(dto.userId()) + .orElseThrow(() -> new UserNotFoundException("User not found!"))); return accountMapper.accountEntityToAccountResponse(accountRepository .save(account)); diff --git a/src/main/java/fun/trackmoney/budget/controller/BudgetsController.java b/src/main/java/fun/trackmoney/budget/controller/BudgetsController.java new file mode 100644 index 0000000..ad5e867 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/controller/BudgetsController.java @@ -0,0 +1,63 @@ +package fun.trackmoney.budget.controller; + +import fun.trackmoney.budget.dtos.BudgetCreateDTO; +import fun.trackmoney.budget.dtos.BudgetResponseDTO; +import fun.trackmoney.budget.service.BudgetsService; +import fun.trackmoney.utils.response.ApiResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("budgets") +public class BudgetsController { + + private final BudgetsService budgetsService; + + public BudgetsController(BudgetsService budgetsService) { + this.budgetsService = budgetsService; + } + + @PostMapping + public ResponseEntity> create(@RequestBody BudgetCreateDTO dto) { + return ResponseEntity.status(HttpStatus.CREATED).body( + new ApiResponse<>(true, "Budget created", budgetsService.create(dto), null)); + } + + @GetMapping + public ResponseEntity>> findAll() { + var list = budgetsService.findAll(); + return ResponseEntity.status(HttpStatus.OK).body( + new ApiResponse<>(true, "Get all Budget",list , null)); + } + + @GetMapping("/{id}") + public ResponseEntity> findById(@PathVariable Integer id) { + return ResponseEntity.status(HttpStatus.OK).body( + new ApiResponse<>(true, "Get Budget by Id", budgetsService.findById(id), null)); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteById(@PathVariable Integer id) { + budgetsService.findById(id); + return ResponseEntity.status(HttpStatus.OK).body( + new ApiResponse<>(true, "Delete Budget by id", "Budget deleted", null)); + } + + @PutMapping("/{id}") + public ResponseEntity> updateById(@PathVariable Integer id, + @RequestBody BudgetCreateDTO dto) { + return ResponseEntity.status(HttpStatus.OK).body( + new ApiResponse<>(true, "Update Budget", budgetsService.update(dto,id), null)); + } +} + diff --git a/src/main/java/fun/trackmoney/budget/dtos/BudgetCreateDTO.java b/src/main/java/fun/trackmoney/budget/dtos/BudgetCreateDTO.java new file mode 100644 index 0000000..c2d1dfb --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/dtos/BudgetCreateDTO.java @@ -0,0 +1,11 @@ +package fun.trackmoney.budget.dtos; + +import java.math.BigDecimal; +import java.util.UUID; + +public record BudgetCreateDTO(Integer categoryId, + UUID userId, + Integer accountId, + BigDecimal targetAmount, + Integer resetDay) { +} diff --git a/src/main/java/fun/trackmoney/budget/dtos/BudgetResponseDTO.java b/src/main/java/fun/trackmoney/budget/dtos/BudgetResponseDTO.java new file mode 100644 index 0000000..bbd7b87 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/dtos/BudgetResponseDTO.java @@ -0,0 +1,13 @@ +package fun.trackmoney.budget.dtos; + +import fun.trackmoney.account.dtos.AccountResponseDTO; +import fun.trackmoney.category.entity.CategoryEntity; + +import java.math.BigDecimal; + +public record BudgetResponseDTO (Integer budgetId, + CategoryEntity category, + AccountResponseDTO account, + BigDecimal targetAmount, + Integer resetDay) { +} diff --git a/src/main/java/fun/trackmoney/budget/entity/BudgetsEntity.java b/src/main/java/fun/trackmoney/budget/entity/BudgetsEntity.java new file mode 100644 index 0000000..887b047 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/entity/BudgetsEntity.java @@ -0,0 +1,108 @@ +package fun.trackmoney.budget.entity; + +import fun.trackmoney.account.entity.AccountEntity; +import fun.trackmoney.category.entity.CategoryEntity; +import fun.trackmoney.user.entity.UserEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +import java.math.BigDecimal; + +@Entity +@Table(name = "tb_budget") +public class BudgetsEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "budget_id") + private Integer budgetId; + + @ManyToOne + @JoinColumn(name = "category_id") + private CategoryEntity category; + + @ManyToOne + @JoinColumn(name = "account_id") + private AccountEntity account; + @ManyToOne + @JoinColumn(name = "user_id") + private UserEntity userEntity; + + private BigDecimal targetAmount; + @Column(name = "reset_day") + private Integer resetDay; + + public AccountEntity getAccount() { + return account; + } + + public BudgetsEntity() { + + } + + public BudgetsEntity(Integer budgetId, + CategoryEntity category, + UserEntity userEntity, + BigDecimal targetAmount, + Integer resetDay) { + this.budgetId = budgetId; + this.category = category; + this.userEntity = userEntity; + this.targetAmount = targetAmount; + this.resetDay = resetDay; + } + + public void setAccount(AccountEntity account) { + this.account = account; + } + + public BudgetsEntity(UserEntity userEntity) { + this.userEntity = userEntity; + } + + public Integer getBudgetId() { + return budgetId; + } + + public void setBudgetId(Integer budgetId) { + this.budgetId = budgetId; + } + + public CategoryEntity getCategory() { + return category; + } + + public void setCategory(CategoryEntity category) { + this.category = category; + } + + public BigDecimal getTargetAmount() { + return targetAmount; + } + + public void setTargetAmount(BigDecimal targetAmount) { + this.targetAmount = targetAmount; + } + + public Integer getResetDay() { + return resetDay; + } + + public void setResetDay(Integer resetDay) { + this.resetDay = resetDay; + } + + public UserEntity getUserEntity() { + return userEntity; + } + + public void setUserEntity(UserEntity userEntity) { + this.userEntity = userEntity; + } +} diff --git a/src/main/java/fun/trackmoney/budget/exception/BudgetsNotFoundException.java b/src/main/java/fun/trackmoney/budget/exception/BudgetsNotFoundException.java new file mode 100644 index 0000000..ba3b831 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/exception/BudgetsNotFoundException.java @@ -0,0 +1,24 @@ +package fun.trackmoney.budget.exception; + +import fun.trackmoney.utils.CustomFieldError; + +import java.util.ArrayList; +import java.util.List; + +public class BudgetsNotFoundException extends RuntimeException { + private final List errors = new ArrayList<>(); + + public BudgetsNotFoundException(String message) { + super(message); + this.errors.add(new CustomFieldError("Budgets", message)); + } + + public BudgetsNotFoundException(List errors) { + super("Budgets not found!"); + this.errors.addAll(errors); + } + + public List getErrors() { + return errors; + } +} diff --git a/src/main/java/fun/trackmoney/budget/mapper/BudgetMapper.java b/src/main/java/fun/trackmoney/budget/mapper/BudgetMapper.java new file mode 100644 index 0000000..5a5e443 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/mapper/BudgetMapper.java @@ -0,0 +1,18 @@ +package fun.trackmoney.budget.mapper; + +import fun.trackmoney.budget.dtos.BudgetCreateDTO; +import fun.trackmoney.budget.dtos.BudgetResponseDTO; +import fun.trackmoney.budget.entity.BudgetsEntity; +import org.mapstruct.Mapper; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface BudgetMapper { + + BudgetsEntity createDtoTOEntity(BudgetCreateDTO dto); + + BudgetResponseDTO entityToResponseDTO(BudgetsEntity entity); + + List entityListToResponseList(List entityList); +} diff --git a/src/main/java/fun/trackmoney/budget/repository/BudgetsRepository.java b/src/main/java/fun/trackmoney/budget/repository/BudgetsRepository.java new file mode 100644 index 0000000..2aea451 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/repository/BudgetsRepository.java @@ -0,0 +1,7 @@ +package fun.trackmoney.budget.repository; + +import fun.trackmoney.budget.entity.BudgetsEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BudgetsRepository extends JpaRepository { +} diff --git a/src/main/java/fun/trackmoney/budget/service/BudgetsService.java b/src/main/java/fun/trackmoney/budget/service/BudgetsService.java new file mode 100644 index 0000000..8121d77 --- /dev/null +++ b/src/main/java/fun/trackmoney/budget/service/BudgetsService.java @@ -0,0 +1,77 @@ +package fun.trackmoney.budget.service; + +import fun.trackmoney.account.mapper.AccountMapper; +import fun.trackmoney.account.service.AccountService; +import fun.trackmoney.budget.dtos.BudgetCreateDTO; +import fun.trackmoney.budget.dtos.BudgetResponseDTO; +import fun.trackmoney.budget.entity.BudgetsEntity; +import fun.trackmoney.budget.exception.BudgetsNotFoundException; +import fun.trackmoney.budget.mapper.BudgetMapper; +import fun.trackmoney.budget.repository.BudgetsRepository; +import fun.trackmoney.category.service.CategoryService; +import fun.trackmoney.user.service.UserService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class BudgetsService { + + private final BudgetsRepository budgetsRepository; + private final BudgetMapper budgetMapper; + private final AccountService accountService; + private final AccountMapper accountMapper; + private final CategoryService categoryService; + private final UserService userService; + + public BudgetsService(BudgetsRepository budgetsRepository, + BudgetMapper budgetMapper, + AccountService accountService, + AccountMapper accountMapper, + CategoryService categoryService, + UserService userService) { + this.budgetsRepository = budgetsRepository; + this.budgetMapper = budgetMapper; + this.accountService = accountService; + this.accountMapper = accountMapper; + this.categoryService = categoryService; + this.userService = userService; + } + + public BudgetResponseDTO create(BudgetCreateDTO dto) { + BudgetsEntity budgets = budgetMapper.createDtoTOEntity(dto); + + budgets.setUserEntity(userService.findUserById(dto.userId())); + budgets.setAccount(accountMapper.accountResponseToEntity(accountService.findAccountById(dto.accountId()))); + budgets.setCategory(categoryService.findById(dto.categoryId())); + return budgetMapper.entityToResponseDTO(budgetsRepository.save(budgets)); + } + + public List findAll() { + return budgetMapper.entityListToResponseList(budgetsRepository.findAll()); + } + + public BudgetResponseDTO findById(Integer id) { + return budgetMapper.entityToResponseDTO(budgetsRepository.findById(id) + .orElseThrow(() -> new BudgetsNotFoundException("Budget not found"))); + } + + public BudgetResponseDTO update(BudgetCreateDTO dto, Integer id) { + budgetsRepository.findById(id) + .orElseThrow(() -> new BudgetsNotFoundException(("Budget not found"))); + + BudgetsEntity budgets = budgetMapper.createDtoTOEntity(dto); + budgets.setBudgetId(id); + budgets.setUserEntity(userService.findUserById(dto.userId())); + budgets.setAccount(accountMapper.accountResponseToEntity(accountService.findAccountById(dto.accountId()))); + budgets.setCategory(categoryService.findById(dto.categoryId())); + budgets.setTargetAmount(dto.targetAmount()); + budgets.setResetDay(dto.resetDay()); + + return budgetMapper.entityToResponseDTO(budgetsRepository.save(budgets)); + } + + public void deleteById(Integer id) { + budgetsRepository.deleteById(id); + } +} diff --git a/src/main/java/fun/trackmoney/config/CorsConfig.java b/src/main/java/fun/trackmoney/config/CorsConfig.java new file mode 100644 index 0000000..b0b3c31 --- /dev/null +++ b/src/main/java/fun/trackmoney/config/CorsConfig.java @@ -0,0 +1,24 @@ +package fun.trackmoney.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:4200") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*"); + } + }; + } +} + diff --git a/src/main/java/fun/trackmoney/config/exception/RestExceptionHandler.java b/src/main/java/fun/trackmoney/config/exception/RestExceptionHandler.java index c49636b..4c966d7 100644 --- a/src/main/java/fun/trackmoney/config/exception/RestExceptionHandler.java +++ b/src/main/java/fun/trackmoney/config/exception/RestExceptionHandler.java @@ -2,6 +2,7 @@ import fun.trackmoney.account.exception.AccountNotFoundException; import fun.trackmoney.auth.exception.LoginException; +import fun.trackmoney.budget.exception.BudgetsNotFoundException; import fun.trackmoney.category.exception.CategoryNotFoundException; import fun.trackmoney.goal.exception.GoalsNotFoundException; import fun.trackmoney.transaction.exception.TransactionNotFoundException; @@ -101,4 +102,12 @@ public ResponseEntity>> goalsNotFound(GoalsNo .body(new ApiResponse<>(false, ex.getMessage(), null, ex.getErrors()) ); } + + @ExceptionHandler(BudgetsNotFoundException.class) + public ResponseEntity>> budgetsNotFound(BudgetsNotFoundException ex) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(new ApiResponse<>(false, ex.getMessage(), null, ex.getErrors()) + ); + } } \ No newline at end of file diff --git a/src/main/java/fun/trackmoney/user/service/UserService.java b/src/main/java/fun/trackmoney/user/service/UserService.java index 9db0314..18f8309 100644 --- a/src/main/java/fun/trackmoney/user/service/UserService.java +++ b/src/main/java/fun/trackmoney/user/service/UserService.java @@ -1,5 +1,7 @@ package fun.trackmoney.user.service; +import fun.trackmoney.account.dtos.AccountRequestDTO; +import fun.trackmoney.account.service.AccountService; import fun.trackmoney.auth.dto.LoginRequestDTO; import fun.trackmoney.user.dtos.UserRequestDTO; import fun.trackmoney.user.dtos.UserResponseDTO; @@ -14,6 +16,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.List; import java.util.UUID; @@ -23,11 +26,16 @@ public class UserService { private final UserRepository userRepository; private final UserMapper userMapper; private final PasswordEncoder encoder; + private final AccountService accountService; - public UserService(UserRepository userRepository, UserMapper userMapper, PasswordEncoder encoder) { + public UserService(UserRepository userRepository, + UserMapper userMapper, + PasswordEncoder encoder, + AccountService accountService) { this.userRepository = userRepository; this.userMapper = userMapper; this.encoder = encoder; + this.accountService = accountService; } @@ -39,7 +47,10 @@ public UserResponseDTO register(UserRequestDTO userRequestDTO) { UserEntity user = userMapper.userRequestDTOToEntity(userRequestDTO); user.setPassword(encoder.encode(user.getPassword())); try { - return userMapper.userEntityToUserResponseDto(userRepository.save(user)); + UserResponseDTO userResponseDTO = userMapper.userEntityToUserResponseDto(userRepository.save(user)); + accountService.createAccount(new AccountRequestDTO( + userResponseDTO.userId(), "Default Account", BigDecimal.valueOf(0), true)); + return userResponseDTO; } catch (RuntimeException e) { throw new EmailAlreadyExistsException("Email already registered."); } @@ -47,15 +58,15 @@ public UserResponseDTO register(UserRequestDTO userRequestDTO) { public UserEntity findUserByEmail(LoginRequestDTO loginDto) { return userRepository.findByEmail(loginDto.email()) - .orElseThrow(() -> { - throw new UserNotFoundException("User not found"); - }); + .orElseThrow(() -> + new UserNotFoundException("User not found") + ); } public UserEntity findUserById(UUID userId) { return userRepository.findById(userId) - .orElseThrow(() -> { - throw new UserNotFoundException("User not found"); - }); + .orElseThrow(() -> + new UserNotFoundException("User not found") + ); } } diff --git a/src/main/resources/db/migration/V2__create_budget_schema.sql b/src/main/resources/db/migration/V2__create_budget_schema.sql new file mode 100644 index 0000000..de0d74d --- /dev/null +++ b/src/main/resources/db/migration/V2__create_budget_schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE tb_budget( + budget_id SERIAL PRIMARY KEY, + user_id UUID NOT NULL, + account_id SERIAL NOT NULL, + category_id SERIAL NOT NULL, + target_amount NUMERIC(19, 4) NOT NULL DEFAULT 0.0000, + reset_day INTEGER, + + CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES tb_user(user_id), + CONSTRAINT fk_account FOREIGN KEY (account_id) REFERENCES tb_account(account_id), + CONSTRAINT fk_category FOREIGN KEY (category_id) REFERENCES tb_category(category_id) +); \ No newline at end of file diff --git a/src/test/java/fun/trackmoney/account/service/AccountServiceTest.java b/src/test/java/fun/trackmoney/account/service/AccountServiceTest.java index a0e1d26..efbf715 100644 --- a/src/test/java/fun/trackmoney/account/service/AccountServiceTest.java +++ b/src/test/java/fun/trackmoney/account/service/AccountServiceTest.java @@ -8,7 +8,7 @@ import fun.trackmoney.account.mapper.AccountMapper; import fun.trackmoney.account.repository.AccountRepository; import fun.trackmoney.user.entity.UserEntity; -import fun.trackmoney.user.service.UserService; +import fun.trackmoney.user.repository.UserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,7 +24,7 @@ class AccountServiceTest { private AccountRepository accountRepository; private AccountMapper accountMapper; - private UserService userService; + private UserRepository userRepository; private AccountService accountService; @@ -32,8 +32,8 @@ class AccountServiceTest { void setUp() { accountRepository = mock(AccountRepository.class); accountMapper = mock(AccountMapper.class); - userService = mock(UserService.class); - accountService = new AccountService(accountRepository, accountMapper, userService); + userRepository = mock(UserRepository.class); + accountService = new AccountService(accountRepository, accountMapper, userRepository); } @Test @@ -46,7 +46,7 @@ void testCreateAccount() { AccountResponseDTO responseDTO = new AccountResponseDTO(1, null, "Conta Corrente", BigDecimal.valueOf(1000), true); when(accountMapper.accountRequestToAccountEntity(requestDTO)).thenReturn(accountEntity); - when(userService.findUserById(userId)).thenReturn(user); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); when(accountRepository.save(accountEntity)).thenReturn(savedEntity); when(accountMapper.accountEntityToAccountResponse(savedEntity)).thenReturn(responseDTO); @@ -54,7 +54,7 @@ void testCreateAccount() { assertEquals(responseDTO, result); verify(accountRepository).save(accountEntity); - verify(userService).findUserById(userId); + verify(userRepository).findById(userId); assertEquals(user, accountEntity.getUser()); } diff --git a/src/test/java/fun/trackmoney/budget/controller/BudgetsControllerTest.java b/src/test/java/fun/trackmoney/budget/controller/BudgetsControllerTest.java new file mode 100644 index 0000000..ff614bb --- /dev/null +++ b/src/test/java/fun/trackmoney/budget/controller/BudgetsControllerTest.java @@ -0,0 +1,123 @@ +package fun.trackmoney.budget.controller; + +import fun.trackmoney.budget.dtos.BudgetCreateDTO; +import fun.trackmoney.budget.dtos.BudgetResponseDTO; +import fun.trackmoney.budget.service.BudgetsService; +import fun.trackmoney.utils.response.ApiResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +class BudgetsControllerTest { + + @Mock + private BudgetsService budgetsService; + + @InjectMocks + private BudgetsController budgetsController; + + private BudgetCreateDTO createDTO; + private BudgetResponseDTO responseDTO; + private Integer budgetId; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + UUID userId = UUID.randomUUID(); + budgetId = 1; + createDTO = new BudgetCreateDTO(10, userId, 20, BigDecimal.valueOf(1000), 5); + responseDTO = new BudgetResponseDTO(budgetId, null, null, BigDecimal.valueOf(1000), 5); + } + + @Test + void create_shouldReturnCreatedAndBudgetResponse() { + when(budgetsService.create(createDTO)).thenReturn(responseDTO); + + ResponseEntity> response = budgetsController.create(createDTO); + + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + assertEquals("Budget created", response.getBody().getMessage()); + assertEquals(responseDTO, response.getBody().getData()); + assertNull(response.getBody().getErrors()); + verify(budgetsService, times(1)).create(createDTO); + } + + @Test + void findAll_shouldReturnOkAndListOfBudgetResponses() { + List budgetList = Collections.singletonList(responseDTO); + when(budgetsService.findAll()).thenReturn(budgetList); + + ResponseEntity>> response = budgetsController.findAll(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + assertEquals("Get all Budget", response.getBody().getMessage()); + assertEquals(budgetList, response.getBody().getData()); + assertNull(response.getBody().getErrors()); + verify(budgetsService, times(1)).findAll(); + } + + @Test + void findById_shouldReturnOkAndBudgetResponse() { + when(budgetsService.findById(budgetId)).thenReturn(responseDTO); + + ResponseEntity> response = budgetsController.findById(budgetId); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + assertEquals("Get Budget by Id", response.getBody().getMessage()); + assertEquals(responseDTO, response.getBody().getData()); + assertNull(response.getBody().getErrors()); + verify(budgetsService, times(1)).findById(budgetId); + } + + @Test + void deleteById_shouldReturnOkAndSuccessMessage() { + ResponseEntity> response = budgetsController.deleteById(budgetId); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + assertEquals("Delete Budget by id", response.getBody().getMessage()); + assertEquals("Budget deleted", response.getBody().getData()); + assertNull(response.getBody().getErrors()); + verify(budgetsService, times(1)).findById(budgetId); // Para garantir que o findById foi chamado + } + + @Test + void updateById_shouldReturnOkAndUpdatedBudgetResponse() { + int updateId = 1; + BudgetCreateDTO updateDTO = new BudgetCreateDTO(11, UUID.randomUUID(), 21, BigDecimal.valueOf(1500), 6); + BudgetResponseDTO updatedResponseDTO = new BudgetResponseDTO(updateId, null, null, BigDecimal.valueOf(1500), 6); + when(budgetsService.update(updateDTO, updateId)).thenReturn(updatedResponseDTO); + + ResponseEntity> response = budgetsController.updateById(updateId, updateDTO); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + assertEquals("Update Budget", response.getBody().getMessage()); + assertEquals(updatedResponseDTO, response.getBody().getData()); + assertNull(response.getBody().getErrors()); + verify(budgetsService, times(1)).update(updateDTO, updateId); + } +} \ No newline at end of file diff --git a/src/test/java/fun/trackmoney/budget/service/BudgetsServiceTest.java b/src/test/java/fun/trackmoney/budget/service/BudgetsServiceTest.java new file mode 100644 index 0000000..7bf4ae8 --- /dev/null +++ b/src/test/java/fun/trackmoney/budget/service/BudgetsServiceTest.java @@ -0,0 +1,236 @@ +package fun.trackmoney.budget.service; + +import fun.trackmoney.account.dtos.AccountResponseDTO; +import fun.trackmoney.account.entity.AccountEntity; +import fun.trackmoney.account.mapper.AccountMapper; +import fun.trackmoney.account.service.AccountService; +import fun.trackmoney.budget.dtos.BudgetCreateDTO; +import fun.trackmoney.budget.dtos.BudgetResponseDTO; +import fun.trackmoney.budget.entity.BudgetsEntity; +import fun.trackmoney.budget.exception.BudgetsNotFoundException; +import fun.trackmoney.budget.mapper.BudgetMapper; +import fun.trackmoney.budget.repository.BudgetsRepository; +import fun.trackmoney.category.entity.CategoryEntity; +import fun.trackmoney.category.service.CategoryService; +import fun.trackmoney.user.dtos.UserResponseDTO; +import fun.trackmoney.user.entity.UserEntity; +import fun.trackmoney.user.service.UserService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class BudgetsServiceTest { + + @Mock + private BudgetsRepository budgetsRepository; + @Mock + private BudgetMapper budgetMapper; + @Mock + private AccountService accountService; + @Mock + private AccountMapper accountMapper; + @Mock + private CategoryService categoryService; + @Mock + private UserService userService; + + @InjectMocks + private BudgetsService budgetsService; + + private UUID userId; + private BudgetCreateDTO createDTO; + private BudgetsEntity budgetEntity; + private UserEntity userEntity; + private UserResponseDTO userResponseDTO; + private AccountResponseDTO accountResponseDTO; + private AccountEntity accountEntity; + private CategoryEntity categoryEntity; + private BudgetResponseDTO budgetResponseDTO; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + userId = UUID.randomUUID(); + createDTO = new BudgetCreateDTO(10, userId, 20, BigDecimal.valueOf(1000), 5); + budgetEntity = new BudgetsEntity(); + userEntity = new UserEntity(); + userResponseDTO = new UserResponseDTO(userId, "testUser", "email@test"); + accountResponseDTO = new AccountResponseDTO(20, userResponseDTO, "true", BigDecimal.valueOf(1000), true); + accountEntity = new AccountEntity(); + categoryEntity = new CategoryEntity(); + budgetResponseDTO = new BudgetResponseDTO(1, categoryEntity, accountResponseDTO, BigDecimal.valueOf(1000), 5); + } + + @Test + void create_shouldReturnBudgetResponseDTO() { + budgetEntity.setBudgetId(99); + budgetEntity.setCategory(categoryEntity); + budgetEntity.setUserEntity(userEntity); + budgetEntity.setTargetAmount(BigDecimal.valueOf(1234)); + budgetEntity.setResetDay(7); + budgetEntity.setAccount(accountEntity); + + when(budgetMapper.createDtoTOEntity(createDTO)).thenReturn(budgetEntity); + when(userService.findUserById(userId)).thenReturn(userEntity); + when(accountService.findAccountById(20)).thenReturn(accountResponseDTO); + when(accountMapper.accountResponseToEntity(accountResponseDTO)).thenReturn(accountEntity); + when(categoryService.findById(10)).thenReturn(categoryEntity); + when(budgetsRepository.save(budgetEntity)).thenReturn(budgetEntity); + when(budgetMapper.entityToResponseDTO(budgetEntity)).thenReturn(budgetResponseDTO); + + BudgetResponseDTO result = budgetsService.create(createDTO); + + assertNotNull(result); + assertEquals(99, budgetEntity.getBudgetId()); + assertEquals(categoryEntity, budgetEntity.getCategory()); + assertEquals(userEntity, budgetEntity.getUserEntity()); + assertEquals(BigDecimal.valueOf(1234), budgetEntity.getTargetAmount()); + assertEquals(7, budgetEntity.getResetDay()); + assertEquals(accountEntity, budgetEntity.getAccount()); + assertEquals(budgetResponseDTO, result); + verify(budgetsRepository, times(1)).save(budgetEntity); + verify(budgetMapper, times(1)).createDtoTOEntity(createDTO); + verify(userService, times(1)).findUserById(userId); + verify(accountService, times(1)).findAccountById(20); + verify(accountMapper, times(1)).accountResponseToEntity(accountResponseDTO); + verify(categoryService, times(1)).findById(10); + verify(budgetMapper, times(1)).entityToResponseDTO(budgetEntity); + } + + @Test + void findAll_shouldReturnEmptyList_whenNoBudgetsExist() { + when(budgetsRepository.findAll()).thenReturn(Collections.emptyList()); + when(budgetMapper.entityListToResponseList(Collections.emptyList())).thenReturn(Collections.emptyList()); + + List result = budgetsService.findAll(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(budgetsRepository, times(1)).findAll(); + verify(budgetMapper, times(1)).entityListToResponseList(Collections.emptyList()); + } + + @Test + void findAll_shouldReturnListOfBudgetResponseDTO() { + BudgetsEntity entity = new BudgetsEntity(); + BudgetResponseDTO dto = new BudgetResponseDTO(1, new CategoryEntity(), + new AccountResponseDTO(1, userResponseDTO, "test", BigDecimal.valueOf(100), true), + BigDecimal.TEN, 10); + List entities = List.of(entity); + List dtos = List.of(dto); + + when(budgetsRepository.findAll()).thenReturn(entities); + when(budgetMapper.entityListToResponseList(entities)).thenReturn(dtos); + + List result = budgetsService.findAll(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(dtos, result); + verify(budgetsRepository, times(1)).findAll(); + verify(budgetMapper, times(1)).entityListToResponseList(entities); + } + + @Test + void findById_shouldReturnBudgetResponseDTO_whenExists() { + int budgetId = 1; + when(budgetsRepository.findById(budgetId)).thenReturn(Optional.of(budgetEntity)); + when(budgetMapper.entityToResponseDTO(budgetEntity)).thenReturn(budgetResponseDTO); + + BudgetResponseDTO result = budgetsService.findById(budgetId); + + assertNotNull(result); + assertEquals(budgetResponseDTO, result); + verify(budgetsRepository, times(1)).findById(budgetId); + verify(budgetMapper, times(1)).entityToResponseDTO(budgetEntity); + } + + @Test + void findById_shouldThrowException_whenNotExists() { + int budgetId = 1; + when(budgetsRepository.findById(budgetId)).thenReturn(Optional.empty()); + + BudgetsNotFoundException exception = assertThrows(BudgetsNotFoundException.class, () -> budgetsService.findById(budgetId)); + assertEquals("Budget not found", exception.getMessage()); + verify(budgetsRepository, times(1)).findById(budgetId); + verify(budgetMapper, never()).entityToResponseDTO(any()); + } + + @Test + void update_shouldReturnUpdatedBudgetResponseDTO_whenBudgetExists() { + int budgetIdToUpdate = 1; + BudgetCreateDTO updateDTO = new BudgetCreateDTO(11, userId, 21, BigDecimal.valueOf(1500), 6); + BudgetsEntity existingEntity = new BudgetsEntity(); + existingEntity.setBudgetId(budgetIdToUpdate); + UserResponseDTO updatedUserResponseDTO = new UserResponseDTO(userId, "updatedUser", "updated@test"); + AccountResponseDTO updatedAccountDTO = new AccountResponseDTO(21, updatedUserResponseDTO, "false", BigDecimal.valueOf(1500), false); + AccountEntity updatedAccountEntity = new AccountEntity(); + CategoryEntity updatedCategory = new CategoryEntity(); + BudgetResponseDTO updatedResponse = new BudgetResponseDTO(budgetIdToUpdate, updatedCategory, + updatedAccountDTO, BigDecimal.valueOf(1500), 6); + BudgetsEntity updatedEntity = new BudgetsEntity(); + updatedEntity.setBudgetId(budgetIdToUpdate); + + when(budgetsRepository.findById(budgetIdToUpdate)).thenReturn(Optional.of(existingEntity)); + when(budgetMapper.createDtoTOEntity(updateDTO)).thenReturn(updatedEntity); + when(userService.findUserById(userId)).thenReturn(userEntity); + when(accountService.findAccountById(21)).thenReturn(updatedAccountDTO); + when(accountMapper.accountResponseToEntity(updatedAccountDTO)).thenReturn(updatedAccountEntity); + when(categoryService.findById(11)).thenReturn(updatedCategory); + when(budgetsRepository.save(updatedEntity)).thenReturn(updatedEntity); + when(budgetMapper.entityToResponseDTO(updatedEntity)).thenReturn(updatedResponse); + + BudgetResponseDTO result = budgetsService.update(updateDTO, budgetIdToUpdate); + + assertNotNull(result); + assertEquals(updatedResponse, result); + assertEquals(budgetIdToUpdate, updatedEntity.getBudgetId()); + verify(budgetsRepository, times(1)).findById(budgetIdToUpdate); + verify(budgetMapper, times(1)).createDtoTOEntity(updateDTO); + verify(userService, times(1)).findUserById(userId); + verify(accountService, times(1)).findAccountById(21); + verify(accountMapper, times(1)).accountResponseToEntity(updatedAccountDTO); + verify(categoryService, times(1)).findById(11); + verify(budgetsRepository, times(1)).save(updatedEntity); + verify(budgetMapper, times(1)).entityToResponseDTO(updatedEntity); + } + + @Test + void update_shouldThrowException_whenBudgetNotExists() { + int budgetIdToUpdate = 1; + BudgetCreateDTO updateDTO = new BudgetCreateDTO(11, userId, 21, BigDecimal.valueOf(1500), 6); + + when(budgetsRepository.findById(budgetIdToUpdate)).thenReturn(Optional.empty()); + + BudgetsNotFoundException exception = assertThrows(BudgetsNotFoundException.class, + () -> budgetsService.update(updateDTO, budgetIdToUpdate)); + assertEquals("Budget not found", exception.getMessage()); + verify(budgetsRepository, times(1)).findById(budgetIdToUpdate); + verify(budgetMapper, never()).createDtoTOEntity(any()); + verify(userService, never()).findUserById(any()); + verify(accountService, never()).findAccountById(anyInt()); + verify(accountMapper, never()).accountResponseToEntity(any()); + verify(categoryService, never()).findById(anyInt()); + verify(budgetsRepository, never()).save(any()); + verify(budgetMapper, never()).entityToResponseDTO(any()); + } + + @Test + void deleteById_shouldCallRepositoryDelete() { + int budgetId = 5; + budgetsService.deleteById(budgetId); + verify(budgetsRepository, times(1)).deleteById(budgetId); + } +} \ No newline at end of file diff --git a/src/test/java/fun/trackmoney/config/exception/RestExceptionHandlerTest.java b/src/test/java/fun/trackmoney/config/exception/RestExceptionHandlerTest.java index 1cdcab8..20ff744 100644 --- a/src/test/java/fun/trackmoney/config/exception/RestExceptionHandlerTest.java +++ b/src/test/java/fun/trackmoney/config/exception/RestExceptionHandlerTest.java @@ -2,6 +2,7 @@ import fun.trackmoney.account.exception.AccountNotFoundException; import fun.trackmoney.auth.exception.LoginException; +import fun.trackmoney.budget.exception.BudgetsNotFoundException; import fun.trackmoney.category.exception.CategoryNotFoundException; import fun.trackmoney.goal.exception.GoalsNotFoundException; import fun.trackmoney.transaction.exception.TransactionNotFoundException; @@ -212,4 +213,21 @@ void goalsNotFound_shouldReturnNotFoundWithError() { assertEquals("Goals not found.", error.getMessage()); } + @Test + void budgetsNotFound_shouldReturnNotFoundWithError() { + BudgetsNotFoundException exception = new BudgetsNotFoundException("Budgets not found."); + ResponseEntity>> response = restExceptionHandler.budgetsNotFound(exception); + + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + ApiResponse> apiResponse = response.getBody(); + assertNotNull(apiResponse); + assertFalse(apiResponse.isSuccess()); + assertEquals("Budgets not found.", apiResponse.getMessage()); + assertNull(apiResponse.getData()); + assertNotNull(apiResponse.getErrors()); + assertEquals(1, (apiResponse.getErrors()).size()); + CustomFieldError error = apiResponse.getErrors().get(0); + assertEquals("Budgets", error.getField()); + assertEquals("Budgets not found.", error.getMessage()); + } } diff --git a/src/test/java/fun/trackmoney/user/service/UserServiceTest.java b/src/test/java/fun/trackmoney/user/service/UserServiceTest.java index 3359419..2306a3a 100644 --- a/src/test/java/fun/trackmoney/user/service/UserServiceTest.java +++ b/src/test/java/fun/trackmoney/user/service/UserServiceTest.java @@ -1,5 +1,6 @@ package fun.trackmoney.user.service; +import fun.trackmoney.account.service.AccountService; import fun.trackmoney.auth.dto.LoginRequestDTO; import fun.trackmoney.user.dtos.UserRequestDTO; import fun.trackmoney.user.dtos.UserResponseDTO; @@ -28,6 +29,9 @@ class UserServiceTest { @Mock private UserRepository userRepository; + @Mock + private AccountService accountService; + @Mock private UserMapper userMapper; @@ -55,6 +59,7 @@ void register_ValidUser_ReturnsUserResponseDTO() { when(passwordEncoder.encode("StrongPassword123#")).thenReturn("encodedPass"); when(userRepository.save(entityToSave)).thenReturn(savedEntity); when(userMapper.userEntityToUserResponseDto(savedEntity)).thenReturn(expectedResponse); + when(accountService.createAccount(any())).thenReturn(null); // Act UserResponseDTO actualResponse = userService.register(requestDTO);