Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jaxtell - added get payment by user id functionality #28

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
44 changes: 44 additions & 0 deletions src/main/java/com/bravo/user/controller/PaymentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.bravo.user.controller;

import java.util.List;

import com.bravo.user.utility.PageUtil;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;

import com.bravo.user.annotation.SwaggerController;
import com.bravo.user.model.dto.PaymentDto;
import com.bravo.user.service.PaymentService;
import com.bravo.user.validator.UserValidator;

import javax.servlet.http.HttpServletResponse;

@RequestMapping(value = "/payment")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any comment on how the endpoint should look if you were to design it without pattern following?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on how it would be used. If user id is always going to be present it might make sense to be /user/{userId}/payment. If there are future requests that allow searching across users then I'd leave it top level. Instead of using words in the URL I'd favor http verbs to indicate action, e.g. GET on /payment/user/{userId} to get all payments for a user and/or GET on /payment with parameters to get payment(s) based on query parameters, PUT on /payment to add a payment, PATCH on /payment to update a payment, POSTs on /payment/[ACTION] for more complex operations. I'd want to balance keeping the URL short while still being descriptive of what it does.

@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 = "/{userId}/retrieve")
@ResponseBody
public List<PaymentDto> retrieve(
final @PathVariable(name = "userId") 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);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public <T extends Collection<Payment>> List<PaymentDto> convertPayments(final T
public PaymentDto convertPayment(final Payment payment){
final String cardNumber = payment.getCardNumber();
final PaymentDto dto = mapperFacade.map(payment, PaymentDto.class);
dto.setCardNumberLast4(cardNumber.substring(cardNumber.length() - 5));
dto.setCardNumberLast4(cardNumber.substring(cardNumber.length() - 4));
return dto;
}

Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/bravo/user/dao/repository/PaymentRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.bravo.user.dao.repository;

import com.bravo.user.dao.model.Payment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;


@Repository
public interface PaymentRepository extends JpaRepository<Payment, String>, JpaSpecificationExecutor<Payment> {

// TODO Users instead uses findAll with a specification and filter.
//If we need to filter on more than just user id then this should too
//But for now this should work and be simpler.
Page<Payment> findByUserId(String userID, Pageable pageRequest);
}
37 changes: 37 additions & 0 deletions src/main/java/com/bravo/user/service/PaymentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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.model.dto.PaymentDto;
import com.bravo.user.utility.PageUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.util.List;

@Service
public class PaymentService {

private final PaymentRepository paymentRepository;
private final ResourceMapper resourceMapper;

public PaymentService(PaymentRepository paymentRepository, ResourceMapper resourceMapper) {
this.paymentRepository = paymentRepository;
this.resourceMapper = resourceMapper;

}

public List<PaymentDto> retrieveByUserId(String userId,
final PageRequest pageRequest,
final HttpServletResponse httpResponse) {
Page<Payment> paymentPage = paymentRepository.findByUserId(userId, pageRequest);
List<PaymentDto> payments = resourceMapper.convertPayments(paymentPage.getContent());
PageUtil.updatePageHeaders(httpResponse, paymentPage, pageRequest);
return payments;
}


}
7 changes: 7 additions & 0 deletions src/main/resources/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ create table payment (
updated timestamp not null default current_timestamp()
);

insert into payment(id, user_id, card_number, expiry_month, expiry_year) values
('4f34e905-72ad-412c-82fe-4974501258d6', '4f34e905-72ad-412c-82fe-4974501258d8', '5400000000000000', 12, 2024),
('4f34e905-72ad-412c-82fe-4974501258d5', '4f34e905-72ad-412c-82fe-4974501258d8', '5400000000000001', 12, 2025),
('4f34e905-72ad-412c-82fe-4974501258d4', '4f34e905-72ad-412c-82fe-4974501258d8', '5400000000000002', 10, 2026),
('4f34e905-72ad-412c-82fe-4974501258d3', '4f34e905-72ad-412c-82fe-4974501258d8', '5400000000000003', 12, 27),
('4f34e905-72ad-412c-82fe-4974501258d2', '0865dbbc-e4b9-4a22-af03-b4d67f56f07b', '5400000000000004', 12, 2028);

create table profile (
id varchar(60) primary key,
user_id varchar(60) not null,
Expand Down
90 changes: 90 additions & 0 deletions src/test/java/com/bravo/user/controller/PaymentControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.bravo.user.controller;

import com.bravo.user.App;
import com.bravo.user.model.dto.PaymentDto;
import com.bravo.user.service.PaymentService;
import com.bravo.user.utility.PageUtil;
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 javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.mockito.ArgumentMatchers.*;
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;

@ContextConfiguration(classes = {App.class})
@ExtendWith(SpringExtension.class)
@SpringBootTest()
@AutoConfigureMockMvc
public class PaymentControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private PaymentService paymentService;

private List<PaymentDto> payments;

@BeforeEach
public void beforeEach(){
final List<Integer> ids = IntStream
.range(1, 10)
.boxed()
.collect(Collectors.toList());

this.payments = ids.stream()
.map(id -> createPaymentDto(Integer.toString(id)))
.collect(Collectors.toList());
}

@Test
void getRetrieve() throws Exception {
when(paymentService
.retrieveByUserId(anyString(), any(PageRequest.class), any(HttpServletResponse.class)))
.thenReturn(payments);

final ResultActions result = this.mockMvc
.perform(get("/payment/testid/retrieve"))
.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("testid"), eq(pageRequest), any(HttpServletResponse.class)
);
}

@Test
void getRetrieveWithIdMissing() 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);
payment.setCardNumberLast4("0000");
return payment;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.bravo.user.dao.model.mapper;

import com.bravo.user.dao.model.Payment;
import com.bravo.user.model.dto.PaymentDto;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down Expand Up @@ -48,4 +50,15 @@ void convertAddressTest(
@ConvertWith(MapperArgConverter.class) AddressDto addressDto) {
Assertions.assertEquals(addressDto, resourceMapper.convertAddress(address));
}

@ParameterizedTest
@CsvFileSource(
resources = ("/ResourceMapperTest/convertPaymentTest.csv"),
delimiter = '$',
lineSeparator = ">")
void convertPaymentTest(
@ConvertWith(MapperArgConverter.class) Payment payment,
@ConvertWith(MapperArgConverter.class) PaymentDto paymentDto) {
Assertions.assertEquals(paymentDto, resourceMapper.convertPayment(payment));
}
}
97 changes: 97 additions & 0 deletions src/test/java/com/bravo/user/service/PaymentServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.bravo.user.service;

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.model.dto.PaymentDto;
import com.bravo.user.utility.PageUtil;
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 javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ContextConfiguration(classes = {App.class})
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class PaymentServiceTest {

@Autowired
private HttpServletResponse httpResponse;

@Autowired
private PaymentService paymentService;

@MockBean
private ResourceMapper resourceMapper;

@MockBean
private PaymentRepository paymentRepository;

private List<PaymentDto> dtoPayments;

@BeforeEach
public void beforeEach() {
final List<Integer> ids = IntStream
.range(1, 10)
.boxed()
.collect(Collectors.toList());

final Page<Payment> mockPage = mock(Page.class);
when(paymentRepository.findByUserId(any(String.class), any(PageRequest.class)))
.thenReturn(mockPage);

final List<Payment> 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
public void retrieveByName() {
final String input = "input";
final PageRequest pageRequest = PageUtil.createPageRequest(null, null);
final List<PaymentDto> 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"));

verify(paymentRepository).findByUserId(input, pageRequest);
}

private PaymentDto createPaymentDto(final String id) {
final PaymentDto payment = new PaymentDto();
payment.setId(id);
payment.setCardNumberLast4("0000");
return payment;
}

private Payment createPayment(final String id) {
final Payment payment = new Payment();
payment.setId(id);
payment.setCardNumber("5400000000000000");
return payment;
}
}
15 changes: 15 additions & 0 deletions src/test/resources/ResourceMapperTest/convertPaymentTest.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
>{
"id":"testId",
"userId":"testUserId",
"cardNumber":"4444444444441234",
"expiryMonth":"12",
"expiryYear":"99",
"updated":"2021-07-12 12:00:00"
}
${
"id":"testId",
"cardNumberLast4":"1234",
"expiryMonth":"12",
"expiryYear":"99",
"updated":"2021-07-12 12:00:00"
}