From ad0b5aa6992bb32e3a11a2d1383a2fd2c9b78c44 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Wed, 20 Jun 2018 08:13:32 -0500 Subject: [PATCH 1/3] Item B-03526: As a Manager, I wold like to sort services in the service management table The ServiceSpecification is implemented to provide sorting and pagination. The AbstracSpecification used a default 'lastModified' order by, which does not exist in the ServiceRepo. To prevent breakage, the the order by assignment was move into its own function, toPredicateDefaultQueryOrderBy(). This allows for the ServiceSpecification, as an exception, to not attempt to order by "lastModified" by overriding that new method. --- .../app/controller/ServiceController.java | 7 +++++++ .../edu/tamu/app/model/repo/ServiceRepo.java | 5 +++++ .../specification/AbstractSpecification.java | 9 ++++++++- .../specification/ServiceSpecification.java | 19 +++++++++++++++++++ .../model/request/FilteredPageRequest.java | 7 +++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/main/java/edu/tamu/app/model/repo/specification/ServiceSpecification.java diff --git a/src/main/java/edu/tamu/app/controller/ServiceController.java b/src/main/java/edu/tamu/app/controller/ServiceController.java index cce2ffe..6f0f8de 100644 --- a/src/main/java/edu/tamu/app/controller/ServiceController.java +++ b/src/main/java/edu/tamu/app/controller/ServiceController.java @@ -17,6 +17,7 @@ import edu.tamu.app.model.Service; import edu.tamu.app.model.repo.IdeaRepo; import edu.tamu.app.model.repo.ServiceRepo; +import edu.tamu.app.model.request.FilteredPageRequest; import edu.tamu.app.model.request.IssueRequest; import edu.tamu.app.model.request.ServiceRequest; import edu.tamu.app.service.ProjectService; @@ -52,6 +53,12 @@ public ApiResponse getPublicServices() { return new ApiResponse(SUCCESS, serviceRepo.findByIsPublicOrderByStatusDescNameAsc(true)); } + @RequestMapping("/page") + @PreAuthorize("hasRole('ANONYMOUS')") + public ApiResponse page(@RequestBody FilteredPageRequest filteredPageRequest) { + return new ApiResponse(SUCCESS, serviceRepo.findAll(filteredPageRequest.getServiceSpecification(), filteredPageRequest.getPageRequest())); + } + @RequestMapping("/{id}") @PreAuthorize("hasRole('ANONYMOUS')") public ApiResponse getService(@PathVariable Long id) { diff --git a/src/main/java/edu/tamu/app/model/repo/ServiceRepo.java b/src/main/java/edu/tamu/app/model/repo/ServiceRepo.java index 6487a5e..8a58c4c 100644 --- a/src/main/java/edu/tamu/app/model/repo/ServiceRepo.java +++ b/src/main/java/edu/tamu/app/model/repo/ServiceRepo.java @@ -2,6 +2,9 @@ import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; import edu.tamu.app.enums.Status; @@ -10,6 +13,8 @@ public interface ServiceRepo extends JpaRepository, ServiceRepoCustom { + public Page findAll(Specification specification, Pageable pageable); + public List findByIsPublicOrderByStatusDescNameAsc(Boolean isPublic); public List findByIsAuto(Boolean isAuto); diff --git a/src/main/java/edu/tamu/app/model/repo/specification/AbstractSpecification.java b/src/main/java/edu/tamu/app/model/repo/specification/AbstractSpecification.java index 7abdab5..7dd2e25 100644 --- a/src/main/java/edu/tamu/app/model/repo/specification/AbstractSpecification.java +++ b/src/main/java/edu/tamu/app/model/repo/specification/AbstractSpecification.java @@ -49,11 +49,18 @@ public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuild } } - query.orderBy(cb.desc(root.get("lastModified"))); + toPredicateDefaultQueryOrderBy(root, query, cb); return builder.build(cb); } + /** + * Allow implementing classes to control order by in case lastModified is non-existent. + */ + protected void toPredicateDefaultQueryOrderBy(Root root, CriteriaQuery query, CriteriaBuilder cb) { + query.orderBy(cb.desc(root.get("lastModified"))); + } + private class PredicateBuilder { private final Map> predicates; diff --git a/src/main/java/edu/tamu/app/model/repo/specification/ServiceSpecification.java b/src/main/java/edu/tamu/app/model/repo/specification/ServiceSpecification.java new file mode 100644 index 0000000..17b6d3a --- /dev/null +++ b/src/main/java/edu/tamu/app/model/repo/specification/ServiceSpecification.java @@ -0,0 +1,19 @@ +package edu.tamu.app.model.repo.specification; + +import java.util.Map; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +public class ServiceSpecification extends AbstractSpecification { + + public ServiceSpecification(Map filters) { + super(filters); + } + + @Override + protected void toPredicateDefaultQueryOrderBy(Root root, CriteriaQuery query, CriteriaBuilder cb) { + query.orderBy(cb.desc(root.get("name"))); + } +} diff --git a/src/main/java/edu/tamu/app/model/request/FilteredPageRequest.java b/src/main/java/edu/tamu/app/model/request/FilteredPageRequest.java index 4269ca8..334473e 100644 --- a/src/main/java/edu/tamu/app/model/request/FilteredPageRequest.java +++ b/src/main/java/edu/tamu/app/model/request/FilteredPageRequest.java @@ -13,9 +13,11 @@ import edu.tamu.app.model.FeatureProposal; import edu.tamu.app.model.Idea; import edu.tamu.app.model.Note; +import edu.tamu.app.model.Service; import edu.tamu.app.model.repo.specification.FeatureProposalSpecification; import edu.tamu.app.model.repo.specification.IdeaSpecification; import edu.tamu.app.model.repo.specification.NoteSpecification; +import edu.tamu.app.model.repo.specification.ServiceSpecification; public class FilteredPageRequest { @@ -42,6 +44,11 @@ public IdeaSpecification getIdeaSpecification() { return new IdeaSpecification(filters); } + @JsonIgnore + public ServiceSpecification getServiceSpecification() { + return new ServiceSpecification(filters); + } + @JsonIgnore public FeatureProposalSpecification getFeatureProposalSpecification() { return new FeatureProposalSpecification(filters); From 8ddf949cc58555cc014fa3256a4b4463623ef3da Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Thu, 21 Jun 2018 12:19:15 -0500 Subject: [PATCH 2/3] Fix coverage, add simple test for page method of ServiceController --- .../app/controller/ServiceControllerTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/edu/tamu/app/controller/ServiceControllerTest.java b/src/test/java/edu/tamu/app/controller/ServiceControllerTest.java index ae9a9ac..2d12c1d 100644 --- a/src/test/java/edu/tamu/app/controller/ServiceControllerTest.java +++ b/src/test/java/edu/tamu/app/controller/ServiceControllerTest.java @@ -18,6 +18,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @@ -31,7 +34,9 @@ import edu.tamu.app.model.repo.IdeaRepo; import edu.tamu.app.model.repo.ServiceRepo; import edu.tamu.app.model.repo.UserRepo; +import edu.tamu.app.model.repo.specification.ServiceSpecification; import edu.tamu.app.model.request.AbstractRequest; +import edu.tamu.app.model.request.FilteredPageRequest; import edu.tamu.app.model.request.IssueRequest; import edu.tamu.app.model.request.ServiceRequest; import edu.tamu.app.service.ProjectService; @@ -59,6 +64,7 @@ public class ServiceControllerTest { private static final Service TEST_SERVICE3 = new Service(TEST_SERVICE3_NAME, TEST_SERVICE_STATUS, TEST_IS_AUTO, TEST_IS_PUBLIC, TEST_NOT_ON_SHORT_LIST, "", ""); private static final Service TEST_MODIFIED_SERVICE1 = new Service(TEST_SERVICE1_NAME, TEST_SERVICE_STATUS, TEST_IS_AUTO, TEST_IS_NOT_PUBLIC, TEST_NOT_ON_SHORT_LIST, "", ""); private static final List mockServiceList = new ArrayList(Arrays.asList(new Service[] { TEST_SERVICE1, TEST_SERVICE2, TEST_SERVICE3 })); + private static final Page mockPageableServiceList = new PageImpl(Arrays.asList(new Service[] { TEST_SERVICE1, TEST_SERVICE2, TEST_SERVICE3 })); private static final List mockPublicServiceList = new ArrayList(Arrays.asList(new Service[] { TEST_SERVICE1, TEST_SERVICE3 })); private static final User TEST_SERVICE = new User("123456789"); @@ -92,11 +98,14 @@ public class ServiceControllerTest { private ServiceController serviceController; @Before + @SuppressWarnings("unchecked") public void setup() throws UserNotFoundException { MockitoAnnotations.initMocks(this); when(credentials.getUin()).thenReturn("123456789"); when(userRepo.findByUsername(any(String.class))).thenReturn(Optional.of(TEST_SERVICE)); when(systemMonitorService.getOverallStatus()).thenReturn(new OverallStatus(edu.tamu.app.enums.OverallMessageType.SUCCESS, "Success")); + when(serviceRepo.findAll()).thenReturn(mockServiceList); + when(serviceRepo.findAll(any(ServiceSpecification.class), any(Pageable.class))).thenReturn(mockPageableServiceList); when(serviceRepo.findAllByOrderByStatusDescNameAsc()).thenReturn(mockServiceList); when(serviceRepo.findByIsPublicOrderByStatusDescNameAsc(true)).thenReturn(mockPublicServiceList); when(serviceRepo.findOne(any(Long.class))).thenReturn(TEST_SERVICE1); @@ -135,6 +144,17 @@ private int countPublicServices(List list) { return count; } + @Test + @SuppressWarnings("unchecked") + public void testPage() { + FilteredPageRequest mockFilter = new FilteredPageRequest(); + response = serviceController.page(mockFilter); + assertEquals("Not successful at getting paged Services", SUCCESS, response.getMeta().getStatus()); + + Page page = (Page) response.getPayload().get("PageImpl"); + assertEquals("The paged list of Services is the wrong length", mockPageableServiceList.getSize(), page.getSize()); + } + @Test public void testService() { response = serviceController.getService(TEST_SERVICE1.getId()); From ef8ead6f05fbbcce8048b25949ef3b6bbf22628d Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Thu, 21 Jun 2018 12:59:25 -0500 Subject: [PATCH 3/3] Add missing tests for page methods for relevant controllers Repeat the coverage fix for the ServiceController in all similar controllers. The FeatureProposalController seems to be inconsistent and used other names than "page" for its method. --- .../FeatureProposalControllerTest.java | 19 +++++++++++++++++++ .../app/controller/IdeaControllerTest.java | 19 +++++++++++++++++++ .../app/controller/NoteControllerTest.java | 19 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/test/java/edu/tamu/app/controller/FeatureProposalControllerTest.java b/src/test/java/edu/tamu/app/controller/FeatureProposalControllerTest.java index 2fb1d5e..a36f661 100644 --- a/src/test/java/edu/tamu/app/controller/FeatureProposalControllerTest.java +++ b/src/test/java/edu/tamu/app/controller/FeatureProposalControllerTest.java @@ -17,6 +17,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @@ -30,6 +33,8 @@ import edu.tamu.app.model.repo.FeatureProposalRepo; import edu.tamu.app.model.repo.ServiceRepo; import edu.tamu.app.model.repo.UserRepo; +import edu.tamu.app.model.repo.specification.FeatureProposalSpecification; +import edu.tamu.app.model.request.FilteredPageRequest; import edu.tamu.weaver.auth.model.Credentials; import edu.tamu.weaver.response.ApiResponse; @@ -56,6 +61,7 @@ public class FeatureProposalControllerTest { private static FeatureProposal TEST_FEATURE_PROPOSAL3 = new FeatureProposal(TEST_FEATURE_PROPOSAL_TITLE3, TEST_FEATURE_PROPOSAL_DESCRIPTION3, TEST_USER1); private static FeatureProposal TEST_MODIFIED_FEATURE_PROPOSAL = new FeatureProposal(TEST_MODIFIED_FEATURE_PROPOSAL_TITLE, TEST_MODIFIED_FEATURE_PROPOSAL_DESCRIPTION, TEST_USER2, TEST_SERVICE); private static List mockFeatureProposalList = new ArrayList(Arrays.asList(new FeatureProposal[] { TEST_FEATURE_PROPOSAL1, TEST_FEATURE_PROPOSAL2, TEST_FEATURE_PROPOSAL3 })); + private static Page mockPageableFeatureProposalList = new PageImpl(Arrays.asList(new FeatureProposal[] { TEST_FEATURE_PROPOSAL1, TEST_FEATURE_PROPOSAL2, TEST_FEATURE_PROPOSAL3 })); private static User user = new User("123456789"); @@ -80,11 +86,13 @@ public class FeatureProposalControllerTest { private FeatureProposalController featureProposalController; @Before + @SuppressWarnings("unchecked") public void setup() throws UserNotFoundException { MockitoAnnotations.initMocks(this); when(credentials.getUin()).thenReturn("123456789"); when(userRepo.findByUsername(any(String.class))).thenReturn(Optional.of(user)); when(featureProposalRepo.findAll()).thenReturn(mockFeatureProposalList); + when(featureProposalRepo.findAll(any(FeatureProposalSpecification.class), any(Pageable.class))).thenReturn(mockPageableFeatureProposalList); when(featureProposalRepo.findOne(any(Long.class))).thenReturn(TEST_FEATURE_PROPOSAL1); when(featureProposalRepo.create(any(FeatureProposal.class), any(Credentials.class))).thenReturn(TEST_FEATURE_PROPOSAL1); when(featureProposalRepo.create(any(Idea.class))).thenReturn(TEST_FEATURE_PROPOSAL1); @@ -94,6 +102,17 @@ public void setup() throws UserNotFoundException { doNothing().when(featureProposalRepo).delete(any(FeatureProposal.class)); } + @Test + @SuppressWarnings("unchecked") + public void testPage() { + FilteredPageRequest mockFilter = new FilteredPageRequest(); + response = featureProposalController.getAllFeatureProposalsByService(mockFilter); + assertEquals("Not successful at getting paged FeatureProposals", SUCCESS, response.getMeta().getStatus()); + + Page page = (Page) response.getPayload().get("PageImpl"); + assertEquals("The paged list of FeatureProposals is the wrong length", mockPageableFeatureProposalList.getSize(), page.getSize()); + } + @Test public void testFeatureProposal() { response = featureProposalController.getFeatureProposal(TEST_FEATURE_PROPOSAL1.getId()); diff --git a/src/test/java/edu/tamu/app/controller/IdeaControllerTest.java b/src/test/java/edu/tamu/app/controller/IdeaControllerTest.java index 428597a..61c261e 100644 --- a/src/test/java/edu/tamu/app/controller/IdeaControllerTest.java +++ b/src/test/java/edu/tamu/app/controller/IdeaControllerTest.java @@ -17,6 +17,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @@ -29,6 +32,8 @@ import edu.tamu.app.model.repo.IdeaRepo; import edu.tamu.app.model.repo.ServiceRepo; import edu.tamu.app.model.repo.UserRepo; +import edu.tamu.app.model.repo.specification.IdeaSpecification; +import edu.tamu.app.model.request.FilteredPageRequest; import edu.tamu.weaver.auth.model.Credentials; import edu.tamu.weaver.response.ApiResponse; @@ -55,6 +60,7 @@ public class IdeaControllerTest { private static Idea TEST_IDEA3 = new Idea(TEST_IDEA_TITLE3, TEST_IDEA_DESCRIPTION3, TEST_USER1); private static Idea TEST_MODIFIED_IDEA = new Idea(TEST_MODIFIED_IDEA_TITLE, TEST_MODIFIED_IDEA_DESCRIPTION, TEST_USER2, TEST_SERVICE); private static List mockIdeaList = new ArrayList(Arrays.asList(new Idea[] { TEST_IDEA1, TEST_IDEA2, TEST_IDEA3 })); + private static Page mockPageableIdeaList = new PageImpl(Arrays.asList(new Idea[] { TEST_IDEA1, TEST_IDEA2, TEST_IDEA3 })); private static User user = new User("123456789"); @@ -79,11 +85,13 @@ public class IdeaControllerTest { private IdeaController ideaController; @Before + @SuppressWarnings("unchecked") public void setup() throws UserNotFoundException { MockitoAnnotations.initMocks(this); when(credentials.getUin()).thenReturn("123456789"); when(userRepo.findByUsername(any(String.class))).thenReturn(Optional.of(user)); when(ideaRepo.findAll()).thenReturn(mockIdeaList); + when(ideaRepo.findAll(any(IdeaSpecification.class), any(Pageable.class))).thenReturn(mockPageableIdeaList); when(ideaRepo.findOne(any(Long.class))).thenReturn(TEST_IDEA1); when(ideaRepo.create(any(Idea.class), any(Credentials.class))).thenReturn(TEST_IDEA1); when(ideaRepo.update(any(Idea.class))).thenReturn(TEST_MODIFIED_IDEA); @@ -92,6 +100,17 @@ public void setup() throws UserNotFoundException { doNothing().when(ideaRepo).delete(any(Idea.class)); } + @Test + @SuppressWarnings("unchecked") + public void testPage() { + FilteredPageRequest mockFilter = new FilteredPageRequest(); + response = ideaController.page(mockFilter); + assertEquals("Not successful at getting paged Ideas", SUCCESS, response.getMeta().getStatus()); + + Page page = (Page) response.getPayload().get("PageImpl"); + assertEquals("The paged list of Ideas is the wrong length", mockPageableIdeaList.getSize(), page.getSize()); + } + @Test public void testIdea() { response = ideaController.getById(TEST_IDEA1.getId()); diff --git a/src/test/java/edu/tamu/app/controller/NoteControllerTest.java b/src/test/java/edu/tamu/app/controller/NoteControllerTest.java index 2243e79..ea28252 100644 --- a/src/test/java/edu/tamu/app/controller/NoteControllerTest.java +++ b/src/test/java/edu/tamu/app/controller/NoteControllerTest.java @@ -17,6 +17,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @@ -30,6 +33,8 @@ import edu.tamu.app.model.repo.NoteRepo; import edu.tamu.app.model.repo.ServiceRepo; import edu.tamu.app.model.repo.UserRepo; +import edu.tamu.app.model.repo.specification.NoteSpecification; +import edu.tamu.app.model.request.FilteredPageRequest; import edu.tamu.weaver.auth.model.Credentials; import edu.tamu.weaver.response.ApiResponse; @@ -52,6 +57,7 @@ public class NoteControllerTest { private static Note TEST_NOTE3 = new Note(TEST_NOTE_TITLE3, TEST_USER1); private static Note TEST_MODIFIED_NOTE = new Note(TEST_MODIFIED_NOTE_TITLE, TEST_USER2, NoteType.ISSUE, "", TEST_SERVICE); private static List mockNoteList = new ArrayList(Arrays.asList(new Note[] { TEST_NOTE1, TEST_NOTE2, TEST_NOTE3 })); + private static Page mockPageableNoteList = new PageImpl(Arrays.asList(new Note[] { TEST_NOTE1, TEST_NOTE2, TEST_NOTE3 })); private static User user = new User("123456789"); @@ -76,11 +82,13 @@ public class NoteControllerTest { private NoteController noteController; @Before + @SuppressWarnings("unchecked") public void setup() throws UserNotFoundException { MockitoAnnotations.initMocks(this); when(credentials.getUin()).thenReturn("123456789"); when(userRepo.findByUsername(any(String.class))).thenReturn(Optional.of(user)); when(noteRepo.findAll()).thenReturn(mockNoteList); + when(noteRepo.findAll(any(NoteSpecification.class), any(Pageable.class))).thenReturn(mockPageableNoteList); when(noteRepo.findOne(any(Long.class))).thenReturn(TEST_NOTE1); when(noteRepo.create(any(Note.class), any(Credentials.class))).thenReturn(TEST_NOTE1); when(noteRepo.update(any(Note.class))).thenReturn(TEST_MODIFIED_NOTE); @@ -89,6 +97,17 @@ public void setup() throws UserNotFoundException { doNothing().when(noteRepo).delete(any(Note.class)); } + @Test + @SuppressWarnings("unchecked") + public void testPage() { + FilteredPageRequest mockFilter = new FilteredPageRequest(); + response = noteController.page(mockFilter); + assertEquals("Not successful at getting paged Notes", SUCCESS, response.getMeta().getStatus()); + + Page page = (Page) response.getPayload().get("PageImpl"); + assertEquals("The paged list of Notes is the wrong length", mockPageableNoteList.getSize(), page.getSize()); + } + @Test public void testNote() { response = noteController.getById(TEST_NOTE1.getId());