Pagination of workflow list API 23/56623/1
authorojasdubey <ojas.dubey@amdocs.com>
Tue, 17 Jul 2018 13:28:13 +0000 (18:58 +0530)
committerojasdubey <ojas.dubey@amdocs.com>
Tue, 17 Jul 2018 13:28:13 +0000 (18:58 +0530)
Implemented spring boot pagination
support

Change-Id: Id56eb7c72af1622348708b7303605db8914be564
Issue-ID: SDC-1483
Signed-off-by: ojasdubey <ojas.dubey@amdocs.com>
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/RestConstants.java
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/WorkflowController.java
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/exceptionshandlers/CustomizedResponseEntityExceptionHandler.java
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/api/types/CollectionWrapper.java
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/WorkflowManager.java
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/WorkflowNameComparator.java [new file with mode: 0644]
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/exceptions/InvalidPaginationParameterException.java [new file with mode: 0644]
workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImpl.java
workflow/workflow-designer-be/src/test/java/org/onap/sdc/workflow/RestPath.java
workflow/workflow-designer-be/src/test/java/org/onap/sdc/workflow/api/WorkflowControllerTest.java
workflow/workflow-designer-be/src/test/java/org/onap/sdc/workflow/services/impl/WorkflowManagerImplTest.java

index 8f02be0..47a757e 100644 (file)
@@ -6,4 +6,10 @@ public class RestConstants {
     }
 
     public static final String USER_ID_HEADER_PARAM = "USER_ID";
+    public static final String LIMIT_PARAM = "size";
+    public static final String OFFSET_PARAM = "page";
+    public static final String SORT_PARAM = "sort";
+    public static final String SORT_FIELD_NAME = "name";
+    public static final int LIMIT_DEFAULT = 20;
+    public static final int OFFSET_DEFAULT = 0;
 }
index b224e84..de35320 100644 (file)
@@ -1,14 +1,28 @@
 package org.onap.sdc.workflow.api;
 
+import static org.onap.sdc.workflow.api.RestConstants.LIMIT_DEFAULT;
+import static org.onap.sdc.workflow.api.RestConstants.SORT_FIELD_NAME;
+import static org.onap.sdc.workflow.api.RestConstants.SORT_PARAM;
 import static org.onap.sdc.workflow.api.RestConstants.USER_ID_HEADER_PARAM;
 
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Arrays;
+import java.util.Set;
+
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.onap.sdc.workflow.api.types.CollectionWrapper;
 import org.onap.sdc.workflow.persistence.types.Workflow;
 import org.onap.sdc.workflow.services.WorkflowManager;
+import org.onap.sdc.workflow.services.exceptions.InvalidPaginationParameterException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.data.web.SortDefault;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
@@ -35,8 +49,14 @@ public class WorkflowController {
 
     @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation("List workflows")
-    public CollectionWrapper<Workflow> list(@RequestHeader(USER_ID_HEADER_PARAM) String user) {
-        return new CollectionWrapper<>(workflowManager.list());
+    public CollectionWrapper<Workflow> list(@RequestHeader(USER_ID_HEADER_PARAM) String user,
+                                            @PageableDefault(size = LIMIT_DEFAULT)
+                                            @SortDefault.SortDefaults({
+                                                @SortDefault(sort = SORT_FIELD_NAME, direction = Sort.Direction.ASC)
+                                            }) Pageable pageable) {
+        PageRequest pageRequest = createPageRequest(pageable);
+        return new CollectionWrapper<>(pageRequest.getPageSize(), pageRequest.getPageNumber(),
+                workflowManager.list(pageRequest));
     }
 
     @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
@@ -63,4 +83,17 @@ public class WorkflowController {
         workflowManager.update(workflow);
         return workflow;
     }
+
+    private PageRequest createPageRequest(Pageable pageable) {
+        Set<String> validSortFields = ImmutableSet.of(SORT_FIELD_NAME);
+        Sort sort = pageable.getSort();
+        for (Sort.Order order : sort) {
+            String sortFieldName = order.getProperty();
+            if (!sortFieldName.equalsIgnoreCase(SORT_FIELD_NAME)) {
+                throw new InvalidPaginationParameterException(SORT_PARAM, sortFieldName,
+                        "is not supported. Supported values are: " + Arrays.toString(validSortFields.toArray()));
+            }
+        }
+        return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
+    }
 }
index 68fd41a..bcc28d6 100644 (file)
@@ -1,19 +1,25 @@
 package org.onap.sdc.workflow.api.exceptionshandlers;
 
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
 import static org.springframework.http.HttpStatus.FORBIDDEN;
 import static org.springframework.http.HttpStatus.NOT_FOUND;
 import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
 
 import org.onap.sdc.workflow.services.exceptions.EntityNotFoundException;
 import org.onap.sdc.workflow.services.exceptions.InvalidArtifactException;
+import org.onap.sdc.workflow.services.exceptions.InvalidPaginationParameterException;
 import org.onap.sdc.workflow.services.exceptions.UniqueValueViolationException;
 import org.onap.sdc.workflow.services.exceptions.VersionCreationException;
 import org.onap.sdc.workflow.services.exceptions.VersionModificationException;
 import org.onap.sdc.workflow.services.exceptions.VersionStateModificationException;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.ServletRequestBindingException;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.WebRequest;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
 @ControllerAdvice
@@ -32,6 +38,20 @@ public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExce
         return new ResponseEntity<>(exception.getMessage(), NOT_FOUND);
     }
 
+    @ExceptionHandler({InvalidPaginationParameterException.class})
+    public final ResponseEntity<String> handlePaginationException(InvalidPaginationParameterException exception) {
+        return new ResponseEntity<>(exception.getMessage(), BAD_REQUEST);
+    }
+
+    //For missing header exceptions
+    @Override
+    public ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex,
+                                                                       HttpHeaders headers, HttpStatus status,
+                                                                       WebRequest request) {
+        return new ResponseEntity<>(ex.getMessage(), BAD_REQUEST);
+    }
+
+
     @ExceptionHandler({InvalidArtifactException.class, VersionModificationException.class,
             VersionStateModificationException.class})
     public final ResponseEntity<String> handleInvalidArtifactException(
index 653b0dc..1d583dd 100644 (file)
@@ -11,6 +11,18 @@ public class CollectionWrapper<T> {
     private int offset;
     private Collection<T> results;
 
+
+    public CollectionWrapper() {
+        //Default constructor for object mappers
+    }
+
+    public CollectionWrapper(int limit, int offset, Collection<T> results) {
+        this.results = results;
+        this.limit = limit;
+        this.offset = offset;
+        this.total = results.size();
+    }
+
     public CollectionWrapper(Collection<T> results) {
         this.results = results;
         this.total = results.size();
index 01c0b05..e2572b7 100644 (file)
@@ -2,10 +2,11 @@ package org.onap.sdc.workflow.services;
 
 import java.util.Collection;
 import org.onap.sdc.workflow.persistence.types.Workflow;
+import org.springframework.data.domain.Pageable;
 
 public interface WorkflowManager {
 
-    Collection<Workflow> list();
+    Collection<Workflow> list(Pageable pageable);
 
     Workflow get(Workflow workflow);
 
diff --git a/workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/WorkflowNameComparator.java b/workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/WorkflowNameComparator.java
new file mode 100644 (file)
index 0000000..1ba4dcd
--- /dev/null
@@ -0,0 +1,16 @@
+package org.onap.sdc.workflow.services;
+
+import java.util.Comparator;
+
+import org.onap.sdc.workflow.persistence.types.Workflow;
+
+public class WorkflowNameComparator implements Comparator<Workflow>{
+
+    @Override
+    public int compare(Workflow workflow1, Workflow workflow2) {
+        String workflowName1 = workflow1.getName().toLowerCase();
+        String workflowName2 = workflow2.getName().toLowerCase();
+        //ascending order
+        return workflowName1.compareTo(workflowName2);
+    }
+}
diff --git a/workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/exceptions/InvalidPaginationParameterException.java b/workflow/workflow-designer-be/src/main/java/org/onap/sdc/workflow/services/exceptions/InvalidPaginationParameterException.java
new file mode 100644 (file)
index 0000000..fde8fd9
--- /dev/null
@@ -0,0 +1,8 @@
+package org.onap.sdc.workflow.services.exceptions;
+
+public class InvalidPaginationParameterException extends RuntimeException {
+
+    public InvalidPaginationParameterException(String parameterName, String parameterValue, String message) {
+        super(String.format("Requested %s: %s %s", parameterName, parameterValue, message));
+    }
+}
\ No newline at end of file
index 8ac5025..2cb897f 100644 (file)
@@ -1,11 +1,18 @@
 package org.onap.sdc.workflow.services.impl;
 
+import static org.onap.sdc.workflow.api.RestConstants.SORT_FIELD_NAME;
+
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.onap.sdc.workflow.persistence.types.Workflow;
 import org.onap.sdc.workflow.services.UniqueValueService;
 import org.onap.sdc.workflow.services.WorkflowManager;
+import org.onap.sdc.workflow.services.WorkflowNameComparator;
 import org.onap.sdc.workflow.services.exceptions.EntityNotFoundException;
 import org.onap.sdc.workflow.services.impl.mappers.WorkflowMapper;
 import org.openecomp.sdc.versioning.ItemManager;
@@ -13,6 +20,8 @@ import org.openecomp.sdc.versioning.types.Item;
 import org.openecomp.sdc.versioning.types.ItemStatus;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Service;
 
 @Service("workflowManager")
@@ -36,9 +45,11 @@ public class WorkflowManagerImpl implements WorkflowManager {
     }
 
     @Override
-    public Collection<Workflow> list() {
-        return itemManager.list(ITEM_PREDICATE).stream().map(workflowMapper::itemToWorkflow)
-                          .collect(Collectors.toList());
+    public Collection<Workflow> list(Pageable pageRequest) {
+        List<Workflow> workflowList = itemManager.list(ITEM_PREDICATE).stream()
+                .map( workflowMapper::itemToWorkflow).collect(Collectors.toList());
+        sortWorkflowList(workflowList, pageRequest);
+        return applyLimitAndOffset(workflowList, pageRequest);
     }
 
     @Override
@@ -76,4 +87,39 @@ public class WorkflowManagerImpl implements WorkflowManager {
         item.setVersionStatusCounters(retrievedItem.getVersionStatusCounters());
         itemManager.update(item);
     }
+
+    private List<Workflow> applyLimitAndOffset(List<Workflow> workflowList, Pageable pageRequest) {
+        int limit = pageRequest.getPageSize();
+        int offset = pageRequest.getPageNumber();
+        int totalNumOfWorkflows = workflowList.size();
+        List<Workflow> selectedWorkflows;
+        try {
+            if (limit > totalNumOfWorkflows) {
+                limit = totalNumOfWorkflows;
+            }
+            int startIndex = offset * limit;
+            int endIndex = startIndex + limit;
+            if (endIndex > totalNumOfWorkflows) {
+                endIndex = totalNumOfWorkflows;
+            }
+            selectedWorkflows = workflowList.subList(startIndex, endIndex);
+        } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
+            selectedWorkflows = new ArrayList<>();
+        }
+        return selectedWorkflows;
+    }
+
+    private void sortWorkflowList(List<Workflow> workflowList, Pageable pageRequest) {
+        Comparator<Workflow> comparator = getWorkflowListComparator();
+        if (pageRequest.getSort().getOrderFor(SORT_FIELD_NAME).getDirection() == Sort.Direction.ASC) {
+            workflowList.sort(comparator);
+        } else {
+            workflowList.sort(Collections.reverseOrder(comparator));
+        }
+    }
+
+    private Comparator<Workflow> getWorkflowListComparator() {
+        //More comparators can be added if required based on sort field name
+        return new WorkflowNameComparator();
+    }
 }
index 266ca91..ba99996 100644 (file)
@@ -1,10 +1,46 @@
 package org.onap.sdc.workflow;
 
+import static org.onap.sdc.workflow.api.RestConstants.LIMIT_PARAM;
+import static org.onap.sdc.workflow.api.RestConstants.OFFSET_PARAM;
+import static org.onap.sdc.workflow.api.RestConstants.SORT_PARAM;
+
 public class RestPath {
+    private RestPath() {
+        //Hiding implicit constructor
+    }
+
     private static final String WORKFLOWS_URL = "/workflows";
     private static final String WORKFLOW_URL_FORMATTER = WORKFLOWS_URL + "/%s";
     private static final String VERSIONS_URL_FORMATTER = WORKFLOWS_URL + "/%s/versions";
     private static final String VERSION_URL_FORMATTER = WORKFLOWS_URL + "/%s/versions/%s";
+    private static final String SORT_QUERY_STRING_FORMATTER = SORT_PARAM + "=%s";
+    private static final String LIMIT_QUERY_STRING_FORMATTER = LIMIT_PARAM + "=%s";
+    private static final String OFFSET_QUERY_STRING_FORMATTER = OFFSET_PARAM + "=%s";
+    private static final String WORKFLOW_URL_FORMATTER_QUERY_PARAMS_ALL =
+            WORKFLOWS_URL + "?" + SORT_QUERY_STRING_FORMATTER+ "&" +  LIMIT_QUERY_STRING_FORMATTER + "&" +
+                    OFFSET_QUERY_STRING_FORMATTER;
+    private static final String WORKFLOW_URL_FORMATTER_QUERY_PARAMS_NO_SORT =
+            WORKFLOWS_URL + "?" + LIMIT_QUERY_STRING_FORMATTER + "&" + OFFSET_QUERY_STRING_FORMATTER;
+    private static final String WORKFLOW_URL_FORMATTER_QUERY_PARAMS_NO_SORT_AND_LIMIT =
+            WORKFLOWS_URL + "?" + OFFSET_QUERY_STRING_FORMATTER;
+    private static final String WORKFLOW_URL_FORMATTER_QUERY_PARAMS_NO_SORT_AND_OFFSET =
+            WORKFLOWS_URL + "?" + LIMIT_QUERY_STRING_FORMATTER;
+
+    public static String getWorkflowsPathAllQueryParams(String sort, String limit, String offset){
+        return String.format(WORKFLOW_URL_FORMATTER_QUERY_PARAMS_ALL, sort, limit, offset);
+    }
+
+    public static String getWorkflowsPathNoSort(String limit, String offset){
+        return String.format(WORKFLOW_URL_FORMATTER_QUERY_PARAMS_NO_SORT, limit, offset);
+    }
+
+    public static String getWorkflowsPathNoSortAndLimit(String offset){
+        return String.format(WORKFLOW_URL_FORMATTER_QUERY_PARAMS_NO_SORT_AND_LIMIT, offset);
+    }
+
+    public static String getWorkflowsPathNoSortAndOffset(String limit){
+        return String.format(WORKFLOW_URL_FORMATTER_QUERY_PARAMS_NO_SORT_AND_OFFSET, limit);
+    }
 
     public static String getWorkflowsPath(){
         return WORKFLOWS_URL;
index 69b25b0..7c27505 100644 (file)
@@ -8,6 +8,10 @@ import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.onap.sdc.workflow.TestUtil.createWorkflow;
+import static org.onap.sdc.workflow.api.RestConstants.LIMIT_DEFAULT;
+import static org.onap.sdc.workflow.api.RestConstants.OFFSET_DEFAULT;
+import static org.onap.sdc.workflow.api.RestConstants.SORT_FIELD_NAME;
+import static org.onap.sdc.workflow.api.RestConstants.SORT_PARAM;
 import static org.onap.sdc.workflow.api.RestConstants.USER_ID_HEADER_PARAM;
 import static org.springframework.http.MediaType.APPLICATION_JSON;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -16,9 +20,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
 import com.google.gson.Gson;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,9 +35,12 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.onap.sdc.workflow.RestPath;
+import org.onap.sdc.workflow.api.exceptionshandlers.CustomizedResponseEntityExceptionHandler;
+import org.onap.sdc.workflow.api.types.CollectionWrapper;
 import org.onap.sdc.workflow.persistence.types.Workflow;
 import org.onap.sdc.workflow.services.WorkflowManager;
 import org.openecomp.sdc.versioning.types.Item;
+import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
 import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.test.web.servlet.MockMvc;
@@ -42,6 +54,11 @@ public class WorkflowControllerTest {
             "Missing request header '%s' for method parameter of type String";
     private static final String USER_ID = "userId";
     private static final Gson GSON = new Gson();
+    private static final String USER_ID_HEADER = "USER_ID";
+    private static final String INVALID_PAGINATION_PARAMETER_FORMAT = "Requested %s: %s %s";
+    private static final String PAGINATION_PARAMETER_INVALID_SORT_FIELD_SUFFIX =
+            "is not supported. Supported values are: ";
+    private static final String DEFAULT_SORT_VALUE = "name,asc";
 
     private MockMvc mockMvc;
 
@@ -55,6 +72,10 @@ public class WorkflowControllerTest {
     @Before
     public void setUp() {
         mockMvc = MockMvcBuilders.standaloneSetup(workflowController).build();
+        mockMvc = MockMvcBuilders.standaloneSetup(workflowController)
+                .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
+                .setControllerAdvice(new CustomizedResponseEntityExceptionHandler())
+                .build();
     }
 
     @Test
@@ -64,7 +85,7 @@ public class WorkflowControllerTest {
                 mockMvc.perform(get(RestPath.getWorkflowPath(workflowMock.getId())).contentType(APPLICATION_JSON))
                        .andDo(print()).andExpect(status().isBadRequest()).andExpect(status().is(400)).andReturn()
                        .getResponse();
-        assertEquals(String.format(MISSING_REQUEST_HEADER_ERRROR_FORMAT, "USER_ID"), response.getErrorMessage());
+        assertEquals(String.format(MISSING_REQUEST_HEADER_ERRROR_FORMAT, USER_ID_HEADER), response.getContentAsString());
     }
 
     @Test
@@ -83,19 +104,129 @@ public class WorkflowControllerTest {
         MockHttpServletResponse response =
                 mockMvc.perform(get(RestPath.getWorkflowsPath()).contentType(APPLICATION_JSON)).andDo(print())
                        .andExpect(status().isBadRequest()).andExpect(status().is(400)).andReturn().getResponse();
-        assertEquals(String.format(MISSING_REQUEST_HEADER_ERRROR_FORMAT, USER_ID_HEADER_PARAM), response.getErrorMessage());
+        assertEquals(String.format(MISSING_REQUEST_HEADER_ERRROR_FORMAT, USER_ID_HEADER_PARAM), response.getContentAsString());
     }
 
     @Test
     public void shouldReturn5WorkflowWhen5WorkflowsExists() throws Exception {
         int numOfWorkflows = 5;
         List<Workflow> workflowMocks = createWorkflows(numOfWorkflows);
-        doReturn(workflowMocks).when(workflowManagerMock).list();
+        doReturn(workflowMocks).when(workflowManagerMock).list(any());
         mockMvc.perform(
                 get(RestPath.getWorkflowsPath()).header(USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
                .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.results", hasSize(numOfWorkflows)));
     }
 
+    @Test
+    public void shouldReturnSortedLimitOffsetAppliedWorkflows() throws Exception {
+        List<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
+        doReturn(workflowMocks).when(workflowManagerMock).list(any());
+        mockMvc.perform(
+                get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.results", hasSize(2)));
+    }
+
+    @Test
+    public void shouldReturnResultsWithDefaultWhenLimitIsNegative() throws Exception {
+        List<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
+        doReturn(workflowMocks).when(workflowManagerMock).list(any());
+        MockHttpServletResponse response = mockMvc.perform(
+                get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "-2", "1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(status().is(200)).andReturn()
+                .getResponse();
+        CollectionWrapper workflowListResponse =
+                new ObjectMapper().readValue(response.getContentAsString(), CollectionWrapper.class);
+        assertEquals(LIMIT_DEFAULT, workflowListResponse.getLimit());
+        assertEquals(1, workflowListResponse.getOffset());
+        assertEquals(2, workflowListResponse.getTotal());
+    }
+
+    @Test
+    public void shouldFallbackOnDefaultOffsetWhenOffsetIsNegative() throws Exception {
+        MockHttpServletResponse response = mockMvc.perform(
+                get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "-1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(status().is(200)).andReturn()
+                .getResponse();
+        CollectionWrapper workflowListResponse =
+                new ObjectMapper().readValue(response.getContentAsString(), CollectionWrapper.class);
+        assertEquals(2, workflowListResponse.getLimit());
+        assertEquals(OFFSET_DEFAULT, workflowListResponse.getOffset());
+        assertEquals(0, workflowListResponse.getTotal());
+    }
+
+    @Test
+    public void shouldFallbackOnDefaultLimitWhenLimitIsNotAnInteger() throws Exception {
+        MockHttpServletResponse response = mockMvc.perform(
+                get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "abc", "0"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(status().is(200)).andReturn()
+                .getResponse();
+        CollectionWrapper workflowListResponse =
+                new ObjectMapper().readValue(response.getContentAsString(), CollectionWrapper.class);
+        assertEquals(LIMIT_DEFAULT, workflowListResponse.getLimit());
+        assertEquals(0, workflowListResponse.getOffset());
+        assertEquals(0, workflowListResponse.getTotal());
+    }
+
+    @Test
+    public void shouldFallbackOnDefaultOffsetWhenOffsetIsNotAnInteger() throws Exception {
+        MockHttpServletResponse response = mockMvc.perform(
+                get(RestPath.getWorkflowsPathAllQueryParams(DEFAULT_SORT_VALUE, "2", "abc"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(status().is(200)).andReturn()
+                .getResponse();
+        CollectionWrapper workflowListResponse =
+                new ObjectMapper().readValue(response.getContentAsString(), CollectionWrapper.class);
+        assertEquals(2, workflowListResponse.getLimit());
+        assertEquals(OFFSET_DEFAULT, workflowListResponse.getOffset());
+        assertEquals(0, workflowListResponse.getTotal());
+    }
+
+    @Test
+    public void shouldThrowExceptionWhenSortFieldIsInvalid() throws Exception {
+        MockHttpServletResponse response = mockMvc.perform(
+                get(RestPath.getWorkflowsPathAllQueryParams("invalidSortField,asc", "2", "1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isBadRequest()).andExpect(status().is(400)).andReturn()
+                .getResponse();
+        assertEquals(String.format(INVALID_PAGINATION_PARAMETER_FORMAT, SORT_PARAM, "invalidSortField",
+                PAGINATION_PARAMETER_INVALID_SORT_FIELD_SUFFIX + getSupportedSortFields()),
+                response.getContentAsString());
+    }
+
+    @Test
+    public void shouldReturnAscSortedLimitOffsetAppliedWorkflowsWhenSortIsNotSpecified() throws Exception {
+        List<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
+        doReturn(workflowMocks).when(workflowManagerMock).list(any());
+        mockMvc.perform(
+                get(RestPath.getWorkflowsPathNoSort("2", "1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.results", hasSize(2)));
+    }
+
+    @Test
+    public void shouldReturnDefaultLimitOffsetAppliedWorkflowsWhenLimitIsNotSpecified() throws Exception {
+        List<Workflow> workflowMocks = createLimit2AndOffset1For5WorkflowList();
+        doReturn(workflowMocks).when(workflowManagerMock).list(any());
+        mockMvc.perform(
+                get(RestPath.getWorkflowsPathNoSortAndLimit("1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.results", hasSize(2)));
+    }
+
+    @Test
+    public void shouldReturnDefaultOffsetAppliedWorkflowsWhenOffsetIsNotSpecified() throws Exception {
+        List<Workflow> workflowMocks = createLimit1WorkflowList();
+        doReturn(workflowMocks).when(workflowManagerMock).list(any());
+        mockMvc.perform(
+                get(RestPath.getWorkflowsPathNoSortAndOffset("1"))
+                        .header(RestConstants.USER_ID_HEADER_PARAM, USER_ID).contentType(APPLICATION_JSON))
+                .andDo(print()).andExpect(status().isOk()).andExpect(jsonPath("$.results", hasSize(1)));
+    }
+
     @Test
     public void shouldCreateWorkflowWhenCallingPostRESTRequest() throws Exception {
         Item item = new Item();
@@ -117,5 +248,22 @@ public class WorkflowControllerTest {
         return workflowList;
     }
 
+    private List<Workflow> createLimit2AndOffset1For5WorkflowList() {
+        List<Workflow> workflowList = new ArrayList<>();
+        workflowList.add(createWorkflow(2, true));
+        workflowList.add(createWorkflow(3, true));
+        return workflowList;
+    }
+
+    private List<Workflow> createLimit1WorkflowList() {
+        List<Workflow> workflowList = new ArrayList<>();
+        workflowList.add(createWorkflow(0, true));
+        return workflowList;
+    }
+
+
+    private Set<String> getSupportedSortFields() {
+        return ImmutableSet.of(SORT_FIELD_NAME);
+    }
 
 }
\ No newline at end of file
index 17037d9..500011b 100644 (file)
@@ -6,9 +6,14 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.onap.sdc.workflow.TestUtil.createItem;
 import static org.onap.sdc.workflow.TestUtil.createWorkflow;
+import static org.onap.sdc.workflow.api.RestConstants.SORT_FIELD_NAME;
 
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
+
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -21,6 +26,8 @@ import org.onap.sdc.workflow.services.impl.mappers.WorkflowMapper;
 import org.openecomp.sdc.versioning.ItemManager;
 import org.openecomp.sdc.versioning.types.Item;
 import org.openecomp.sdc.versioning.types.ItemStatus;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
 @RunWith(SpringJUnit4ClassRunner.class)
@@ -30,6 +37,7 @@ public class WorkflowManagerImplTest {
     private static final String WORKFLOW_TYPE = "WORKFLOW";
     private static final String WORKFLOW_NAME_UNIQUE_TYPE = "WORKFLOW_NAME";
     private List<Item> itemList;
+    private List<Workflow> workflowList;
 
     @Mock
     private WorkflowMapper workflowMapperMock;
@@ -46,16 +54,21 @@ public class WorkflowManagerImplTest {
 
     @Before
     public void setUp() {
-        itemList = Arrays.asList(createItem(1, true, true), createItem(2, true, true), createItem(3, true, true));
-
+        itemList = Arrays.asList(createItem(1, true, true), createItem(2, true, true), createItem(3, true, true),
+                createItem(4, true, true), createItem(5, true, true));
+        workflowList = Arrays.asList(createWorkflow(1, true), createWorkflow(2, true), createWorkflow(3, true),
+                createWorkflow(4, true), createWorkflow(5, true));
     }
 
 
     @Test
     public void shouldReturnWorkflowVersionList() {
-
+        PageRequest pageRequest = createPageRequest(2, 1, Sort.Direction.DESC, SORT_FIELD_NAME);
         doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
-        workflowManager.list();
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        workflowManager.list(pageRequest);
         verify(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
     }
 
@@ -116,4 +129,83 @@ public class WorkflowManagerImplTest {
         workflowManager.update(createWorkflow(1, true));
     }
 
+    @Test
+    public void shouldListAllWorkflowsWhenLimitAndOffsetAreValid() {
+        PageRequest pageRequest = createPageRequest(5, 0, Sort.Direction.ASC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Assert.assertEquals(5, workflowManager.list(pageRequest).size());
+    }
+
+    @Test
+    public void shouldListLimitFilteredWorkflowsInFirstOffsetRange() {
+        PageRequest pageRequest = createPageRequest(3, 0, Sort.Direction.ASC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Assert.assertEquals(3, workflowManager.list(pageRequest).size());
+    }
+
+    @Test
+    public void shouldListLimitFilteredWorkflowsInSecondOffsetRange() {
+        PageRequest pageRequest = createPageRequest(3, 1, Sort.Direction.ASC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Assert.assertEquals(2, workflowManager.list(pageRequest).size());
+    }
+
+    @Test
+    public void shouldListAllWorkflowsWhenLimitGreaterThanTotalRecordsAndOffsetInRange() {
+        PageRequest pageRequest = createPageRequest(10, 0, Sort.Direction.ASC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Assert.assertEquals(5, workflowManager.list(pageRequest).size());
+    }
+
+    @Test
+    public void shouldNotListWorkflowsIfOffsetGreaterThanTotalRecords() {
+        PageRequest pageRequest = createPageRequest(3, 6, Sort.Direction.ASC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Assert.assertEquals(0, workflowManager.list(pageRequest).size());
+    }
+
+    @Test
+    public void shouldNotListWorkflowsBothLimitAndOffsetGreaterThanTotalRecords() {
+        PageRequest pageRequest = createPageRequest(10, 10, Sort.Direction.ASC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Assert.assertEquals(0, workflowManager.list(pageRequest).size());
+    }
+
+    @Test
+    public void shouldListLimitOffsetAppliedWorkflowsSortedInDescOrder() {
+        PageRequest pageRequest = createPageRequest(2, 1, Sort.Direction.DESC, SORT_FIELD_NAME);
+        doReturn(itemList).when(itemManagerMock).list(WorkflowManagerImpl.ITEM_PREDICATE);
+        for (int i=0; i<itemList.size(); i++) {
+            doReturn(workflowList.get(i)).when(workflowMapperMock).itemToWorkflow(itemList.get(i));
+        }
+        Collection<Workflow> workflows = workflowManager.list(pageRequest);
+        Assert.assertEquals(2, workflows.size());
+        Iterator<Workflow> workflowIterator = workflows.iterator();
+        Assert.assertEquals("workflowName3", workflowIterator.next().getName());
+        Assert.assertEquals("workflowName2", workflowIterator.next().getName());
+    }
+
+    private PageRequest createPageRequest(int limit, int offset,
+                                          Sort.Direction sortOrder, String sortField) {
+        return PageRequest.of(offset, limit, sortOrder, sortField);
+    }
+
 }
\ No newline at end of file