Daniel Bohry 7 сар өмнө
parent
commit
e3e5d8b528

+ 0 - 9
src/main/java/com/danielbohry/stocks/api/stock/StockController.java

@@ -2,9 +2,7 @@ package com.danielbohry.stocks.api.stock;
 
 import com.danielbohry.stocks.context.UserContextHolder;
 import com.danielbohry.stocks.domain.Stock;
-import com.danielbohry.stocks.domain.StockHistory;
 import com.danielbohry.stocks.domain.StockInfo;
-import com.danielbohry.stocks.service.stock.StockHistoryService;
 import com.danielbohry.stocks.service.stock.StockInfoService;
 import com.danielbohry.stocks.service.stock.StockService;
 import io.swagger.v3.oas.annotations.Hidden;
@@ -32,7 +30,6 @@ public class StockController {
 
     private final StockService service;
     private final StockInfoService infoService;
-    private final StockHistoryService historyService;
 
     @GetMapping
     public ResponseEntity<List<Stock>> find(@RequestParam(value = "q", required = false) String query) {
@@ -55,12 +52,6 @@ public class StockController {
         return ResponseEntity.ok(response);
     }
 
-    @GetMapping("{code}/history")
-    public ResponseEntity<?> getStockHistory(@PathVariable String code) {
-        List<StockHistory> response = historyService.get(code);
-        return ResponseEntity.ok(response);
-    }
-
     @Hidden
     @PostMapping("/upload-csv")
     public ResponseEntity<StockUploadCSVResponse> uploadCsvFile(@RequestParam("file") MultipartFile file, @RequestParam("currency") String currency) {

+ 27 - 0
src/main/java/com/danielbohry/stocks/api/stock/StockHistoryController.java

@@ -0,0 +1,27 @@
+package com.danielbohry.stocks.api.stock;
+
+import com.danielbohry.stocks.domain.StockHistory;
+import com.danielbohry.stocks.service.stock.StockHistoryService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+@RestController
+@RequestMapping("api/stocks/{code}")
+@AllArgsConstructor
+@CrossOrigin
+public class StockHistoryController {
+
+    private final StockHistoryService service;
+
+    @GetMapping("history")
+    public ResponseEntity<?> getStockHistory(@PathVariable String code) {
+        List<StockHistory> response = service.get(code);
+        return ResponseEntity.ok(response);
+    }
+
+}

+ 110 - 0
src/test/java/integration/PortfolioServiceIT.java

@@ -0,0 +1,110 @@
+package integration;
+
+import com.danielbohry.stocks.App;
+import com.danielbohry.stocks.domain.Portfolio;
+import com.danielbohry.stocks.domain.PortfolioStock;
+import com.danielbohry.stocks.repository.portfolio.PortfolioRepository;
+import com.danielbohry.stocks.repository.stock.StockRepository;
+import com.danielbohry.stocks.service.ExchangeService;
+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.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@Disabled
+@SpringBootTest
+@ContextConfiguration(classes = {App.class})
+public class PortfolioServiceIT {
+
+    @Autowired
+    private PortfolioRepository portfolioRepository;
+
+    @Mock
+    private StockRepository stockRepository;
+
+    @Mock
+    private ExchangeService exchangeService;
+
+    @Mock
+    private PortfolioEncryptService encryptService;
+
+    private PortfolioService portfolioService;
+
+    @BeforeEach
+    public void setup() {
+        StockService stockService = new StockService(stockRepository);
+        portfolioService = new PortfolioService(portfolioRepository, stockService, exchangeService, encryptService);
+    }
+
+    @AfterEach
+    public void teardown() {
+        portfolioRepository.deleteAll();
+    }
+
+    @Test
+    public void shouldCreateNewPortfolio() {
+        //when
+        Portfolio result = portfolioService.create();
+
+        //then
+        assertNotNull(result.getId());
+    }
+
+    @Test
+    public void shouldGetAPortfolio() {
+        //given
+        Portfolio portfolio = portfolioService.create();
+
+        //when
+        Portfolio result = portfolioService.get(portfolio.getId(), "USD");
+
+        //then
+        assertNotNull(result.getId());
+    }
+
+    @Test
+    public void shouldUpdateAPortfolio() {
+        //given
+        when(stockRepository.isValid(any())).thenReturn(true);
+
+        Portfolio portfolio = portfolioService.create();
+        PortfolioStock newStock = PortfolioStock.builder()
+            .code("code")
+            .quantity(3)
+            .build();
+
+        //when
+        Portfolio result = portfolioService.update(portfolio.getId(), List.of(newStock));
+
+        //then
+        assertNotNull(result.getId());
+        assertFalse(result.getStocks().isEmpty());
+    }
+
+    @Test
+    public void shouldDeleteAPortfolio() {
+        //given
+        Portfolio portfolio = portfolioService.create();
+
+        //when
+        portfolioService.delete(portfolio.getId());
+
+        //then
+        assertEquals(portfolioRepository.findById(portfolio.getId()), Optional.empty());
+    }
+
+}

+ 59 - 0
src/test/java/integration/StockServiceIT.java

@@ -0,0 +1,59 @@
+package integration;
+
+import com.danielbohry.stocks.App;
+import com.danielbohry.stocks.domain.Stock;
+import com.danielbohry.stocks.repository.stock.StockRepository;
+import com.danielbohry.stocks.service.stock.StockService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Disabled
+@SpringBootTest
+@ContextConfiguration(classes = {App.class})
+public class StockServiceIT {
+
+    @Autowired
+    private StockRepository repository;
+
+    private StockService service;
+
+    @BeforeEach
+    public void setup() {
+        service = new StockService(repository);
+    }
+
+    @Test
+    public void shouldGetStockByCode() {
+        //given
+        String code = "AAPL";
+        Stock expected = new Stock(code, "Apple Inc.", "USD", null, null, null);
+
+        //when
+        Stock result = service.getByCode(code);
+
+        //then
+        assertEquals(expected.getCode(), result.getCode());
+        assertEquals(expected.getName(), result.getName());
+    }
+
+    @Test
+    public void shouldGetStockByName() {
+        //given
+        String name = "West";
+
+        //when
+        List<Stock> result = service.get(name);
+
+        //then
+        assertEquals(6, result.size());
+    }
+
+}

+ 126 - 42
src/test/java/service/PortfolioServiceTest.java

@@ -1,40 +1,43 @@
 package service;
 
-import com.danielbohry.stocks.App;
+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.repository.stock.StockRepository;
 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.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ContextConfiguration;
+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.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
-@Disabled
-@SpringBootTest
-@ContextConfiguration(classes = {App.class})
 public class PortfolioServiceTest {
 
-    @Autowired
-    private PortfolioRepository portfolioRepository;
+    @Mock
+    private PortfolioRepository repository;
 
     @Mock
-    private StockRepository stockRepository;
+    private StockService stockService;
 
     @Mock
     private ExchangeService exchangeService;
@@ -42,69 +45,150 @@ public class PortfolioServiceTest {
     @Mock
     private PortfolioEncryptService encryptService;
 
+    @InjectMocks
     private PortfolioService portfolioService;
 
+    private static final String APPLE_CODE = "AAPL";
+    private static final String APPLE_NAME = "Apple Inc.";
+
     @BeforeEach
-    public void setup() {
-        StockService stockService = new StockService(stockRepository);
-        portfolioService = new PortfolioService(portfolioRepository, stockService, exchangeService, encryptService);
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+        UserContextHolder.set(new UserContext("user-1", "user", List.of("USER")));
     }
 
-    @AfterEach
-    public void teardown() {
-        portfolioRepository.deleteAll();
+    @Test
+    void testGetAllIds_returnsIds() {
+        //given
+        List<String> ids = List.of("id1", "id2");
+        when(repository.findAllPortfolioIds()).thenReturn(ids);
+
+        //expect
+        assertEquals(ids, portfolioService.getAllIds());
     }
 
     @Test
-    public void shouldCreateNewPortfolio() {
+    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
-        Portfolio result = portfolioService.create();
+        List<Portfolio> result = portfolioService.getByUser("user-1", "USD");
 
         //then
-        assertNotNull(result.getId());
+        assertEquals(1, result.size());
     }
 
     @Test
-    public void shouldGetAPortfolio() {
+    void testGet_withMissingPortfolio_throws() {
         //given
-        Portfolio portfolio = portfolioService.create();
+        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.get(portfolio.getId(), "USD");
+        Portfolio result = portfolioService.create();
 
         //then
-        assertNotNull(result.getId());
+        assertEquals("new-id", result.getId());
     }
 
     @Test
-    public void shouldUpdateAPortfolio() {
+    void testUpdate_validStocks_saves() {
         //given
-        when(stockRepository.isValid(any())).thenReturn(true);
-
-        Portfolio portfolio = portfolioService.create();
-        PortfolioStock newStock = PortfolioStock.builder()
-            .code("code")
-            .quantity(3)
+        PortfolioEntity existing = PortfolioEntity.builder()
+            .id("pid").
+            user("user-1")
             .build();
 
+        List<PortfolioStock> 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 result = portfolioService.update(portfolio.getId(), List.of(newStock));
+        Portfolio updated = portfolioService.update("pid", stocks);
 
         //then
-        assertNotNull(result.getId());
-        assertFalse(result.getStocks().isEmpty());
+        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
-    public void shouldDeleteAPortfolio() {
+    void testUpdate_withInvalidStock_throwsBadRequest() {
         //given
-        Portfolio portfolio = portfolioService.create();
+        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.delete(portfolio.getId());
+        portfolioService.cleanup();
 
         //then
-        assertEquals(portfolioRepository.findById(portfolio.getId()), Optional.empty());
+        verify(repository).deleteById("pid");
     }
 
 }

+ 51 - 0
src/test/java/service/StockHistoryServiceTest.java

@@ -0,0 +1,51 @@
+package service;
+
+import com.danielbohry.stocks.domain.StockHistory;
+import com.danielbohry.stocks.repository.stock.StockHistoryRepository;
+import com.danielbohry.stocks.service.stock.StockHistoryService;
+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 static java.time.Instant.now;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+public class StockHistoryServiceTest {
+
+    @Mock
+    private StockHistoryRepository repository;
+
+    @InjectMocks
+    private StockHistoryService stockHistoryService;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    void testGet_returnsStockHistoryFromRepository() {
+        //given
+        String code = "AAPL";
+        List<StockHistory> expected = List.of(
+            new StockHistory("AAPL", "USD", new BigDecimal("150.00"), now())
+        );
+
+        //and
+        when(repository.findAllByCode(code)).thenReturn(expected);
+
+        //when
+        List<StockHistory> result = stockHistoryService.get(code);
+
+        //then
+        assertEquals(expected, result);
+        verify(repository, times(1)).findAllByCode(code);
+    }
+
+}

+ 90 - 22
src/test/java/service/StockServiceTest.java

@@ -1,59 +1,127 @@
 package service;
 
-import com.danielbohry.stocks.App;
 import com.danielbohry.stocks.domain.Stock;
 import com.danielbohry.stocks.repository.stock.StockRepository;
 import com.danielbohry.stocks.service.stock.StockService;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ContextConfiguration;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.util.Arrays;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.*;
 
-@Disabled
-@SpringBootTest
-@ContextConfiguration(classes = {App.class})
 public class StockServiceTest {
 
-    @Autowired
+    @Mock
     private StockRepository repository;
 
-    private StockService service;
+    @InjectMocks
+    private StockService stockService;
+
+    private Stock stock1;
+    private Stock stock2;
 
     @BeforeEach
-    public void setup() {
-        service = new StockService(repository);
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+        stock1 = new Stock("1", "AAPL", "Apple Inc.", "USD", new BigDecimal("150.00"), new BigDecimal("2500000000000"), Instant.now());
+        stock2 = new Stock("2", "GOOG", "Alphabet Inc.", "USD", new BigDecimal("2800.00"), new BigDecimal("1800000000000"), Instant.now());
+    }
+
+    @Test
+    void testGetAll_returnsSortedStocksByName() {
+        //given
+        when(repository.findAll()).thenReturn(List.of(stock2, stock1));
+
+        //when
+        List<Stock> result = stockService.getAll();
+
+        //then
+        assertEquals(2, result.size());
+        assertEquals("Alphabet Inc.", result.get(0).getName());
+        assertEquals("Apple Inc.", result.get(1).getName());
+    }
+
+    @Test
+    void testGet_withEmptyQuery_returnsEmptyList() {
+        //when
+        List<Stock> result = stockService.get("");
+
+        //then
+        assertTrue(result.isEmpty());
+        verify(repository, never()).findLike(any());
+    }
+
+    @Test
+    void testGet_withQuery_callsRepository() {
+        //given
+        when(repository.findLike("tech")).thenReturn(List.of(stock1));
+
+        //when
+        List<Stock> result = stockService.get("tech");
+
+        //then
+        assertEquals(1, result.size());
+        assertEquals("AAPL", result.get(0).getCode());
+        verify(repository, times(1)).findLike("tech");
+    }
+
+    @Test
+    void testGetByCode_returnsStock() {
+        //given
+        when(repository.findByCode("AAPL")).thenReturn(stock1);
+
+        //when
+        Stock result = stockService.getByCode("AAPL");
+
+        //then
+        assertEquals(stock1, result);
+        verify(repository).findByCode("AAPL");
     }
 
     @Test
-    public void shouldGetStockByCode() {
+    void testUpdate_delegatesToRepository() {
         //given
-        String code = "AAPL";
-        Stock expected = new Stock(code, "Apple Inc.", "USD", null, null, null);
+        List<Stock> input = Arrays.asList(stock1, stock2);
+        when(repository.update(input)).thenReturn(input);
 
         //when
-        Stock result = service.getByCode(code);
+        List<Stock> result = stockService.update(input);
 
         //then
-        assertEquals(expected.getCode(), result.getCode());
-        assertEquals(expected.getName(), result.getName());
+        assertEquals(input, result);
+        verify(repository).update(input);
+    }
+
+    @Test
+    void testIsValid_delegatesToRepository() {
+        //given
+        when(repository.isValid("AAPL")).thenReturn(true);
+
+        //expect
+        assertTrue(stockService.isValid("AAPL"));
+        verify(repository).isValid("AAPL");
     }
 
     @Test
-    public void shouldGetStockByName() {
+    void testGetStockQuote_delegatesToRepository() {
         //given
-        String name = "West";
+        when(repository.getStockQuote("AAPL")).thenReturn(stock1);
 
         //when
-        List<Stock> result = service.get(name);
+        Stock result = stockService.getStockQuote("AAPL");
 
         //then
-        assertEquals(6, result.size());
+        assertEquals(stock1, result);
+        verify(repository).getStockQuote("AAPL");
     }
 
 }