18 changed files with 480 additions and 135 deletions
@ -1,82 +1,83 @@ |
|||
package com.singlestone.contacts.controller; |
|||
|
|||
import com.singlestone.contacts.model.CallListEntry; |
|||
import com.singlestone.contacts.model.Contact; |
|||
import com.singlestone.contacts.model.Phone; |
|||
import com.singlestone.contacts.model.dto.CallListDTO; |
|||
import com.singlestone.contacts.model.dto.ContactDTO; |
|||
import com.singlestone.contacts.repository.ContactRepository; |
|||
import com.singlestone.contacts.repository.PhoneRepository; |
|||
import com.singlestone.contacts.service.ContactService; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.domain.Sort; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.server.ResponseStatusException; |
|||
|
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.http.ResponseEntity; |
|||
|
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
|
|||
@RestController |
|||
// @RequestMapping("/api/v1")
|
|||
@RequestMapping("/api/v1") |
|||
public class ContactController { |
|||
|
|||
@Autowired |
|||
private ContactService contactService; |
|||
|
|||
@Autowired |
|||
private PhoneRepository phoneRepository; |
|||
|
|||
//Get All Contacts
|
|||
@GetMapping("/contacts") |
|||
public List<ContactDTO> getAllContacts() { |
|||
return contactService.getAllContacts(); |
|||
public ResponseEntity<List<ContactDTO>> getAllContacts() { |
|||
List<ContactDTO> allContacts = contactService.getAllContacts(); |
|||
if (allContacts.isEmpty()) { |
|||
return new ResponseEntity<>(HttpStatus.NO_CONTENT); |
|||
} |
|||
return new ResponseEntity<>(allContacts, HttpStatus.OK); |
|||
} |
|||
|
|||
//Create a new contact
|
|||
@PostMapping("/contacts") |
|||
public void newContact(@RequestBody Contact newContact) |
|||
public ResponseEntity<HttpStatus> createContact(@RequestBody ContactDTO newContact) |
|||
{ |
|||
contactRepository.save(newContact); |
|||
ContactDTO createdContact = contactService.createContact(newContact); |
|||
if (createdContact == null) { |
|||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); |
|||
} |
|||
return new ResponseEntity<>(HttpStatus.CREATED); |
|||
} |
|||
|
|||
//Update an existing contact
|
|||
@PutMapping("/contacts/{id}") |
|||
public void updateContact(@PathVariable long id, @RequestBody Contact newContact) |
|||
public ResponseEntity<HttpStatus> updateContact(@PathVariable long id, @RequestBody ContactDTO newContactInfo) |
|||
{ |
|||
Optional<Contact> contact = contactRepository.findById(id); |
|||
if (contact.isEmpty()) { |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
ContactDTO updatedContact = contactService.updateContact(id, newContactInfo); |
|||
if (updatedContact == null) { |
|||
return new ResponseEntity<>(HttpStatus.NOT_FOUND); |
|||
} |
|||
Contact updatedContact = new Contact(id, newContact); |
|||
contactRepository.save(updatedContact); |
|||
return new ResponseEntity<>(HttpStatus.OK); |
|||
} |
|||
|
|||
//Get a single contact
|
|||
@GetMapping("/contacts/{id}") |
|||
public Contact getContact(@PathVariable long id) { |
|||
Optional<Contact> contact = contactRepository.findById(id); |
|||
if (contact.isEmpty()) { |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unable to find specified contact ID: " + id); |
|||
public ResponseEntity<ContactDTO> getContact(@PathVariable long id) { |
|||
ContactDTO contact = contactService.getContact(id); |
|||
if (contact == null) { |
|||
return new ResponseEntity<>(HttpStatus.NOT_FOUND); |
|||
} |
|||
return contact.get(); |
|||
return new ResponseEntity<>(contact, HttpStatus.OK); |
|||
} |
|||
|
|||
//Delete a contact
|
|||
@DeleteMapping("/contacts/{id}") |
|||
public void deleteContact(@PathVariable long id) { |
|||
Optional<Contact> contact = contactRepository.findById(id); |
|||
if (contact.isEmpty()) { |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unable to find specified contact ID: " + id); |
|||
public ResponseEntity<HttpStatus> deleteContact(@PathVariable long id) { |
|||
ContactDTO contact = contactService.deleteContact(id); |
|||
if (contact == null) { |
|||
return new ResponseEntity<>(HttpStatus.NOT_FOUND); |
|||
} |
|||
contactRepository.deleteById(id); |
|||
return new ResponseEntity<>(HttpStatus.NO_CONTENT); |
|||
} |
|||
|
|||
//Get a single contact
|
|||
//Get a list of contacts with home phone numbers
|
|||
@GetMapping("/contacts/call-list") |
|||
public List<CallListEntry> getCallList() { |
|||
Sort sortByLast = Sort.by("contact.name.last").ascending(); |
|||
Sort sortByFirst = Sort.by("contact.name.first").ascending(); |
|||
return phoneRepository.findByType(Phone.Type.home, sortByLast.and(sortByFirst)); |
|||
public ResponseEntity<List<CallListDTO>> getCallList() { |
|||
List<CallListDTO> callList = contactService.getCallList(); |
|||
if (callList.isEmpty()) { |
|||
return new ResponseEntity<>(HttpStatus.NO_CONTENT); |
|||
} |
|||
return new ResponseEntity<>(callList, HttpStatus.OK); |
|||
} |
|||
} |
|||
|
|||
@ -1,10 +0,0 @@ |
|||
package com.singlestone.contacts.model; |
|||
|
|||
import org.springframework.beans.factory.annotation.Value; |
|||
|
|||
public interface CallListEntry { |
|||
@Value("#{target.getContact().getName()}") |
|||
Name getName(); |
|||
@Value("#{target.getNumber()}") |
|||
String getPhone(); |
|||
} |
|||
@ -1,11 +1,23 @@ |
|||
package com.singlestone.contacts.model.dto; |
|||
|
|||
import com.singlestone.contacts.model.Address; |
|||
import com.singlestone.contacts.model.Name; |
|||
import com.singlestone.contacts.model.Phone; |
|||
|
|||
public class CallListDTO { |
|||
|
|||
private Name name; |
|||
private String phone; |
|||
|
|||
|
|||
public CallListDTO(Phone phone) { |
|||
name = phone.getContact().getName(); |
|||
this.phone = phone.getNumber(); |
|||
} |
|||
|
|||
public Name getName() { |
|||
return name; |
|||
} |
|||
|
|||
public String getPhone() { |
|||
return phone; |
|||
} |
|||
} |
|||
|
|||
@ -1,6 +1,23 @@ |
|||
package com.singlestone.contacts.model.dto; |
|||
|
|||
import com.singlestone.contacts.model.Phone.Type; |
|||
|
|||
public class PhoneDTO { |
|||
private String number; |
|||
private Type type; |
|||
private String type; |
|||
|
|||
public PhoneDTO() {} |
|||
|
|||
public PhoneDTO(String number, Type type) { |
|||
this.number = number; |
|||
this.type = type.name().toLowerCase(); |
|||
} |
|||
|
|||
public String getNumber() { |
|||
return number; |
|||
} |
|||
|
|||
public String getType() { |
|||
return type; |
|||
} |
|||
} |
|||
|
|||
@ -1,14 +1,17 @@ |
|||
|
|||
#Enable H2 console |
|||
# ---Comments without preceeding '---' are settings to enable for development |
|||
# ---Enable H2 console |
|||
spring.h2.console.enabled=true |
|||
#Turn Statistics on |
|||
# ---Turn Statistics on |
|||
spring.jpa.properties.hibernate.generate_statistics=true |
|||
logging.level.org.hibernate.stat=debug |
|||
# Show all queries |
|||
# ---Show queries |
|||
spring.jpa.show-sql=true |
|||
# ---In memory DB |
|||
spring.datasource.url=jdbc:h2:mem:contactsdb |
|||
spring.data.jpa.repositories.bootstrap-mode=default |
|||
spring.jpa.properties.hibernate.format_sql=true |
|||
# ---Initialize Hibernate before loading from data.sql |
|||
spring.jpa.defer-datasource-initialization=true |
|||
logging.level.org.hibernate.type=trace |
|||
server.error.include-message=always |
|||
logging.level.org.hibernate.type=debug |
|||
# ---For development only |
|||
# server.error.include-message=always |
|||
|
|||
@ -0,0 +1,125 @@ |
|||
package com.singlestone.contacts.controller; |
|||
|
|||
import static org.mockito.ArgumentMatchers.any; |
|||
import static org.mockito.Mockito.times; |
|||
import static org.mockito.Mockito.verify; |
|||
import static org.mockito.Mockito.when; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.singlestone.contacts.model.Address; |
|||
import com.singlestone.contacts.model.Name; |
|||
import com.singlestone.contacts.model.Phone.Type; |
|||
import com.singlestone.contacts.model.dto.ContactDTO; |
|||
import com.singlestone.contacts.model.dto.PhoneDTO; |
|||
import com.singlestone.contacts.service.ContactService; |
|||
|
|||
import org.junit.jupiter.api.BeforeEach; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.junit.jupiter.api.extension.ExtendWith; |
|||
import org.mockito.InjectMocks; |
|||
import org.mockito.Mock; |
|||
import org.mockito.junit.jupiter.MockitoExtension; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.test.web.servlet.MockMvc; |
|||
import org.springframework.test.web.servlet.setup.MockMvcBuilders; |
|||
|
|||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@ExtendWith(MockitoExtension.class) |
|||
public class ContactControllerTest { |
|||
|
|||
@Mock |
|||
private ContactService contactService; |
|||
|
|||
@InjectMocks |
|||
private ContactController contactController; |
|||
|
|||
@Autowired |
|||
private MockMvc mockMvc; |
|||
|
|||
private ContactDTO createTestContactDTO() { |
|||
ContactDTO testContactDTO = new ContactDTO(); |
|||
|
|||
Name name = new Name(); |
|||
name.setFirst("first"); |
|||
name.setMiddle("middle"); |
|||
name.setLast("last"); |
|||
testContactDTO.setName(name); |
|||
|
|||
Address address = new Address(); |
|||
address.setStreet("street"); |
|||
address.setCity("city"); |
|||
address.setState("state"); |
|||
address.setZip("zip"); |
|||
|
|||
List<PhoneDTO> phones = new ArrayList<>(); |
|||
phones.add(new PhoneDTO("111-111-1111", Type.HOME)); |
|||
|
|||
testContactDTO.setPhone(phones); |
|||
testContactDTO.setEmail("e@mail.com"); |
|||
|
|||
return testContactDTO; |
|||
} |
|||
|
|||
@BeforeEach |
|||
public void setup(){ |
|||
mockMvc = MockMvcBuilders.standaloneSetup(contactController).build(); |
|||
} |
|||
|
|||
@Test |
|||
void testCreateContact() throws Exception { |
|||
ContactDTO contactToCreate = createTestContactDTO(); |
|||
when(contactService.createContact(any())).thenReturn(contactToCreate); |
|||
mockMvc.perform(post("/api/v1/contacts"). |
|||
contentType(MediaType.APPLICATION_JSON). |
|||
content(asJsonString(contactToCreate))). |
|||
andExpect(status().isCreated()); |
|||
verify(contactService, times(1)).createContact(any()); |
|||
} |
|||
|
|||
@Test |
|||
void testGetAllContacts() throws Exception { |
|||
ContactDTO contactToCreate = createTestContactDTO(); |
|||
List<ContactDTO> contacts = new ArrayList<>(); |
|||
contacts.add(contactToCreate); |
|||
when(contactService.getAllContacts()).thenReturn(contacts); |
|||
mockMvc.perform(get("/api/v1/contacts"). |
|||
accept(MediaType.APPLICATION_JSON). |
|||
content("")). |
|||
andExpect(status().isOk()); |
|||
verify(contactService, times(1)).getAllContacts(); |
|||
} |
|||
|
|||
@Test |
|||
void testGetContact() { |
|||
|
|||
} |
|||
|
|||
@Test |
|||
void testUpdateContact() { |
|||
|
|||
} |
|||
|
|||
@Test |
|||
void testDeleteContact() { |
|||
|
|||
} |
|||
|
|||
@Test |
|||
void testGetCallList() { |
|||
|
|||
} |
|||
|
|||
public static String asJsonString(final Object obj){ |
|||
try{ |
|||
return new ObjectMapper().writeValueAsString(obj); |
|||
}catch (Exception e){ |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
# ---Comments without preceeding '---' are settings to enable for development |
|||
# ---Enable H2 console |
|||
spring.h2.console.enabled=true |
|||
# ---Turn Statistics on |
|||
spring.jpa.properties.hibernate.generate_statistics=true |
|||
logging.level.org.hibernate.stat=debug |
|||
# ---Show queries |
|||
spring.jpa.show-sql=true |
|||
# ---In memory DB |
|||
spring.datasource.url=jdbc:h2:mem:contactsdb |
|||
spring.data.jpa.repositories.bootstrap-mode=default |
|||
spring.jpa.properties.hibernate.format_sql=true |
|||
# ---Initialize Hibernate before loading from data.sql |
|||
spring.jpa.defer-datasource-initialization=true |
|||
logging.level.org.hibernate.type=debug |
|||
# ---For development only |
|||
# server.error.include-message=always |
|||
@ -0,0 +1,25 @@ |
|||
-- Contacts |
|||
insert into CONTACTS (street, city, state, zip, email, middle, first, last) |
|||
values('street1', 'city1', 'state1', 'zip1', 'e1@mail.com', 'a', 'a', 'a'); |
|||
insert into CONTACTS (street, city, state, zip, email, middle, first, last) |
|||
values('street2', 'city2', 'state2', 'zip2', 'e2@mail.com', 'a', 'b', 'a'); |
|||
insert into CONTACTS (street, city, state, zip, email, middle, first, last) |
|||
values('street3', 'city3', 'state3', 'zip3', 'e3@mail.com', 'a', 'b', 'b'); |
|||
insert into CONTACTS (street, city, state, zip, email, middle, first, last) |
|||
values('street4', 'city4', 'state4', 'zip4', 'e4@mail.com', 'a', 'a', 'b'); |
|||
|
|||
-- Phones |
|||
insert into PHONES (number, type, contact_id) |
|||
values('111-111-1111', 'HOME', 1); |
|||
insert into PHONES (number, type, contact_id) |
|||
values('222-222-2222', 'WORK', 1); |
|||
insert into PHONES (number, type, contact_id) |
|||
values('333-333-3333', 'WORK', 2); |
|||
insert into PHONES (number, type, contact_id) |
|||
values('444-444-4444', 'HOME', 2); |
|||
insert into PHONES (number, type, contact_id) |
|||
values('555-555-5555', 'MOBILE', 3); |
|||
insert into PHONES (number, type, contact_id) |
|||
values('777-777-7777', 'WORK', 4); |
|||
insert into PHONES (number, type, contact_id) |
|||
values('888-888-8888', 'HOME', 4); |
|||
@ -0,0 +1,116 @@ |
|||
package com.singlestone.contacts.service; |
|||
|
|||
import static org.mockito.ArgumentMatchers.any; |
|||
import static org.mockito.ArgumentMatchers.anyLong; |
|||
import static org.mockito.Mockito.times; |
|||
import static org.mockito.Mockito.verify; |
|||
import static org.mockito.Mockito.when; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
|
|||
import com.singlestone.contacts.model.Address; |
|||
import com.singlestone.contacts.model.Contact; |
|||
import com.singlestone.contacts.model.Name; |
|||
import com.singlestone.contacts.model.Phone; |
|||
import com.singlestone.contacts.model.dto.ContactDTO; |
|||
import com.singlestone.contacts.repository.ContactRepository; |
|||
import com.singlestone.contacts.repository.PhoneRepository; |
|||
|
|||
import org.junit.jupiter.api.Test; |
|||
import org.junit.jupiter.api.extension.ExtendWith; |
|||
import org.mockito.InjectMocks; |
|||
import org.mockito.Mock; |
|||
import org.mockito.junit.jupiter.MockitoExtension; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
|
|||
@ExtendWith(MockitoExtension.class) |
|||
public class ContactServiceTest { |
|||
|
|||
@Mock |
|||
private ContactRepository contactRepository; |
|||
|
|||
@Mock |
|||
private PhoneRepository phoneRepository; |
|||
|
|||
@Autowired |
|||
@InjectMocks |
|||
private ContactService contactService; |
|||
|
|||
private Contact createTestContact() { |
|||
Contact testContact = new Contact(); |
|||
|
|||
Name name = new Name(); |
|||
name.setFirst("first"); |
|||
name.setMiddle("middle"); |
|||
name.setLast("last"); |
|||
testContact.setName(name); |
|||
|
|||
Address address = new Address(); |
|||
address.setStreet("street"); |
|||
address.setCity("city"); |
|||
address.setState("state"); |
|||
address.setZip("zip"); |
|||
|
|||
List<Phone> phones = new ArrayList<>(); |
|||
phones.add(new Phone("111-111-1111", "home")); |
|||
|
|||
testContact.setPhone(phones); |
|||
testContact.setEmail("e@mail.com"); |
|||
|
|||
return testContact; |
|||
} |
|||
|
|||
@Test |
|||
void testCreateContact() { |
|||
Contact contact = createTestContact(); |
|||
when(contactRepository.save(any())).thenReturn(contact); |
|||
contactService.createContact(new ContactDTO(contact)); |
|||
verify(contactRepository, times(1)).save(any()); |
|||
} |
|||
|
|||
@Test |
|||
void testGetAllContacts() { |
|||
List<Contact> contacts = new ArrayList<>(); |
|||
contacts.add(createTestContact()); |
|||
when(contactRepository.findAll()).thenReturn(contacts); |
|||
contactService.getAllContacts(); |
|||
verify(contactRepository, times(1)).findAll(); |
|||
} |
|||
|
|||
@Test |
|||
void testGetContact() { |
|||
when(contactRepository.findById(anyLong())).thenReturn(Optional.of(createTestContact())); |
|||
contactService.getContact(1); |
|||
verify(contactRepository, times(1)).findById(anyLong()); |
|||
} |
|||
|
|||
@Test |
|||
void testUpdateContact() { |
|||
Contact testContact = createTestContact(); |
|||
testContact.setId(1L); |
|||
when(contactRepository.findById(anyLong())).thenReturn(Optional.of(testContact)); |
|||
contactService.updateContact(1, new ContactDTO(testContact)); |
|||
verify(contactRepository, times(1)).findById(anyLong()); |
|||
verify(contactRepository, times(1)).save(any()); |
|||
} |
|||
|
|||
@Test |
|||
void testDeleteContact() { |
|||
when(contactRepository.findById(anyLong())).thenReturn(Optional.of(createTestContact())); |
|||
contactService.deleteContact(1); |
|||
verify(contactRepository, times(1)).deleteById(anyLong()); |
|||
} |
|||
|
|||
@Test |
|||
void testGetCallList() { |
|||
List<Phone> phones = new ArrayList<>(); |
|||
Phone testPhone = new Phone("111-111-1111", "home"); |
|||
testPhone.setContact(createTestContact()); |
|||
phones.add(testPhone); |
|||
when(phoneRepository.findByType(any(), any())).thenReturn(phones); |
|||
contactService.getCallList(); |
|||
verify(phoneRepository, times(1)).findByType(any(), any()); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue