[XAPID-1002] add more tests
diff --git a/api.go b/api.go index 55e5fcb..564dc8b 100644 --- a/api.go +++ b/api.go
@@ -395,7 +395,7 @@ case http.StatusOK: a.writePutRegisterResp(w, trackerResp) default: - log.Infof("apiPutRegister code: %v Reason: %v", trackerResp.code, trackerResp.body) + log.Debugf("apiPutRegister code: %v Reason: %v", trackerResp.code, string(trackerResp.body)) a.writeError(w, trackerResp.code, API_ERR_FROM_TRACKER, string(trackerResp.body)) } @@ -435,7 +435,7 @@ case http.StatusOK: a.writeConfigStatusResp(w, trackerResp) default: - log.Infof("apiPutConfigStatus code: %v Reason: %v", trackerResp.code, trackerResp.body) + log.Infof("apiPutConfigStatus code: %v Reason: %v", trackerResp.code, string(trackerResp.body)) a.writeError(w, trackerResp.code, API_ERR_FROM_TRACKER, string(trackerResp.body)) } } @@ -583,6 +583,23 @@ ReportedTime string `json:"reportedTime"` } +func (body *configStatusBody) validateBody() (bool, string) { + switch { + case !isValidUuid(body.ServiceId): + return false, "Bad/Missing gateway ServiceId" + case body.ReportedTime == "" || !isIso8601(body.ReportedTime): + return false, "Bad/Missing gateway reportedTime" + } + + for _, s := range body.StatusDetails { + isValid, reason := s.validateBody() + if !isValid { + return false, reason + } + } + return true, "" +} + type statusDetailsJson struct { Status string `json:"status"` ConfigurationId string `json:"configurationId"` @@ -590,21 +607,12 @@ Message string `json:"message"` } -func (body *configStatusBody) validateBody() (bool, string) { +func (s *statusDetailsJson) validateBody() (bool, string) { switch { - case !isValidUuid(body.ServiceId): - return false, "Bad/Missing gateway ServiceId" - case body.ReportedTime == "": - return false, "Bad/Missing gateway reportedTime" - } - - for _, s := range body.StatusDetails { - switch { - case s.Status == "": - return false, "Bad/Missing configuration Status" - case s.ConfigurationId == "": - return false, "Bad/Missing configuration ConfigurationId" - } + case s.Status == "": + return false, "Bad/Missing configuration Status" + case s.ConfigurationId == "": + return false, "Bad/Missing configuration ConfigurationId" } return true, "" }
diff --git a/api_test.go b/api_test.go index 8178020..4cf93c0 100644 --- a/api_test.go +++ b/api_test.go
@@ -25,8 +25,6 @@ . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" mathrand "math/rand" - "os" - "reflect" "strconv" "strings" "time" @@ -394,6 +392,203 @@ }) }) + Context("PUT /configurations/status", func() { + It("/configurations/status should validate request body", func() { + // setup test data + dummyClient.code = http.StatusOK + testData := [][]string{ + {GenerateUUID(), time.Now().Format(iso8601)}, + {GenerateUUID(), time.Now().Format(time.RFC3339)}, + {GenerateUUID(), "invalid-time"}, + {GenerateUUID(), time.Now().Format(time.RubyDate)}, + {"invalid-uuid", time.Now().Format(iso8601)}, + {"invalid-uuid", "invalid-time"}, + {"", time.Now().Format(time.RFC3339)}, + {GenerateUUID(), ""}, + } + + expectedCode := []int{ + http.StatusOK, + http.StatusOK, + http.StatusBadRequest, + http.StatusBadRequest, + http.StatusBadRequest, + http.StatusBadRequest, + http.StatusBadRequest, + http.StatusBadRequest, + } + + expectedBody := []string{ + "", + "", + "reportedTime", + "reportedTime", + "ServiceId", + "ServiceId", + "ServiceId", + "reportedTime", + } + + // setup http client + uri, err := url.Parse(apiTestUrl) + Expect(err).Should(Succeed()) + for i, data := range testData { + uri.Path = testApiMan.configStatusEndpoint + log.Debug(uri.String()) + configNum := mathrand.Intn(5) + statusDetails := make([]statusDetailsJson, 0) + expectedSlice := []string{} + for j := 0; j < configNum; j++ { + configStatus, expected := generateStatusDetails(0) + statusDetails = append(statusDetails, *configStatus) + expectedSlice = append(expectedSlice, expected) + } + + expectedSlice = append(expectedSlice, data...) + + if expectedCode[i] == http.StatusOK { + expectedBody[i] = strings.Join(expectedSlice, " ") + } + + reqBody, err := json.Marshal(configStatusBody{ + ServiceId: data[0], + ReportedTime: data[1], + StatusDetails: statusDetails, + }) + Expect(err).Should(Succeed()) + req, err := http.NewRequest("PUT", uri.String(), bytes.NewReader(reqBody)) + Expect(err).Should(Succeed()) + // http put + res, err := testClient.Do(req) + Expect(err).Should(Succeed()) + // parse response + defer res.Body.Close() + Expect(res.StatusCode).Should(Equal(expectedCode[i])) + body, err := ioutil.ReadAll(res.Body) + Expect(err).Should(Succeed()) + Expect(strings.Contains(strings.ToLower(string(body)), strings.ToLower(expectedBody[i]))).To(BeTrue()) + } + }) + + It("/configurations/status should validate status detail", func() { + // setup test data + dummyClient.code = http.StatusOK + testData := [][]string{ + {GenerateUUID(), time.Now().Format(iso8601)}, + {GenerateUUID(), time.Now().Format(iso8601)}, + } + + expectedCode := []int{ + http.StatusBadRequest, + http.StatusBadRequest, + } + + expectedBody := []string{ + "Status", + "ConfigurationId", + } + + // setup http client + uri, err := url.Parse(apiTestUrl) + Expect(err).Should(Succeed()) + for i, data := range testData { + uri.Path = testApiMan.configStatusEndpoint + log.Debug(uri.String()) + configNum := mathrand.Intn(5) + 1 + statusDetails := make([]statusDetailsJson, 0) + expectedSlice := []string{} + for j := 0; j < configNum; j++ { + configStatus, expected := generateStatusDetails(i + 1) + statusDetails = append(statusDetails, *configStatus) + expectedSlice = append(expectedSlice, expected) + } + + expectedSlice = append(expectedSlice, data...) + + reqBody, err := json.Marshal(configStatusBody{ + ServiceId: data[0], + ReportedTime: data[1], + StatusDetails: statusDetails, + }) + Expect(err).Should(Succeed()) + req, err := http.NewRequest("PUT", uri.String(), bytes.NewReader(reqBody)) + Expect(err).Should(Succeed()) + // http put + res, err := testClient.Do(req) + Expect(err).Should(Succeed()) + // parse response + defer res.Body.Close() + Expect(res.StatusCode).Should(Equal(expectedCode[i])) + body, err := ioutil.ReadAll(res.Body) + Expect(err).Should(Succeed()) + Expect(strings.Contains(strings.ToLower(string(body)), strings.ToLower(expectedBody[i]))).To(BeTrue()) + } + }) + + It("/configurations/status should populate errors from tracker", func() { + // setup test data + testData := [][]string{ + {GenerateUUID(), time.Now().Format(iso8601)}, + {GenerateUUID(), time.Now().Format(iso8601)}, + {GenerateUUID(), time.Now().Format(iso8601)}, + } + + expectedCode := []int{ + http.StatusBadRequest, + http.StatusInternalServerError, + http.StatusBadGateway, + } + + expectedBody := []string{ + "", + "", + "", + } + + // setup http client + uri, err := url.Parse(apiTestUrl) + Expect(err).Should(Succeed()) + for i, data := range testData { + uri.Path = testApiMan.configStatusEndpoint + log.Debug(uri.String()) + configNum := mathrand.Intn(5) + statusDetails := make([]statusDetailsJson, 0) + expectedSlice := []string{} + for j := 0; j < configNum; j++ { + configStatus, expected := generateStatusDetails(0) + statusDetails = append(statusDetails, *configStatus) + expectedSlice = append(expectedSlice, expected) + } + + expectedSlice = append(expectedSlice, data...) + + if expectedCode[i] == http.StatusOK { + expectedBody[i] = strings.Join(expectedSlice, " ") + } + + reqBody, err := json.Marshal(configStatusBody{ + ServiceId: data[0], + ReportedTime: data[1], + StatusDetails: statusDetails, + }) + Expect(err).Should(Succeed()) + req, err := http.NewRequest("PUT", uri.String(), bytes.NewReader(reqBody)) + Expect(err).Should(Succeed()) + + dummyClient.code = expectedCode[i] + // http put + res, err := testClient.Do(req) + Expect(err).Should(Succeed()) + // parse response + defer res.Body.Close() + Expect(res.StatusCode).Should(Equal(expectedCode[i])) + body, err := ioutil.ReadAll(res.Body) + Expect(err).Should(Succeed()) + Expect(strings.Contains(strings.ToLower(string(body)), strings.ToLower(expectedBody[i]))).To(BeTrue()) + } + }) + }) + Context("PUT /register/{uuid}", func() { It("/register should validate request", func() { // setup test data @@ -483,6 +678,57 @@ Expect(strings.Contains(strings.ToLower(string(body)), strings.ToLower(expectedBody[i]))).To(BeTrue()) } }) + + It("/register should populate errors from tracker", func() { + // setup test data + testData := [][]string{ + {GenerateUUID(), "pod", "podType", time.Now().Format(iso8601), "name", "type"}, + {GenerateUUID(), "pod", "podType", time.Now().Format(iso8601), "name", "type"}, + {GenerateUUID(), "pod", "podType", time.Now().Format(iso8601), "name", "type"}, + } + + expectedCode := []int{ + http.StatusBadRequest, + http.StatusInternalServerError, + http.StatusBadGateway, + } + + expectedBody := []string{ + strings.Join(testData[0], " "), + strings.Join(testData[1], " "), + strings.Join(testData[2], " "), + } + + // setup http client + uri, err := url.Parse(apiTestUrl) + Expect(err).Should(Succeed()) + for i, data := range testData { + dummyClient.code = expectedCode[i] + uuid := data[0] + uri.Path = strings.Replace(testApiMan.registerEndpoint, "{uuid}", uuid, 1) + reqBody, err := json.Marshal(registerBody{ + Uuid: data[0], + Pod: data[1], + PodType: data[2], + ReportedTime: data[3], + Name: data[4], + Type: data[5], + }) + Expect(err).Should(Succeed()) + log.Debug(uri.String()) + req, err := http.NewRequest("PUT", uri.String(), bytes.NewReader(reqBody)) + Expect(err).Should(Succeed()) + // http put + res, err := testClient.Do(req) + Expect(err).Should(Succeed()) + // parse response + defer res.Body.Close() + Expect(res.StatusCode).Should(Equal(expectedCode[i])) + body, err := ioutil.ReadAll(res.Body) + Expect(err).Should(Succeed()) + Expect(strings.Contains(strings.ToLower(string(body)), strings.ToLower(expectedBody[i]))).To(BeTrue()) + } + }) }) }) @@ -544,48 +790,6 @@ return detail } -type dummyDbManager struct { - unreadyBlobIds []string - readyDeployments []DataDeployment - localFSLocation string - fileResponse chan string - version string -} - -func (d *dummyDbManager) setDbVersion(version string) { - d.version = version -} - -func (d *dummyDbManager) initDb() error { - return nil -} - -func (d *dummyDbManager) getUnreadyBlobs() ([]string, error) { - return d.unreadyBlobIds, nil -} - -func (d *dummyDbManager) getReadyDeployments() ([]DataDeployment, error) { - return d.readyDeployments, nil -} - -func (d *dummyDbManager) updateLocalFsLocation(blobId, localFsLocation string) error { - file, err := os.Open(localFsLocation) - if err != nil { - return err - } - buff := make([]byte, 36) - _, err = file.Read(buff) - if err != nil { - return err - } - d.fileResponse <- string(buff) - return nil -} - -func (d *dummyDbManager) getLocalFSLocation(string) (string, error) { - return d.localFSLocation, nil -} - func GenerateUUID() string { buff := make([]byte, 16) @@ -599,43 +803,37 @@ return fmt.Sprintf("%x-%x-%x-%x-%x", buff[0:4], buff[4:6], buff[6:8], buff[8:10], buff[10:]) } -type dummyTrackerClient struct { - code int - args []string -} - -func (d *dummyTrackerClient) putConfigStatus(reqBody *configStatusBody) *trackerResponse { - - return &trackerResponse{ - code: d.code, - contentType: "application/octet-stream", - body: []byte(concatenateFields(reqBody)), +func generateStatusDetails(flag int) (*statusDetailsJson, string) { + log.Warnf("flag: %v", flag) + id := GenerateUUID() + var ret *statusDetailsJson + var expected string + switch flag { + case 1: // invalid Status + ret = &statusDetailsJson{ + Status: "", + ConfigurationId: id, + ErrorCode: "errorcode" + id, + Message: "message" + id, + } + expected = "Status" + case 2: // invalid ConfigurationId + ret = &statusDetailsJson{ + Status: "status" + id, + ConfigurationId: "", + ErrorCode: "errorcode" + id, + Message: "message" + id, + } + expected = "ConfigurationId" + default: + ret = &statusDetailsJson{ + Status: "status" + id, + ConfigurationId: id, + ErrorCode: "errorcode" + id, + Message: "message" + id, + } + expected = strings.Join([]string{ret.Status, ret.ConfigurationId, ret.ErrorCode, ret.Message}, " ") } -} -func (d *dummyTrackerClient) putRegister(uuid string, reqBody *registerBody) *trackerResponse { - - return &trackerResponse{ - code: d.code, - contentType: "application/octet-stream", - body: []byte(concatenateFields(reqBody)), - } -} - -func (d *dummyTrackerClient) putHeartbeat(uuid, reported string) *trackerResponse { - return &trackerResponse{ - code: d.code, - contentType: "application/octet-stream", - body: []byte(uuid + " " + reported), - } -} - -func concatenateFields(s interface{}) string { - v := reflect.ValueOf(s).Elem() - fields := []string{} - for i := 0; i < v.NumField(); i++ { - fields = append(fields, v.Field(i).String()) - } - log.Warn(strings.Join(fields, " ")) - return strings.Join(fields, " ") + return ret, expected }
diff --git a/bundle_test.go b/bundle_test.go index 7946f96..649e251 100644 --- a/bundle_test.go +++ b/bundle_test.go
@@ -15,15 +15,8 @@ package apiGatewayConfDeploy import ( - "net/http" - - "bytes" - "encoding/json" - "github.com/gorilla/mux" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "io" - "strings" "sync/atomic" "time" ) @@ -136,71 +129,3 @@ Expect(req.markFailedAt.IsZero()).Should(BeTrue()) }, 4) }) - -type dummyApiManager struct { - initCalled bool -} - -func (a *dummyApiManager) InitAPI() { - a.initCalled = true -} - -type dummyBlobServer struct { - serverEndpoint string - signedEndpoint string - signedTimeout *int32 - blobTimeout *int32 - resetTimeout bool -} - -func (b *dummyBlobServer) start() { - services.API().HandleFunc(b.serverEndpoint, b.returnSigned).Methods("GET") - services.API().HandleFunc(b.signedEndpoint, b.returnBlob).Methods("GET") -} - -// send a dummy uri as response -func (b *dummyBlobServer) returnSigned(w http.ResponseWriter, r *http.Request) { - defer GinkgoRecover() - if atomic.LoadInt32(b.signedTimeout) == int32(1) { - if b.resetTimeout { - atomic.StoreInt32(b.signedTimeout, 0) - } - time.Sleep(time.Second) - } - vars := mux.Vars(r) - blobId := vars["blobId"] - - uriString := strings.Replace(bundleTestUrl+b.signedEndpoint, "{blobId}", blobId, 1) - log.Debug("dummyBlobServer returnSigned: " + uriString) - - res := blobServerResponse{ - Id: blobId, - Kind: "Blob", - Self: r.RequestURI, - SignedUrl: uriString, - SignedUrlExpiryTimestamp: time.Now().Add(3 * time.Hour).Format(time.RFC3339), - } - - resBytes, err := json.Marshal(res) - Expect(err).Should(Succeed()) - _, err = io.Copy(w, bytes.NewReader(resBytes)) - Expect(err).Should(Succeed()) - w.Header().Set("Content-Type", headerSteam) -} - -// send blobId back as response -func (b *dummyBlobServer) returnBlob(w http.ResponseWriter, r *http.Request) { - defer GinkgoRecover() - if atomic.LoadInt32(b.blobTimeout) == int32(1) { - if b.resetTimeout { - atomic.StoreInt32(b.blobTimeout, 0) - } - time.Sleep(time.Second) - } - vars := mux.Vars(r) - blobId := vars["blobId"] - log.Debug("dummyBlobServer returnBlob id=" + blobId) - _, err := io.Copy(w, bytes.NewReader([]byte(blobId))) - Expect(err).Should(Succeed()) - w.Header().Set("Content-Type", headerSteam) -}
diff --git a/listener_test.go b/listener_test.go index 7549efb..41ad076 100644 --- a/listener_test.go +++ b/listener_test.go
@@ -277,45 +277,6 @@ }) }) -type dummyBundleManager struct { - requestChan chan *DownloadRequest - depChan chan *DataDeployment - delChan chan *DataDeployment - delBlobChan chan string -} - -func (bm *dummyBundleManager) initializeBundleDownloading() { - -} - -func (bm *dummyBundleManager) queueDownloadRequest(dep *DataDeployment) { - bm.depChan <- dep -} - -func (bm *dummyBundleManager) enqueueRequest(req *DownloadRequest) { - bm.requestChan <- req -} - -func (bm *dummyBundleManager) makeDownloadRequest(blobId string) *DownloadRequest { - return &DownloadRequest{ - blobId: blobId, - } -} - -func (bm *dummyBundleManager) deleteBundlesFromDeployments(deployments []DataDeployment) { - for i := range deployments { - bm.delChan <- &deployments[i] - } -} - -func (bm *dummyBundleManager) deleteBundleById(blobId string) { - bm.delBlobChan <- blobId -} - -func (bm *dummyBundleManager) Close() { - -} - func rowFromDeployment(dep *DataDeployment) common.Row { row := common.Row{} row["id"] = &common.ColumnVal{Value: dep.ID}
diff --git a/test_mocks_test.go b/test_mocks_test.go new file mode 100644 index 0000000..7f0d4cd --- /dev/null +++ b/test_mocks_test.go
@@ -0,0 +1,228 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package apiGatewayConfDeploy + +import ( + "bytes" + "encoding/json" + "github.com/gorilla/mux" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "io" + "net/http" + "os" + "reflect" + "strings" + "sync/atomic" + "time" +) + +type dummyTrackerClient struct { + code int + args []string +} + +func (d *dummyTrackerClient) putConfigStatus(reqBody *configStatusBody) *trackerResponse { + + return &trackerResponse{ + code: d.code, + contentType: "application/octet-stream", + body: []byte(concatenateFields(reqBody)), + } +} + +func (d *dummyTrackerClient) putRegister(uuid string, reqBody *registerBody) *trackerResponse { + + return &trackerResponse{ + code: d.code, + contentType: "application/octet-stream", + body: []byte(concatenateFields(reqBody)), + } +} + +func (d *dummyTrackerClient) putHeartbeat(uuid, reported string) *trackerResponse { + return &trackerResponse{ + code: d.code, + contentType: "application/octet-stream", + body: []byte(uuid + " " + reported), + } +} +func concatenateFields(s interface{}) string { + v := reflect.ValueOf(s).Elem() + return recursiveSerialize(v) +} + +func recursiveSerialize(v reflect.Value) string { + fields := []string{} + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Type().Kind() == reflect.Slice { + for j := 0; j < field.Len(); j++ { + fields = append(fields, recursiveSerialize(field.Index(j))) + } + } else { + fields = append(fields, field.String()) + } + } + return strings.Join(fields, " ") +} + +type dummyDbManager struct { + unreadyBlobIds []string + readyDeployments []DataDeployment + localFSLocation string + fileResponse chan string + version string +} + +func (d *dummyDbManager) setDbVersion(version string) { + d.version = version +} + +func (d *dummyDbManager) initDb() error { + return nil +} + +func (d *dummyDbManager) getUnreadyBlobs() ([]string, error) { + return d.unreadyBlobIds, nil +} + +func (d *dummyDbManager) getReadyDeployments() ([]DataDeployment, error) { + return d.readyDeployments, nil +} + +func (d *dummyDbManager) updateLocalFsLocation(blobId, localFsLocation string) error { + file, err := os.Open(localFsLocation) + if err != nil { + return err + } + buff := make([]byte, 36) + _, err = file.Read(buff) + if err != nil { + return err + } + d.fileResponse <- string(buff) + return nil +} + +func (d *dummyDbManager) getLocalFSLocation(string) (string, error) { + return d.localFSLocation, nil +} + +type dummyBundleManager struct { + requestChan chan *DownloadRequest + depChan chan *DataDeployment + delChan chan *DataDeployment + delBlobChan chan string +} + +func (bm *dummyBundleManager) initializeBundleDownloading() { + +} + +func (bm *dummyBundleManager) queueDownloadRequest(dep *DataDeployment) { + bm.depChan <- dep +} + +func (bm *dummyBundleManager) enqueueRequest(req *DownloadRequest) { + bm.requestChan <- req +} + +func (bm *dummyBundleManager) makeDownloadRequest(blobId string) *DownloadRequest { + return &DownloadRequest{ + blobId: blobId, + } +} + +func (bm *dummyBundleManager) deleteBundlesFromDeployments(deployments []DataDeployment) { + for i := range deployments { + bm.delChan <- &deployments[i] + } +} + +func (bm *dummyBundleManager) deleteBundleById(blobId string) { + bm.delBlobChan <- blobId +} + +func (bm *dummyBundleManager) Close() { + +} + +type dummyApiManager struct { + initCalled bool +} + +func (a *dummyApiManager) InitAPI() { + a.initCalled = true +} + +type dummyBlobServer struct { + serverEndpoint string + signedEndpoint string + signedTimeout *int32 + blobTimeout *int32 + resetTimeout bool +} + +func (b *dummyBlobServer) start() { + services.API().HandleFunc(b.serverEndpoint, b.returnSigned).Methods("GET") + services.API().HandleFunc(b.signedEndpoint, b.returnBlob).Methods("GET") +} + +// send a dummy uri as response +func (b *dummyBlobServer) returnSigned(w http.ResponseWriter, r *http.Request) { + defer GinkgoRecover() + if atomic.LoadInt32(b.signedTimeout) == int32(1) { + if b.resetTimeout { + atomic.StoreInt32(b.signedTimeout, 0) + } + time.Sleep(time.Second) + } + vars := mux.Vars(r) + blobId := vars["blobId"] + + uriString := strings.Replace(bundleTestUrl+b.signedEndpoint, "{blobId}", blobId, 1) + log.Debug("dummyBlobServer returnSigned: " + uriString) + + res := blobServerResponse{ + Id: blobId, + Kind: "Blob", + Self: r.RequestURI, + SignedUrl: uriString, + SignedUrlExpiryTimestamp: time.Now().Add(3 * time.Hour).Format(time.RFC3339), + } + + resBytes, err := json.Marshal(res) + Expect(err).Should(Succeed()) + _, err = io.Copy(w, bytes.NewReader(resBytes)) + Expect(err).Should(Succeed()) + w.Header().Set("Content-Type", headerSteam) +} + +// send blobId back as response +func (b *dummyBlobServer) returnBlob(w http.ResponseWriter, r *http.Request) { + defer GinkgoRecover() + if atomic.LoadInt32(b.blobTimeout) == int32(1) { + if b.resetTimeout { + atomic.StoreInt32(b.blobTimeout, 0) + } + time.Sleep(time.Second) + } + vars := mux.Vars(r) + blobId := vars["blobId"] + log.Debug("dummyBlobServer returnBlob id=" + blobId) + _, err := io.Copy(w, bytes.NewReader([]byte(blobId))) + Expect(err).Should(Succeed()) + w.Header().Set("Content-Type", headerSteam) +}