package service; import com.danielbohry.stocks.context.UserContext; import com.danielbohry.stocks.context.UserContextHolder; import com.danielbohry.stocks.domain.Portfolio; import com.danielbohry.stocks.domain.PortfolioStock; import com.danielbohry.stocks.exception.BadRequestException; import com.danielbohry.stocks.exception.NotFoundException; import com.danielbohry.stocks.exception.UnauthorizedException; import com.danielbohry.stocks.repository.portfolio.PortfolioEntity; import com.danielbohry.stocks.repository.portfolio.PortfolioRepository; import com.danielbohry.stocks.service.ExchangeService; import com.danielbohry.stocks.service.ExchangeService.ExchangeRateResponse; import com.danielbohry.stocks.service.portfolio.PortfolioEncryptService; import com.danielbohry.stocks.service.portfolio.PortfolioService; import com.danielbohry.stocks.service.stock.StockService; 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.List; import java.util.Map; import java.util.Optional; import static java.math.BigDecimal.TEN; import static java.math.BigDecimal.TWO; import static java.time.Instant.now; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; public class PortfolioServiceTest { @Mock private PortfolioRepository repository; @Mock private StockService stockService; @Mock private ExchangeService exchangeService; @Mock private PortfolioEncryptService encryptService; @InjectMocks private PortfolioService portfolioService; private static final String APPLE_CODE = "AAPL"; private static final String APPLE_NAME = "Apple Inc."; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); UserContextHolder.set(new UserContext("user-1", "user", List.of("USER"))); } @Test void testGetAllIds_returnsIds() { //given List ids = List.of("id1", "id2"); when(repository.findAllPortfolioIds()).thenReturn(ids); //expect assertEquals(ids, portfolioService.getAllIds()); } @Test void testGetByUser_mapsPortfolios() { //given PortfolioEntity entity = PortfolioEntity.builder() .id("p1") .user("user-1") .encryptedStocks("encrypted") .build(); when(repository.findAllByUser("user-1")).thenReturn(List.of(entity)); when(repository.findById("p1")).thenReturn(Optional.of(entity)); when(encryptService.decryptStocks("encrypted")).thenReturn(List.of()); when(exchangeService.getCurrentRate("USD")) .thenReturn(new ExchangeRateResponse("USD", "", Map.of("USD", BigDecimal.ONE))); //when List result = portfolioService.getByUser("user-1", "USD"); //then assertEquals(1, result.size()); } @Test void testGet_withMissingPortfolio_throws() { //given when(repository.findById("missing")).thenReturn(Optional.empty()); //expect assertThrows(NotFoundException.class, () -> portfolioService.get("missing", "USD")); } @Test void testCreate_savesNewPortfolio() { //given PortfolioEntity saved = PortfolioEntity.builder() .id("new-id").user("user-1") .stocks(List.of()) .createdAt(now()) .updatedAt(now()) .build(); when(repository.save(any())).thenReturn(saved); //when Portfolio result = portfolioService.create(); //then assertEquals("new-id", result.getId()); } @Test void testUpdate_validStocks_saves() { //given PortfolioEntity existing = PortfolioEntity.builder() .id("pid"). user("user-1") .build(); List stocks = List.of(new PortfolioStock(APPLE_CODE, APPLE_NAME, 5, TWO, TEN)); when(repository.findById("pid")).thenReturn(Optional.of(existing)); when(stockService.isValid(APPLE_CODE)).thenReturn(true); when(encryptService.encryptStocks(any())).thenReturn("encrypted"); when(repository.save(any())).thenReturn(existing); when(encryptService.decryptStocks("encrypted")).thenReturn(List.of(new PortfolioEntity.PortfolioStock(APPLE_CODE, 5))); //when Portfolio updated = portfolioService.update("pid", stocks); //then assertNotNull(updated); } @Test void testUpdate_invalidUser_throwsUnauthorized() { //given PortfolioEntity existing = PortfolioEntity.builder() .id("pid").user("another-user").build(); when(repository.findById("pid")).thenReturn(Optional.of(existing)); //expect assertThrows(UnauthorizedException.class, () -> portfolioService.update("pid", List.of())); } @Test void testUpdate_withInvalidStock_throwsBadRequest() { //given PortfolioEntity existing = PortfolioEntity.builder() .id("pid").user("user-1").build(); when(repository.findById("pid")).thenReturn(Optional.of(existing)); when(stockService.isValid("FAKE")) .thenReturn(false); //expect assertThrows(BadRequestException.class, () -> portfolioService.update("pid", List.of(new PortfolioStock("FAKE", "FAKE", 0, TWO, BigDecimal.ZERO)))); } @Test void testDelete_callsRepository() { //when portfolioService.delete("pid"); //expect verify(repository).deleteById("pid"); } @Test void testCleanup_deletesEmptyEncrypted() { //given PortfolioEntity empty = PortfolioEntity.builder() .id("pid").encryptedStocks("").build(); when(repository.findAll()).thenReturn(List.of(empty)); //when portfolioService.cleanup(); //then verify(repository).deleteById("pid"); } }