Skip to content

Commit

Permalink
Merge pull request #11 from alvinmarshall/4-endpoint-to-fetch-customer
Browse files Browse the repository at this point in the history
chore: add fetch customers endpoint
  • Loading branch information
alvinmarshall authored Jul 28, 2024
2 parents a77ed23 + dd32dcc commit 8a55340
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 22 deletions.
11 changes: 9 additions & 2 deletions src/main/java/com/cheise_proj/auditing/Address.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cheise_proj.auditing;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;

Expand All @@ -24,17 +25,23 @@ class Address {
@Column(name = "zip_code")
private String zipCode;

@ManyToOne
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
@ToString.Exclude
private Customer customer;

static Address of(CustomerDto.CustomerAddress customerAddress) {
@Column(name = "customer_id", insertable = false, updatable = false)
private Long customerId;

static Address of(CustomerDto.CustomerAddress customerAddress, Customer customer) {
return Address.builder()
.city(customerAddress.city())
.streetAddress(customerAddress.streetAddress())
.stateCode(customerAddress.stateCode())
.country(customerAddress.country())
.zipCode(customerAddress.zipCode())
.customer(customer)
.build();
}

Expand Down
10 changes: 4 additions & 6 deletions src/main/java/com/cheise_proj/auditing/Customer.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,21 @@ class Customer {
private String emailAddress;

@ToString.Exclude
@OneToMany(mappedBy = "customer", orphanRemoval = true)
@OneToMany(mappedBy = "customer", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Address> addresses;

static Customer of(CustomerDto.CreateCustomer customer) {
Customer customerEntity = Customer.builder()
return Customer.builder()
.firstName(customer.firstName())
.lastName(customer.lastName())
.emailAddress(customer.emailAddress())
.build();
customerEntity.setAddresses(customer.customerAddress());
return customerEntity;
}

void setAddresses(Set<CustomerDto.CustomerAddress> customerAddresses) {
void setAddresses(Set<CustomerDto.CustomerAddress> customerAddresses, Customer customer) {
if (customerAddresses == null) return;
this.addresses = (this.addresses == null) ? new LinkedHashSet<>() : this.addresses;
Set<Address> addressSet = customerAddresses.stream().map(Address::of).collect(Collectors.toSet());
Set<Address> addressSet = customerAddresses.stream().map(customerAddress -> Address.of(customerAddress, customer)).collect(Collectors.toSet());
this.addresses.addAll(addressSet);
}
}
27 changes: 23 additions & 4 deletions src/main/java/com/cheise_proj/auditing/CustomerController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.cheise_proj.auditing;

import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/customers")
Expand All @@ -25,4 +26,22 @@ ResponseEntity<URI> createCustomer(@RequestBody @Valid CustomerDto.CreateCustome
URI location = UriComponentsBuilder.fromPath("/customers/{id}").buildAndExpand(customer.getId()).toUri();
return ResponseEntity.created(location).build();
}

@GetMapping
ResponseEntity<Object> index(
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "10") int size
) {
Page<Customer> customerPage = customerService.getCustomers(PageRequest.of(page, size));
List<CustomerDto.GetCustomer> customerList = customerPage.getContent().stream().map(CustomerDto::toGetCustomer).toList();
Map<String, Object> customers = Map.of("customers", customerList, "total", customerPage.getTotalElements());
return ResponseEntity.ok(customers);
}

@GetMapping("{id}")
ResponseEntity<Object> getCustomer(@PathVariable("id") Long id) {
Customer customer = customerService.getCustomer(id);
return ResponseEntity.ok(CustomerDto.toCustomer(customer));
}

}
38 changes: 37 additions & 1 deletion src/main/java/com/cheise_proj/auditing/CustomerDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
import lombok.Builder;

import java.util.Set;
import java.util.stream.Collectors;

interface CustomerDto {
@Builder
record CreateCustomer(
@NotBlank @JsonProperty String firstName,
@NotBlank @JsonProperty String lastName,
@Email @JsonProperty("email") String emailAddress,
Set<CustomerAddress> customerAddress
@JsonProperty Set<CustomerAddress> customerAddress
) implements CustomerDto {
}

Expand All @@ -26,4 +27,39 @@ record CustomerAddress(
@JsonProperty String zipCode
) {
}

@Builder
record GetCustomer(
@JsonProperty String firstName,
@JsonProperty String lastName,
@JsonProperty("email") String emailAddress,
Set<CustomerAddress> customerAddress
) implements CustomerDto {
}

static GetCustomer toGetCustomer(Customer customer) {
Set<CustomerAddress> customerAddresses = null;
if (customer.getAddresses() != null) {
customerAddresses = customer.getAddresses().stream().map(address -> CustomerAddress.builder()
.zipCode(address.getZipCode())
.city(address.getCity())
.country(address.getCountry())
.stateCode(address.getStateCode())
.streetAddress(address.getStreetAddress())
.build()).collect(Collectors.toSet());
}
return GetCustomer.builder()
.firstName(customer.getFirstName())
.lastName(customer.getLastName())
.emailAddress(customer.getEmailAddress())
.customerAddress(customerAddresses)
.build();
}
static GetCustomer toCustomer(Customer customer) {
return GetCustomer.builder()
.firstName(customer.getFirstName())
.lastName(customer.getLastName())
.emailAddress(customer.getEmailAddress())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.cheise_proj.auditing;

import jakarta.persistence.EntityNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
class CustomerExceptionAdviser {

@ExceptionHandler(value = {EntityNotFoundException.class})
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ProblemDetail resourceNotFoundException(EntityNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package com.cheise_proj.auditing;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

interface CustomerRepository extends JpaRepository<Customer, Long> {
@EntityGraph(attributePaths = "addresses")
@Override
Page<Customer> findAll(Pageable pageable);
}
14 changes: 14 additions & 0 deletions src/main/java/com/cheise_proj/auditing/CustomerService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.cheise_proj.auditing;

import jakarta.persistence.EntityNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -12,6 +15,17 @@ class CustomerService {

Customer createCustomer(CustomerDto.CreateCustomer customer) {
Customer newCustomer = Customer.of(customer);
newCustomer.setAddresses(customer.customerAddress(), newCustomer);
return customerRepository.save(newCustomer);
}

Page<Customer> getCustomers(Pageable pageable) {
return customerRepository.findAll(pageable);
}

public Customer getCustomer(Long id) {
return customerRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Customer with id %d not found".formatted(id)));
}

}
3 changes: 2 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
spring:
application:
name: auditing

jpa:
open-in-view: false
42 changes: 42 additions & 0 deletions src/test/java/com/cheise_proj/auditing/CustomerControllerIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,46 @@ void createCustomer_returns_400() throws Exception {
).andExpect(MockMvcResultMatchers.status().isBadRequest())
.andDo(result -> log.info("result: {}", result.getResponse().getHeaderValue("location")));
}

@Test
void getCustomers_With_Address_returns_200() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content(CustomerFixture.createCustomerWithAddress(objectMapper))
).andExpectAll(MockMvcResultMatchers.status().isCreated())
.andDo(result -> log.info("result: {}", result.getResponse().getHeaderValue("location")));

mockMvc.perform(MockMvcRequestBuilders.get("/customers")
.contentType(MediaType.APPLICATION_JSON)
).andExpectAll(MockMvcResultMatchers.status().isOk())
.andDo(result -> log.info("result: {}", result.getResponse().getContentAsString()));
}

@Test
void getCustomer_by_id_returns_200() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content(CustomerFixture.createCustomerWithAddress(objectMapper))
).andExpectAll(MockMvcResultMatchers.status().isCreated())
.andDo(result -> log.info("result: {}", result.getResponse().getHeaderValue("location")));

mockMvc.perform(MockMvcRequestBuilders.get("/customers/1")
.contentType(MediaType.APPLICATION_JSON)
).andExpectAll(MockMvcResultMatchers.status().isOk())
.andDo(result -> log.info("result: {}", result.getResponse().getContentAsString()));
}

@Test
void getCustomer_by_id_returns_404() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content(CustomerFixture.createCustomerWithAddress(objectMapper))
).andExpectAll(MockMvcResultMatchers.status().isCreated())
.andDo(result -> log.info("result: {}", result.getResponse().getHeaderValue("location")));

mockMvc.perform(MockMvcRequestBuilders.get("/customers/10")
.contentType(MediaType.APPLICATION_JSON)
).andExpectAll(MockMvcResultMatchers.status().isNotFound())
.andDo(result -> log.info("result: {}", result.getResponse().getContentAsString()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.cheise_proj.auditing;

import jakarta.persistence.EntityNotFoundException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ProblemDetail;

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

@ExtendWith(MockitoExtension.class)
class CustomerExceptionAdviserTest {
@InjectMocks
private CustomerExceptionAdviser sut;

@Test
void resourceNotFoundException() {
ProblemDetail notFound = sut.resourceNotFoundException(new EntityNotFoundException("not found"));
assertNotNull(notFound);
}
}
51 changes: 43 additions & 8 deletions src/test/java/com/cheise_proj/auditing/CustomerServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.cheise_proj.auditing;

import jakarta.persistence.EntityNotFoundException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -37,7 +44,7 @@ void createCustomer() {
.emailAddress("[email protected]")
.build();
sut.createCustomer(customerDto);
Mockito.verify(customerRepository,Mockito.atMostOnce()).save(customerArgumentCaptor.capture());
Mockito.verify(customerRepository, Mockito.atMostOnce()).save(customerArgumentCaptor.capture());
Customer customer = customerArgumentCaptor.getValue();
assertNotNull(customer);
assertEquals("Claribel", customer.getFirstName());
Expand All @@ -52,17 +59,45 @@ void createCustomerWithAddress() {
.lastName("Zieme")
.emailAddress("[email protected]")
.customerAddress(Set.of(CustomerDto.CustomerAddress.builder()
.city("Risaberg")
.country("USA")
.streetAddress("942 Walker Street")
.stateCode("WV")
.zipCode("88742")
.city("Risaberg")
.country("USA")
.streetAddress("942 Walker Street")
.stateCode("WV")
.zipCode("88742")
.build()))
.build();
sut.createCustomer(customerDto);
Mockito.verify(customerRepository,Mockito.atMostOnce()).save(customerArgumentCaptor.capture());
Mockito.verify(customerRepository, Mockito.atMostOnce()).save(customerArgumentCaptor.capture());
Customer customer = customerArgumentCaptor.getValue();
assertNotNull(customer);
assertEquals(1,customer.getAddresses().size());
assertEquals(1, customer.getAddresses().size());
}

@Test
void getCustomers() {
List<Customer> customerList = List.of(Customer.builder().id(1L).build());
Mockito.when(customerRepository.findAll(ArgumentMatchers.any(Pageable.class)))
.thenReturn(new PageImpl<>(customerList));
Page<Customer> customerPage = sut.getCustomers(Pageable.ofSize(1));
assertNotNull(customerPage);
assertEquals(1, customerPage.getTotalElements());
assertEquals(1, customerPage.getContent().size());
}

@Test
void getCustomer() {
Mockito.when(customerRepository.findById(ArgumentMatchers.anyLong()))
.thenReturn(Optional.of(Customer.builder().id(1L).build()));
Customer customer = sut.getCustomer(1L);
assertNotNull(customer);
}

@Test
void getCustomer_throw_if_customer_not_found() {
EntityNotFoundException exception = Assertions.assertThrows(
EntityNotFoundException.class,
() -> sut.getCustomer(1L)
);
assertEquals("Customer with id 1 not found", exception.getMessage());
}
}

0 comments on commit 8a55340

Please sign in to comment.