From 5af6fef6303d327f542e4201d91b64be635917a1 Mon Sep 17 00:00:00 2001 From: Maciej Wereski Date: Wed, 6 Sep 2017 17:45:01 +0200 Subject: [PATCH] HTTP API: Creating new requests Change-Id: I42423080832d0bba13f9b219d05c0e9fa48c7d06 Signed-off-by: Maciej Wereski --- server/api/v1/api.go | 13 ++++++- server/api/v1/api_test.go | 44 +++++++++++++++++++++- server/api/v1/handlers.go | 19 +++++++++- server/api/v1/handlers_test.go | 40 +++++++++++++++++--- server/api/v1/testdata/new-req-POST.json | 1 - server/api/v1/testdata/new-req-bad-prio-POST.json | 1 + .../v1/testdata/new-req-malformed-json-POST.json | 1 + server/api/v1/testdata/new-req-valid-POST.json | 1 + 8 files changed, 109 insertions(+), 11 deletions(-) delete mode 100644 server/api/v1/testdata/new-req-POST.json create mode 100644 server/api/v1/testdata/new-req-bad-prio-POST.json create mode 100644 server/api/v1/testdata/new-req-malformed-json-POST.json create mode 100644 server/api/v1/testdata/new-req-valid-POST.json diff --git a/server/api/v1/api.go b/server/api/v1/api.go index 4331679..c923096 100644 --- a/server/api/v1/api.go +++ b/server/api/v1/api.go @@ -24,6 +24,7 @@ import ( "fmt" "net/http" + . "git.tizen.org/tools/boruta" "github.com/dimfeld/httptreemux" ) @@ -31,12 +32,18 @@ import ( // Returned values are directly converted to JSON responses. type responseData interface{} +// reqIDPack is used as input for JSON (un)marshaller. +type reqIDPack struct { + ReqID +} + // reqHandler denotes function that parses HTTP request and returns responseData. type reqHandler func(*http.Request, map[string]string) responseData // API provides HTTP API handlers. type API struct { - r *httptreemux.TreeMux + r *httptreemux.TreeMux + reqs Requests } // jsonMustMarshal tries to marshal responseData to JSON. Panics if error occurs. @@ -99,9 +106,11 @@ func routerSetHandler(grp *httptreemux.Group, path string, fn reqHandler, // NewAPI takes router and registers HTTP API in it. httptreemux.PanicHandler // function is set. Also other setting of the router may be modified. -func NewAPI(router *httptreemux.TreeMux) (api *API) { +func NewAPI(router *httptreemux.TreeMux, requestsAPI Requests) (api *API) { api = new(API) + api.reqs = requestsAPI + api.r = router api.r.PanicHandler = panicHandler diff --git a/server/api/v1/api_test.go b/server/api/v1/api_test.go index 038b678..32ca17d 100644 --- a/server/api/v1/api_test.go +++ b/server/api/v1/api_test.go @@ -34,7 +34,22 @@ import ( "github.com/stretchr/testify/assert" ) -const contentTypeJSON = "application/json" +const ( + contentTypeJSON = "application/json" + validReqJSON = `{ + "ID":1, + "State":"WAITING", + "Job":null, + "Priority":8, + "Deadline":"2200-12-31T01:02:03Z", + "ValidAfter":"2100-01-01T04:05:06Z", + "Caps":{ + "architecture":"armv7l", + "monitor":"yes" + }, + "Owner":{} + }` +) var update bool @@ -53,6 +68,20 @@ type allMocks struct { wm *mocks.MockWorkers } +var ( + // malformedJSONTestTempl may be used by functions that need to check + // for malformed JSON to initialize test. Every test must set path, name + // and method appropriately. + malformedJSONTestTempl = &requestTest{ + name: "malformed-json", + path: "", + methods: []string{}, + json: `{"Priority{}`, + contentType: contentTypeJSON, + status: http.StatusBadRequest, + } +) + func TestMain(m *testing.M) { flag.BoolVar(&update, "update", false, "update testdata") flag.Parse() @@ -66,13 +95,24 @@ func initTest(t *testing.T) (*assert.Assertions, *allMocks, *API) { rq: mocks.NewMockRequests(ctrl), wm: mocks.NewMockWorkers(ctrl), } - return assert.New(t), m, NewAPI(httptreemux.New()) + return assert.New(t), m, NewAPI(httptreemux.New(), m.rq) } func (m *allMocks) finish() { m.ctrl.Finish() } +func testFromTempl(templ *requestTest, name string, path string, + methods ...string) (ret requestTest) { + ret = *templ + ret.name = name + templ.name + ret.path = path + if len(methods) != 0 { + ret.methods = methods + } + return +} + func runTests(assert *assert.Assertions, api *API, tests []requestTest) { srv := httptest.NewServer(api.r) defer srv.Close() diff --git a/server/api/v1/handlers.go b/server/api/v1/handlers.go index def6731..494bdd8 100644 --- a/server/api/v1/handlers.go +++ b/server/api/v1/handlers.go @@ -19,13 +19,30 @@ package v1 import ( + "encoding/json" "net/http" + + . "git.tizen.org/tools/boruta" ) // newRequestHandler parses HTTP request for creating new Boruta request and // calls NewRequest(). func (api *API) newRequestHandler(r *http.Request, ps map[string]string) responseData { - return newServerError(ErrNotImplemented, "new request") + var newReq ReqInfo + defer r.Body.Close() + + if err := json.NewDecoder(r.Body).Decode(&newReq); err != nil { + return newServerError(err) + } + + //FIXME: currently UserInfo is ignored. Change when user support is added. + rid, err := api.reqs.NewRequest(newReq.Caps, newReq.Priority, UserInfo{}, + newReq.ValidAfter.UTC(), newReq.Deadline.UTC()) + if err != nil { + return newServerError(err) + } + + return reqIDPack{rid} } // closeRequestHandler parses HTTP request for closing existing Boruta request diff --git a/server/api/v1/handlers_test.go b/server/api/v1/handlers_test.go index 69bd11c..9aa92d1 100644 --- a/server/api/v1/handlers_test.go +++ b/server/api/v1/handlers_test.go @@ -16,23 +16,53 @@ package v1 import ( + "encoding/json" "net/http" "testing" + + . "git.tizen.org/tools/boruta" + "git.tizen.org/tools/boruta/requests" ) func TestNewRequestHandler(t *testing.T) { assert, m, api := initTest(t) defer m.finish() + prefix := "new-req-" + path := "/api/v1/reqs/" + methods := []string{http.MethodPost} + malformedJSONTest := testFromTempl(malformedJSONTestTempl, prefix, path, methods...) + + var req ReqInfo + err := json.Unmarshal([]byte(validReqJSON), &req) + assert.Nil(err) + + m.rq.EXPECT().NewRequest(req.Caps, req.Priority, req.Owner, req.ValidAfter, + req.Deadline).Return(ReqID(1), nil) + m.rq.EXPECT().NewRequest(req.Caps, Priority(32), req.Owner, req.ValidAfter, + req.Deadline).Return(ReqID(0), requests.ErrPriority) + tests := []requestTest{ { - name: "new-req", - path: "/api/v1/reqs/", - methods: []string{http.MethodPost}, - json: ``, + // valid request + name: prefix + "valid", + path: path, + methods: methods, + json: validReqJSON, contentType: contentTypeJSON, - status: http.StatusNotImplemented, + status: http.StatusCreated, + }, + { + // bad request - priority out of bounds + name: prefix + "bad-prio", + path: path, + methods: methods, + json: `{"Priority":32,"Deadline":"2200-12-31T01:02:03Z","ValidAfter":"2100-01-01T04:05:06Z","Caps":{"architecture":"armv7l","monitor":"yes"}}`, + contentType: contentTypeJSON, + status: http.StatusBadRequest, }, + // bad request - malformed request JSON + malformedJSONTest, } runTests(assert, api, tests) diff --git a/server/api/v1/testdata/new-req-POST.json b/server/api/v1/testdata/new-req-POST.json deleted file mode 100644 index 620302e..0000000 --- a/server/api/v1/testdata/new-req-POST.json +++ /dev/null @@ -1 +0,0 @@ -{"error":"not implemented yet: new request"} \ No newline at end of file diff --git a/server/api/v1/testdata/new-req-bad-prio-POST.json b/server/api/v1/testdata/new-req-bad-prio-POST.json new file mode 100644 index 0000000..f97a9e1 --- /dev/null +++ b/server/api/v1/testdata/new-req-bad-prio-POST.json @@ -0,0 +1 @@ +{"error":"invalid request: requested priority out of bounds"} \ No newline at end of file diff --git a/server/api/v1/testdata/new-req-malformed-json-POST.json b/server/api/v1/testdata/new-req-malformed-json-POST.json new file mode 100644 index 0000000..c59dde1 --- /dev/null +++ b/server/api/v1/testdata/new-req-malformed-json-POST.json @@ -0,0 +1 @@ +{"error":"invalid request: unexpected EOF"} \ No newline at end of file diff --git a/server/api/v1/testdata/new-req-valid-POST.json b/server/api/v1/testdata/new-req-valid-POST.json new file mode 100644 index 0000000..b4770ed --- /dev/null +++ b/server/api/v1/testdata/new-req-valid-POST.json @@ -0,0 +1 @@ +{"ReqID":1} \ No newline at end of file -- 2.7.4