diff --git a/passing-payment-controller-test.png b/passing-payment-controller-test.png new file mode 100644 index 0000000..7726f51 Binary files /dev/null and b/passing-payment-controller-test.png differ diff --git a/passing-payment-service-test.png b/passing-payment-service-test.png new file mode 100644 index 0000000..28573bb Binary files /dev/null and b/passing-payment-service-test.png differ diff --git a/payment-query-1.png b/payment-query-1.png new file mode 100644 index 0000000..fd0f7d4 Binary files /dev/null and b/payment-query-1.png differ diff --git a/payment-query-2.png b/payment-query-2.png new file mode 100644 index 0000000..35442d8 Binary files /dev/null and b/payment-query-2.png differ diff --git a/payment-sql.png b/payment-sql.png new file mode 100644 index 0000000..55d9013 Binary files /dev/null and b/payment-sql.png differ diff --git a/src/main/java/com/bravo/user/controller/PaymentController.java b/src/main/java/com/bravo/user/controller/PaymentController.java new file mode 100644 index 0000000..7077d18 --- /dev/null +++ b/src/main/java/com/bravo/user/controller/PaymentController.java @@ -0,0 +1,42 @@ +package com.bravo.user.controller; + +import com.bravo.user.annotation.SwaggerController; +import com.bravo.user.model.dto.PaymentDto; +import com.bravo.user.service.PaymentService; +import com.bravo.user.utility.PageUtil; +import com.bravo.user.validator.UserValidator; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.data.domain.PageRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@RequestMapping(value = "/payment") +@SwaggerController +public class PaymentController { + + private final PaymentService paymentService; + private final UserValidator userValidator; + + public PaymentController(PaymentService paymentService, UserValidator userValidator) { + this.paymentService = paymentService; + this.userValidator = userValidator; + } + + @GetMapping(value = "/retrieve/{userId}") + @ResponseBody + public List retrieve( + final @PathVariable String userId, + final @RequestParam(required = false) Integer page, + final @RequestParam(required = false) Integer size, + final HttpServletResponse httpResponse + ) { + userValidator.validateId(userId); + final PageRequest pageRequest = PageUtil.createPageRequest(page, size); + return paymentService.retrieveByUserId(userId, pageRequest, httpResponse); + } +} diff --git a/src/main/java/com/bravo/user/dao/repository/PaymentRepository.java b/src/main/java/com/bravo/user/dao/repository/PaymentRepository.java new file mode 100644 index 0000000..6deae63 --- /dev/null +++ b/src/main/java/com/bravo/user/dao/repository/PaymentRepository.java @@ -0,0 +1,10 @@ +package com.bravo.user.dao.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +import com.bravo.user.dao.model.Payment; + +@Repository +public interface PaymentRepository extends JpaRepository, JpaSpecificationExecutor{} diff --git a/src/main/java/com/bravo/user/dao/specification/PaymentSpecification.java b/src/main/java/com/bravo/user/dao/specification/PaymentSpecification.java new file mode 100644 index 0000000..dd62412 --- /dev/null +++ b/src/main/java/com/bravo/user/dao/specification/PaymentSpecification.java @@ -0,0 +1,25 @@ +package com.bravo.user.dao.specification; + +import com.bravo.user.dao.model.Payment; + +import java.util.Collections; + +import javax.persistence.criteria.*; + +public class PaymentSpecification extends AbstractSpecification { + + private final String userId; + + public PaymentSpecification(final String userId){ + this.userId = userId; + } + + @Override + public void doFilter( + Root root, + CriteriaQuery criteriaQuery, + CriteriaBuilder criteriaBuilder + ){ + applyStringFilter(root.get("userId"), Collections.singletonList(userId)); + } +} diff --git a/src/main/java/com/bravo/user/service/PaymentService.java b/src/main/java/com/bravo/user/service/PaymentService.java new file mode 100644 index 0000000..1870a6a --- /dev/null +++ b/src/main/java/com/bravo/user/service/PaymentService.java @@ -0,0 +1,42 @@ +package com.bravo.user.service; + +import com.bravo.user.dao.model.Payment; +import com.bravo.user.dao.model.mapper.ResourceMapper; +import com.bravo.user.dao.repository.PaymentRepository; +import com.bravo.user.dao.specification.PaymentSpecification; +import com.bravo.user.model.dto.PaymentDto; +import com.bravo.user.utility.PageUtil; + +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.data.domain.Page; + +@Service +public class PaymentService { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentService.class); + + private final PaymentRepository paymentRepository; + private final ResourceMapper resourceMapper; + + public PaymentService(PaymentRepository paymentRepository, ResourceMapper resourceMapper) { + this.paymentRepository = paymentRepository; + this.resourceMapper = resourceMapper; + } + + public List retrieveByUserId(final String userId, final PageRequest pageRequest, HttpServletResponse httpResponse) { + final PaymentSpecification specification = new PaymentSpecification(userId); + final Page paymentPage = paymentRepository.findAll(specification, pageRequest); + final List payments = resourceMapper.convertPayments(paymentPage.getContent()); + + LOGGER.info("found {} payment(s)", payments.size()); + PageUtil.updatePageHeaders(httpResponse, paymentPage, pageRequest); + return payments; + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index da45720..14d2e48 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -648,3 +648,8 @@ insert into address (id, user_id, line1, line2, city, state, zip) values ('42f33d30-f3f8-4743-a94e-4db11fdb747d', '008a4215-0b1d-445e-b655-a964039cbb5a', '412 Maple St', null, 'Dowagiac', 'Michigan', '49047'), ('579872ec-46f8-46b5-b809-d0724d965f0e', '00963d9b-f884-485e-9455-fcf30c6ac379', '237 Mountain Ter', 'Apt 10', 'Odenville', 'Alabama', '35120'), ('95a983d0-ba0e-4f30-afb6-667d4724b253', '00963d9b-f884-485e-9455-fcf30c6ac379', '107 Annettes Ct', null, 'Aydlett', 'North Carolina', '27916'); + +insert into payment (id, user_id, card_number, expiry_month, expiry_year) values +('payment-1', '008a4215-0b1d-445e-b655-a964039cbb5a', 'card-number-1', 12, 2027), +('payment-2', '00963d9b-f884-485e-9455-fcf30c6ac379', 'card-number-2', 11, 2026), +('payment-3', '00963d9b-f884-485e-9455-fcf30c6ac379', 'card-number-3', 10, 2025); \ No newline at end of file diff --git a/src/test/java/com/bravo/user/controller/PaymentControllerTest.java b/src/test/java/com/bravo/user/controller/PaymentControllerTest.java new file mode 100644 index 0000000..9a0aeab --- /dev/null +++ b/src/test/java/com/bravo/user/controller/PaymentControllerTest.java @@ -0,0 +1,97 @@ +package com.bravo.user.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import javax.servlet.http.HttpServletResponse; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import com.bravo.user.App; +import com.bravo.user.model.dto.PaymentDto; +import com.bravo.user.service.PaymentService; +import com.bravo.user.utility.PageUtil; + +@ContextConfiguration(classes = { App.class }) +@ExtendWith(SpringExtension.class) +@SpringBootTest() +@AutoConfigureMockMvc +class PaymentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private PaymentService paymentService; + + private List payments; + + @BeforeEach + public void beforeEach() { + final List ids = IntStream + .range(1, 10) + .boxed() + .collect(Collectors.toList()); + + this.payments = ids.stream() + .map(id -> createPaymentDto(Integer.toString(id))) + .collect(Collectors.toList()); + } + + @Test + void getRetrieveByUserId() throws Exception { + when(paymentService + .retrieveByUserId(anyString(), any(PageRequest.class), any(HttpServletResponse.class))) + .thenReturn(payments); + + final ResultActions result = this.mockMvc + .perform(get("/payment/retrieve/1")) + .andExpect(status().isOk()); + + for(int i = 0; i < payments.size(); i++){ + result.andExpect(jsonPath(String.format("$[%d].id", i)).value(payments.get(i).getId())); + } + + final PageRequest pageRequest = PageUtil.createPageRequest(null, null); + verify(paymentService).retrieveByUserId( + eq("1"), eq(pageRequest), any(HttpServletResponse.class) + ); + } + + @Test + void getRetrieveByUserId_Space() throws Exception { + this.mockMvc.perform(get("/payment/retrieve/ /")).andExpect(status().isBadRequest()); + } + + @Test + void getRetrieveByUserId_Missing() throws Exception { + this.mockMvc.perform(get("/payment/retrieve")).andExpect(status().isNotFound()); + } + + private PaymentDto createPaymentDto(final String id) { + final PaymentDto payment = new PaymentDto(); + payment.setId(id); + return payment; + } +} diff --git a/src/test/java/com/bravo/user/service/PaymentServiceTest.java b/src/test/java/com/bravo/user/service/PaymentServiceTest.java new file mode 100644 index 0000000..1d532eb --- /dev/null +++ b/src/test/java/com/bravo/user/service/PaymentServiceTest.java @@ -0,0 +1,107 @@ +package com.bravo.user.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import javax.servlet.http.HttpServletResponse; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import com.bravo.user.App; +import com.bravo.user.dao.model.Payment; +import com.bravo.user.dao.model.mapper.ResourceMapper; +import com.bravo.user.dao.repository.PaymentRepository; +import com.bravo.user.dao.specification.PaymentSpecification; +import com.bravo.user.model.dto.PaymentDto; +import com.bravo.user.utility.PageUtil; + +@ContextConfiguration(classes = { App.class }) +@ExtendWith(SpringExtension.class) +@SpringBootTest +class PaymentServiceTest { + + @Autowired + private HttpServletResponse httpResponse; + + @Autowired + private PaymentService paymentService; + + @MockBean + private ResourceMapper resourceMapper; + + @MockBean + private PaymentRepository paymentRepository; + + private List dtoPayments; + + @BeforeEach + public void beforeEach() { + final List ids = IntStream.range(1, 10).boxed().collect(Collectors.toList()); + + final Page mockPage = mock(Page.class); + when(paymentRepository.findAll(any(PaymentSpecification.class), any(PageRequest.class))) + .thenReturn(mockPage); + + + final List daoPayments = ids.stream() + .map(id -> createPayment(Integer.toString(id))) + .collect(Collectors.toList()); + when(mockPage.getContent()).thenReturn(daoPayments); + when(mockPage.getTotalPages()).thenReturn(9); + + this.dtoPayments = ids.stream() + .map(id -> createPaymentDto(Integer.toString(id))) + .collect(Collectors.toList()); + when(resourceMapper.convertPayments(daoPayments)).thenReturn(dtoPayments); + } + + @Test + void retrieveByUserId() { + final String input = "input"; + final PageRequest pageRequest = PageUtil.createPageRequest(null, null); + final List results = paymentService.retrieveByUserId(input, pageRequest, httpResponse); + assertEquals(dtoPayments, results); + assertEquals("9", httpResponse.getHeader("page-count")); + assertEquals("1", httpResponse.getHeader("page-number")); + assertEquals("20", httpResponse.getHeader("page-size")); + } + + @Test + void retrieveByUserIdPaged() { + final String input = "input2"; + final PageRequest pageRequest = PageUtil.createPageRequest(2, 5); + final List results = paymentService.retrieveByUserId(input, pageRequest, httpResponse); + assertEquals(dtoPayments, results); + assertEquals("9", httpResponse.getHeader("page-count")); + assertEquals("2", httpResponse.getHeader("page-number")); + assertEquals("5", httpResponse.getHeader("page-size")); + } + + private Payment createPayment(final String id) { + final Payment payment = new Payment(); + payment.setId(id); + return payment; + } + + private PaymentDto createPaymentDto(final String id) { + final PaymentDto payment = new PaymentDto(); + payment.setId(id); + return payment; + } + +} diff --git a/user_query.png b/user_query.png new file mode 100644 index 0000000..7f900ac Binary files /dev/null and b/user_query.png differ