18 changed files with 480 additions and 135 deletions
@ -1,82 +1,83 @@ |
|||||
package com.singlestone.contacts.controller; |
package com.singlestone.contacts.controller; |
||||
|
|
||||
import com.singlestone.contacts.model.CallListEntry; |
import com.singlestone.contacts.model.dto.CallListDTO; |
||||
import com.singlestone.contacts.model.Contact; |
|
||||
import com.singlestone.contacts.model.Phone; |
|
||||
import com.singlestone.contacts.model.dto.ContactDTO; |
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 com.singlestone.contacts.service.ContactService; |
||||
|
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
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.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.List; |
||||
import java.util.Optional; |
|
||||
|
|
||||
@RestController |
@RestController |
||||
// @RequestMapping("/api/v1")
|
@RequestMapping("/api/v1") |
||||
public class ContactController { |
public class ContactController { |
||||
|
|
||||
@Autowired |
@Autowired |
||||
private ContactService contactService; |
private ContactService contactService; |
||||
|
|
||||
@Autowired |
|
||||
private PhoneRepository phoneRepository; |
|
||||
|
|
||||
//Get All Contacts
|
//Get All Contacts
|
||||
@GetMapping("/contacts") |
@GetMapping("/contacts") |
||||
public List<ContactDTO> getAllContacts() { |
public ResponseEntity<List<ContactDTO>> getAllContacts() { |
||||
return contactService.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
|
//Create a new contact
|
||||
@PostMapping("/contacts") |
@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
|
//Update an existing contact
|
||||
@PutMapping("/contacts/{id}") |
@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); |
ContactDTO updatedContact = contactService.updateContact(id, newContactInfo); |
||||
if (contact.isEmpty()) { |
if (updatedContact == null) { |
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
return new ResponseEntity<>(HttpStatus.NOT_FOUND); |
||||
} |
} |
||||
Contact updatedContact = new Contact(id, newContact); |
return new ResponseEntity<>(HttpStatus.OK); |
||||
contactRepository.save(updatedContact); |
|
||||
} |
} |
||||
|
|
||||
//Get a single contact
|
//Get a single contact
|
||||
@GetMapping("/contacts/{id}") |
@GetMapping("/contacts/{id}") |
||||
public Contact getContact(@PathVariable long id) { |
public ResponseEntity<ContactDTO> getContact(@PathVariable long id) { |
||||
Optional<Contact> contact = contactRepository.findById(id); |
ContactDTO contact = contactService.getContact(id); |
||||
if (contact.isEmpty()) { |
if (contact == null) { |
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unable to find specified contact ID: " + id); |
return new ResponseEntity<>(HttpStatus.NOT_FOUND); |
||||
} |
} |
||||
return contact.get(); |
return new ResponseEntity<>(contact, HttpStatus.OK); |
||||
} |
} |
||||
|
|
||||
//Delete a contact
|
//Delete a contact
|
||||
@DeleteMapping("/contacts/{id}") |
@DeleteMapping("/contacts/{id}") |
||||
public void deleteContact(@PathVariable long id) { |
public ResponseEntity<HttpStatus> deleteContact(@PathVariable long id) { |
||||
Optional<Contact> contact = contactRepository.findById(id); |
ContactDTO contact = contactService.deleteContact(id); |
||||
if (contact.isEmpty()) { |
if (contact == null) { |
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unable to find specified contact ID: " + id); |
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") |
@GetMapping("/contacts/call-list") |
||||
public List<CallListEntry> getCallList() { |
public ResponseEntity<List<CallListDTO>> getCallList() { |
||||
Sort sortByLast = Sort.by("contact.name.last").ascending(); |
List<CallListDTO> callList = contactService.getCallList(); |
||||
Sort sortByFirst = Sort.by("contact.name.first").ascending(); |
if (callList.isEmpty()) { |
||||
return phoneRepository.findByType(Phone.Type.home, sortByLast.and(sortByFirst)); |
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; |
package com.singlestone.contacts.model.dto; |
||||
|
|
||||
import com.singlestone.contacts.model.Address; |
|
||||
import com.singlestone.contacts.model.Name; |
import com.singlestone.contacts.model.Name; |
||||
|
import com.singlestone.contacts.model.Phone; |
||||
|
|
||||
public class CallListDTO { |
public class CallListDTO { |
||||
|
|
||||
private Name name; |
private Name name; |
||||
private String phone; |
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; |
package com.singlestone.contacts.model.dto; |
||||
|
|
||||
|
import com.singlestone.contacts.model.Phone.Type; |
||||
|
|
||||
public class PhoneDTO { |
public class PhoneDTO { |
||||
private String number; |
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 @@ |
|||||
|
# ---Comments without preceeding '---' are settings to enable for development |
||||
#Enable H2 console |
# ---Enable H2 console |
||||
spring.h2.console.enabled=true |
spring.h2.console.enabled=true |
||||
#Turn Statistics on |
# ---Turn Statistics on |
||||
spring.jpa.properties.hibernate.generate_statistics=true |
spring.jpa.properties.hibernate.generate_statistics=true |
||||
logging.level.org.hibernate.stat=debug |
logging.level.org.hibernate.stat=debug |
||||
# Show all queries |
# ---Show queries |
||||
spring.jpa.show-sql=true |
spring.jpa.show-sql=true |
||||
|
# ---In memory DB |
||||
spring.datasource.url=jdbc:h2:mem:contactsdb |
spring.datasource.url=jdbc:h2:mem:contactsdb |
||||
spring.data.jpa.repositories.bootstrap-mode=default |
spring.data.jpa.repositories.bootstrap-mode=default |
||||
spring.jpa.properties.hibernate.format_sql=true |
spring.jpa.properties.hibernate.format_sql=true |
||||
|
# ---Initialize Hibernate before loading from data.sql |
||||
spring.jpa.defer-datasource-initialization=true |
spring.jpa.defer-datasource-initialization=true |
||||
logging.level.org.hibernate.type=trace |
logging.level.org.hibernate.type=debug |
||||
server.error.include-message=always |
# ---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