HTTP API: Implement listing and filtering requests
authorMaciej Wereski <m.wereski@partner.samsung.com>
Fri, 29 Sep 2017 09:14:22 +0000 (11:14 +0200)
committerMaciej Wereski <m.wereski@partner.samsung.com>
Tue, 5 Jun 2018 09:26:40 +0000 (11:26 +0200)
Change-Id: I27f8d037978ef0f0f60ea5c7ee406f8459a90796
Signed-off-by: Maciej Wereski <m.wereski@partner.samsung.com>
14 files changed:
requests/requests.go
requests/requests_test.go
server/api/v1/api_test.go
server/api/v1/handlers.go
server/api/v1/handlers_test.go
server/api/v1/testdata/filter-reqs-bad-filter-POST.json [new file with mode: 0644]
server/api/v1/testdata/filter-reqs-empty-POST.json [new file with mode: 0644]
server/api/v1/testdata/filter-reqs-empty-json-POST.json [new file with mode: 0644]
server/api/v1/testdata/filter-reqs-malformed-json-POST.json [new file with mode: 0644]
server/api/v1/testdata/filter-reqs-nil-POST.json [new file with mode: 0644]
server/api/v1/testdata/filter-reqs-nomatch-all-POST.json [new file with mode: 0644]
server/api/v1/testdata/filter-reqs-valid-filter-POST.json [new file with mode: 0644]
server/api/v1/testdata/list-reqs-all-GET.json
server/api/v1/testdata/list-reqs-filter-POST.json [deleted file]

index c343f22..952c55a 100644 (file)
@@ -18,6 +18,8 @@
 package requests
 
 import (
+       "reflect"
+       "sort"
        "sync"
        "time"
 
@@ -257,16 +259,20 @@ func (reqs *ReqsCollection) GetRequestInfo(reqID ReqID) (ReqInfo, error) {
 }
 
 // ListRequests is part of implementation of Requests interface. It returns slice
-// of ReqInfo that matches ListFilter.
+// of ReqInfo that matches ListFilter. Returned slice is sorted by request ids.
 func (reqs *ReqsCollection) ListRequests(filter ListFilter) ([]ReqInfo, error) {
        reqs.mutex.RLock()
-       defer reqs.mutex.RUnlock()
        res := make([]ReqInfo, 0, len(reqs.requests))
        for _, req := range reqs.requests {
-               if filter == nil || filter.Match(req) {
+               if filter == nil || reflect.ValueOf(filter).IsNil() ||
+                       filter.Match(req) {
                        res = append(res, *req)
                }
        }
+       reqs.mutex.RUnlock()
+       // TODO(mwereski): HTTP backend needs this to be sorted. This isn't best
+       // place to do it, rethink that when DB backend is implemented.
+       sort.Slice(res, func(i, j int) bool { return res[i].ID < res[j].ID })
        return res, nil
 }
 
index 2226ffb..3dcb708 100644 (file)
@@ -471,9 +471,16 @@ func TestListRequests(t *testing.T) {
        }
 
        // Nil filter should return all requests.
+
+       // Nil interface.
        resp, err := rqueue.ListRequests(nil)
        assert.Nil(err)
        checkReqs(reqs, resp)
+       var flt *reqFilter
+       // Concrete type is nil but interface isn't nil.
+       resp, err = rqueue.ListRequests(flt)
+       assert.Nil(err)
+       checkReqs(reqs, resp)
 }
 
 func TestAcquireWorker(t *testing.T) {
index f532063..854e10a 100644 (file)
@@ -38,6 +38,9 @@ import (
 const (
        contentTypeJSON = "application/json"
        invalidID       = "test"
+       dateLayout      = "2006-01-02"
+       past            = "1683-09-12"
+       future          = "2222-12-31"
        validReqJSON    = `{
                "ID":1,
                "State":"WAITING",
index 29cf5b2..80aa825 100644 (file)
@@ -20,6 +20,7 @@ package v1
 
 import (
        "encoding/json"
+       "io"
        "net/http"
 
        . "git.tizen.org/tools/boruta"
@@ -86,7 +87,25 @@ func (api *API) getRequestInfoHandler(r *http.Request, ps map[string]string) res
 // listRequestsHandler parses HTTP request for listing Boruta requests and calls
 // ListRequests().
 func (api *API) listRequestsHandler(r *http.Request, ps map[string]string) responseData {
-       return newServerError(ErrNotImplemented, "list requests")
+       var filter *RequestFilter
+       defer r.Body.Close()
+
+       if r.Method == http.MethodPost {
+               filter = new(RequestFilter)
+               if err := json.NewDecoder(r.Body).Decode(filter); err != nil {
+                       if err != io.EOF {
+                               return newServerError(err)
+                       }
+                       filter = nil
+               }
+       }
+
+       reqs, err := api.reqs.ListRequests(filter)
+       if err != nil {
+               return newServerError(err)
+       }
+
+       return reqs
 }
 
 // acquireWorkerHandler parses HTTP request for acquiring worker for Boruta
index 5b0811f..9d8a556 100644 (file)
@@ -17,9 +17,11 @@ package v1
 
 import (
        "encoding/json"
+       "errors"
        "fmt"
        "net/http"
        "testing"
+       "time"
 
        . "git.tizen.org/tools/boruta"
        "git.tizen.org/tools/boruta/requests"
@@ -159,23 +161,105 @@ func TestListRequestsHandler(t *testing.T) {
        assert, m, api := initTest(t)
        defer m.finish()
 
+       deadline, err := time.Parse(dateLayout, future)
+       assert.Nil(err)
+       validAfter, err := time.Parse(dateLayout, past)
+       assert.Nil(err)
+       reqs := []ReqInfo{
+               {ID: 1, Priority: (HiPrio + LoPrio) / 2, State: WAIT,
+                       Deadline: deadline, ValidAfter: validAfter},
+               {ID: 2, Priority: (HiPrio+LoPrio)/2 + 1, State: WAIT,
+                       Deadline: deadline, ValidAfter: validAfter},
+               {ID: 3, Priority: (HiPrio + LoPrio) / 2, State: CANCEL,
+                       Deadline: deadline, ValidAfter: validAfter},
+               {ID: 4, Priority: (HiPrio+LoPrio)/2 + 1, State: CANCEL,
+                       Deadline: deadline, ValidAfter: validAfter},
+       }
+
+       methods := []string{http.MethodPost}
+       prefix := "filter-reqs-"
+       filterPath := "/api/v1/reqs/list"
+       malformedJSONTest := testFromTempl(malformedJSONTestTempl, prefix, filterPath, methods...)
+
+       validFilter := NewRequestFilter("WAIT", "")
+       m.rq.EXPECT().ListRequests(validFilter).Return(reqs[:2], nil)
+
+       emptyFilter := NewRequestFilter("", "")
+       m.rq.EXPECT().ListRequests(emptyFilter).Return(reqs, nil).Times(2)
+       m.rq.EXPECT().ListRequests(nil).Return(reqs, nil).Times(3)
+
+       missingFilter := NewRequestFilter("INVALID", "")
+       m.rq.EXPECT().ListRequests(missingFilter).Return([]ReqInfo{}, nil)
+
+       // Currently ListRequests doesn't return any error hence the meaningless values.
+       badFilter := NewRequestFilter("FAIL", "-1")
+       m.rq.EXPECT().ListRequests(badFilter).Return([]ReqInfo{}, errors.New("foo bar: pizza failed"))
+
        tests := []requestTest{
+               // Valid filter - list some requests.
+               {
+                       name:        prefix + "valid-filter",
+                       path:        filterPath,
+                       methods:     methods,
+                       json:        string(jsonMustMarshal(validFilter)),
+                       contentType: contentTypeJSON,
+                       status:      http.StatusOK,
+               },
+               // List all requests.
                {
                        name:        "list-reqs-all",
                        path:        "/api/v1/reqs/",
                        methods:     []string{http.MethodGet, http.MethodHead},
                        json:        ``,
                        contentType: contentTypeJSON,
-                       status:      http.StatusNotImplemented,
+                       status:      http.StatusOK,
                },
+               // Empty body - list all requests.
                {
-                       name:        "list-reqs-filter",
-                       path:        "/api/v1/reqs/list",
-                       methods:     []string{http.MethodPost},
-                       json:        ``,
+                       name:        prefix + "empty-json",
+                       path:        filterPath,
+                       methods:     methods,
+                       json:        "",
                        contentType: contentTypeJSON,
-                       status:      http.StatusNotImplemented,
+                       status:      http.StatusOK,
                },
+               // Nil filter - list all requests (same as emptyFilter).
+               {
+                       name:        prefix + "nil",
+                       path:        filterPath,
+                       methods:     methods,
+                       json:        string(jsonMustMarshal(nil)),
+                       contentType: contentTypeJSON,
+                       status:      http.StatusOK,
+               },
+               // Empty filter - list all requests.
+               {
+                       name:        prefix + "empty",
+                       path:        filterPath,
+                       methods:     methods,
+                       json:        string(jsonMustMarshal(emptyFilter)),
+                       contentType: contentTypeJSON,
+                       status:      http.StatusOK,
+               },
+               // No matches
+               {
+                       name:        prefix + "nomatch-all",
+                       path:        filterPath,
+                       methods:     methods,
+                       json:        string(jsonMustMarshal(missingFilter)),
+                       contentType: contentTypeJSON,
+                       status:      http.StatusOK,
+               },
+               // Error
+               {
+                       name:        prefix + "bad-filter",
+                       path:        filterPath,
+                       methods:     methods,
+                       json:        string(jsonMustMarshal(badFilter)),
+                       contentType: contentTypeJSON,
+                       status:      http.StatusBadRequest,
+               },
+               malformedJSONTest,
        }
 
        runTests(assert, api, tests)
diff --git a/server/api/v1/testdata/filter-reqs-bad-filter-POST.json b/server/api/v1/testdata/filter-reqs-bad-filter-POST.json
new file mode 100644 (file)
index 0000000..f6dd9fd
--- /dev/null
@@ -0,0 +1 @@
+{"error":"invalid request: foo bar: pizza failed"}
\ No newline at end of file
diff --git a/server/api/v1/testdata/filter-reqs-empty-POST.json b/server/api/v1/testdata/filter-reqs-empty-POST.json
new file mode 100644 (file)
index 0000000..1879046
--- /dev/null
@@ -0,0 +1 @@
+[{"ID":1,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":2,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":3,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null},{"ID":4,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null}]
\ No newline at end of file
diff --git a/server/api/v1/testdata/filter-reqs-empty-json-POST.json b/server/api/v1/testdata/filter-reqs-empty-json-POST.json
new file mode 100644 (file)
index 0000000..1879046
--- /dev/null
@@ -0,0 +1 @@
+[{"ID":1,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":2,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":3,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null},{"ID":4,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null}]
\ No newline at end of file
diff --git a/server/api/v1/testdata/filter-reqs-malformed-json-POST.json b/server/api/v1/testdata/filter-reqs-malformed-json-POST.json
new file mode 100644 (file)
index 0000000..c59dde1
--- /dev/null
@@ -0,0 +1 @@
+{"error":"invalid request: unexpected EOF"}
\ No newline at end of file
diff --git a/server/api/v1/testdata/filter-reqs-nil-POST.json b/server/api/v1/testdata/filter-reqs-nil-POST.json
new file mode 100644 (file)
index 0000000..1879046
--- /dev/null
@@ -0,0 +1 @@
+[{"ID":1,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":2,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":3,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null},{"ID":4,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null}]
\ No newline at end of file
diff --git a/server/api/v1/testdata/filter-reqs-nomatch-all-POST.json b/server/api/v1/testdata/filter-reqs-nomatch-all-POST.json
new file mode 100644 (file)
index 0000000..0637a08
--- /dev/null
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/server/api/v1/testdata/filter-reqs-valid-filter-POST.json b/server/api/v1/testdata/filter-reqs-valid-filter-POST.json
new file mode 100644 (file)
index 0000000..a168045
--- /dev/null
@@ -0,0 +1 @@
+[{"ID":1,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":2,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null}]
\ No newline at end of file
index ed5be09..1879046 100644 (file)
@@ -1 +1 @@
-{"error":"not implemented yet: list requests"}
\ No newline at end of file
+[{"ID":1,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":2,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"WAITING","Job":null,"Caps":null},{"ID":3,"Priority":8,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null},{"ID":4,"Priority":9,"Owner":{"Groups":null},"Deadline":"2222-12-31T00:00:00Z","ValidAfter":"1683-09-12T00:00:00Z","State":"CANCELLED","Job":null,"Caps":null}]
\ No newline at end of file
diff --git a/server/api/v1/testdata/list-reqs-filter-POST.json b/server/api/v1/testdata/list-reqs-filter-POST.json
deleted file mode 100644 (file)
index ed5be09..0000000
+++ /dev/null
@@ -1 +0,0 @@
-{"error":"not implemented yet: list requests"}
\ No newline at end of file