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 @@ -15,7 +15,7 @@
import java.util.List;
import java.util.UUID;

@CrossOrigin(origins = "http://localhost:3000")
@CrossOrigin(origins = "http://localhost:8080")
@RestController
@RequestMapping("/products")
final class InventoryManagerController {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,270 @@
package com.encorazone.inventory_manager.controller;


import com.encorazone.inventory_manager.domain.*;
import com.encorazone.inventory_manager.service.InventoryService;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.hasItems;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class InventoryManagerControllerTests {

@Autowired
private MockMvc mockMvc;
private MockMvc mvc;

@Autowired
ObjectMapper objectMapper;

@MockitoBean
private InventoryService inventoryService;

@Test
void getAllProducts_shouldReturnDataFromDatabase() throws Exception {
mockMvc.perform(get("/products"))
.andExpect(status().isOk());
@DisplayName("returns selected page with products when getting all products")
void getAll_returnsPage() throws Exception {
ProductListResponse payload = new ProductListResponse(List.of(sampleProductRes()), 1);
given(inventoryService.getAll(0, 10)).willReturn(payload);

mvc.perform(get("/products"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.products", hasSize(1)))
.andExpect(jsonPath("$.totalPages", is(1)));
}

@Test
@DisplayName("returns selected page according to de filter/sort orders")
void findByFilter_passesPageable() throws Exception {
ProductListResponse payload = new ProductListResponse(List.of(sampleProductRes()), 1);
ArgumentCaptor<Pageable> captor = ArgumentCaptor.forClass(Pageable.class);
Pageable expected = PageRequest.of(1, 10, Sort.by(Sort.Order.desc("price")));
given(inventoryService.findByNameAndCategoryAndStockQuantity(
"Watermelon", "food", 0, expected))
.willReturn(payload);

mvc.perform(get("/products/filters")
.param("name", "Watermelon")
.param("category", "food")
.param("stockQuantity", "0")
.param("page", "1")
.param("size", "10")
.param("sort", "price,desc"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.products", hasSize(1)))
.andExpect(jsonPath("$.totalPages", is(1)));
verify(inventoryService)
.findByNameAndCategoryAndStockQuantity(
eq("Watermelon"),
eq("food"),
eq(0),
captor.capture());

Pageable p = captor.getValue();
assert p.getPageNumber() == 1;
assert p.getPageSize() == 10;
Sort.Order o = p.getSort().getOrderFor("price");
assert o != null && o.getDirection() == Sort.Direction.DESC;
}

@Test
@DisplayName("returns small product descriptions corresponding to the created product")
void create_returnsShortResp() throws Exception {
ProductShortResponse created = sampleProductShort();
given(inventoryService.create(nullable(Product.class))).willReturn(created);

Product toCreate = sampleProduct();
mvc.perform(post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(toCreate)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", notNullValue()))
.andExpect(jsonPath("$.name", is("Watermelon")));
}

@Test
@DisplayName("returns small product descriptions corresponding to the updated product")
void update_returnsShortResp() throws Exception {
ProductShortResponse updated = sampleProductShort();
UUID id = updated.getId();
given(inventoryService.update(eq(id), nullable(Product.class))).willReturn(Optional.of(updated));

mvc.perform(put("/products/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(sampleProduct())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(id.toString())))
.andExpect(jsonPath("$.name", is("Watermelon")));
}

@Test
@DisplayName("returns 404-Not Found when no product id corresponds to the sent one - update")
void update_returnsNotFound() throws Exception {
UUID id = UUID.randomUUID();
given(inventoryService.update(eq(id), nullable(Product.class))).willReturn(Optional.empty());

mvc.perform(put("/products/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(sampleProduct())))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("returns small product descriptions corresponding to the marked out of stock product")
void markOutOfStock_returnsShortResp() throws Exception {
ProductShortResponse resp = sampleProductShort();
UUID id = resp.getId();
given(inventoryService.markOutOfStock(id)).willReturn(Optional.of(resp));

mvc.perform(patch("/products/{id}/outofstock", id))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(id.toString())))
.andExpect(jsonPath("$.name", is("Watermelon")));
}

@Test
@DisplayName("returns 404-Not Found when no product id corresponds to the sent one - markOutOfStock")
void markOutOfStock_returnsNotFound() throws Exception {
UUID id = UUID.randomUUID();
given(inventoryService.markOutOfStock(id)).willReturn(Optional.empty());

mvc.perform(patch("/products/{id}/outofstock", id))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("returns small product descriptions corresponding to the restored stock product")
void restoreStock_returnsShortResp() throws Exception {
ProductShortResponse resp = sampleProductShort();
UUID id = resp.getId();
given(inventoryService.updateStock(id, 10)).willReturn(Optional.of(resp));

mvc.perform(patch("/products/{id}/instock", id).param("stockQuantity", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(id.toString())))
.andExpect(jsonPath("$.name", is("Watermelon")));
}

@Test
@DisplayName("returns 404-Not Found when no product id corresponds to the sent one - restoreStock")
void restoreStock_returnsNotFound() throws Exception {
UUID id = UUID.randomUUID();
given(inventoryService.updateStock(id, 10)).willReturn(Optional.empty());

mvc.perform(patch("/products/{id}/instock", id).param("stockQuantity", "10"))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("returns 204-No Content when deleting a product")
void delete_returnsNoContent() throws Exception {
UUID id = UUID.randomUUID();
doNothing().when(inventoryService).delete(id);

mvc.perform(delete("/products/{id}", id))
.andExpect(status().isNoContent());
}

@Test
@DisplayName("returns a list with all the categories with products")
void fetchCategories_returnsCategories() throws Exception {
given(inventoryService.fetchCategories()).willReturn(Optional.of(List.of("food", "drinks")));

mvc.perform(get("/products/categories"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasItems("food", "drinks")));
}

@Test
@DisplayName("returns 404-Not Found when no category is available")
void fetchCategories_returnsNotFound() throws Exception {
given(inventoryService.fetchCategories()).willReturn(Optional.empty());

mvc.perform(get("/products/categories"))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("returns Inventory Summary when calling for metrics")
void fetchSummary_returnsInventorySummary() throws Exception {
InventorySummaryResponse row = new InventorySummaryResponse("food", 100L, BigDecimal.valueOf(2500), BigDecimal.valueOf(25));
given(inventoryService.fetchInventorySummary()).willReturn(Optional.of(List.of(row)));

mvc.perform(get("/products/summary"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].category", is("food")));
}

@Test
@DisplayName("returns 404-Not Found when no product in stock")
void fetchSummary_returnsNotFound() throws Exception {
given(inventoryService.fetchInventorySummary()).willReturn(Optional.empty());

mvc.perform(get("/products/summary"))
.andExpect(status().isNotFound());
}

private Product sampleProduct() {
Product p = new Product();
p.setName("Melon");
p.setCategory("food");
p.setUnitPrice(BigDecimal.valueOf(25));
p.setStockQuantity(5);
return p;
}

private ProductResponse sampleProductRes() {
return new ProductResponse(
UUID.randomUUID(),
"Watermelon",
"food",
BigDecimal.valueOf(20),
null,
10,
LocalDateTime.now(),
LocalDateTime.now());
}

private ProductShortResponse sampleProductShort() {
return new ProductShortResponse(
UUID.randomUUID(),
"Watermelon",
LocalDateTime.now(),
LocalDateTime.now());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.encorazone.inventory_manager.mapper;

import com.encorazone.inventory_manager.domain.Product;
import com.encorazone.inventory_manager.domain.InventorySummaryInterface;
import com.encorazone.inventory_manager.domain.ProductListResponse;
import com.encorazone.inventory_manager.domain.ProductResponse;
import com.encorazone.inventory_manager.domain.ProductShortResponse;
import com.encorazone.inventory_manager.domain.InventorySummaryResponse;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;

class ProductMapperTests {

@Test
void toProductShortResponse_mapsFields() {
Product p = sampleProduct();
ProductShortResponse r = ProductMapper.toProductShortResponse(p);
assertEquals(p.getId(), r.getId());
assertEquals(p.getName(), r.getName());
assertEquals(p.getCreationDate(), r.getCreationDate());
assertEquals(p.getUpdateDate(), r.getUpdateDate());
}

@Test
void toProductResponse_mapsFields() {
Product p = sampleProduct();
ProductResponse r = ProductMapper.toProductResponse(p);
assertEquals(p.getId(), r.getId());
assertEquals(p.getName(), r.getName());
assertEquals(p.getCategory(), r.getCategory());
assertEquals(p.getUnitPrice(), r.getUnitPrice());
assertEquals(p.getExpirationDate(), r.getExpirationDate());
assertEquals(p.getStockQuantity(), r.getStockQuantity());
assertEquals(p.getCreationDate(), r.getCreationDate());
assertEquals(p.getUpdateDate(), r.getUpdateDate());
}

@Test
void toProductListResponse_mapsListAndTotalPages() {
Product p1 = sampleProduct();
Product p2 = sampleProduct();
p2.setId(UUID.randomUUID());
p2.setName("Another");
ProductListResponse r = ProductMapper.toProductListResponse(List.of(p1, p2), 7);
assertEquals(2, r.getProducts().size());
assertEquals(7, r.getTotalPages());
assertEquals(p1.getId(), r.getProducts().get(0).getId());
assertEquals(p2.getId(), r.getProducts().get(1).getId());
}

@Test
void toInventorySummaryResponse_mapsFields() {
InventorySummaryInterface row = sampleSummaryRow("food", 12L, new BigDecimal("345.67"), new BigDecimal("28.81"));
InventorySummaryResponse r = ProductMapper.toInventorySummaryResponse(row);
assertEquals("food", r.getCategory());
assertEquals(12L, Long.valueOf(r.getProductsInStock()));
assertEquals(new BigDecimal("345.67"), r.getValueInStock());
assertEquals(new BigDecimal("28.81"), r.getAverageValue());
}

@Test
void toInventorySummaryResponseList_mapsList() {
List<InventorySummaryResponse> list = ProductMapper.toInventorySummaryResponseList(List.of(
sampleSummaryRow("food", 10L, new BigDecimal("100.00"), new BigDecimal("10.00")),
sampleSummaryRow("drinks", 5L, new BigDecimal("55.50"), new BigDecimal("11.10"))
));
assertEquals(2, list.size());
assertEquals("food", list.get(0).getCategory());
assertEquals("drinks", list.get(1).getCategory());
}

private Product sampleProduct() {
Product p = new Product();
p.setId(UUID.randomUUID());
p.setName("Watermelon");
p.setCategory("food");
p.setUnitPrice(new BigDecimal("19.99"));
p.setExpirationDate(LocalDate.now().plusDays(30));
p.setStockQuantity(7);
p.setCreationDate(LocalDateTime.now().minusDays(1));
p.setUpdateDate(LocalDateTime.now());
return p;
}

private InventorySummaryInterface sampleSummaryRow(String category, Long inStock, BigDecimal value, BigDecimal avg) {
return new InventorySummaryInterface() {
@Override public String getCategory() { return category; }
@Override public Long getProductsInStock() { return inStock; }
@Override public BigDecimal getValueInStock() { return value; }
@Override public BigDecimal getAverageValue() { return avg; }
};
}
}
Loading